Python程序设计与人工智能基础 课件 第9章 异常_第1页
Python程序设计与人工智能基础 课件 第9章 异常_第2页
Python程序设计与人工智能基础 课件 第9章 异常_第3页
Python程序设计与人工智能基础 课件 第9章 异常_第4页
Python程序设计与人工智能基础 课件 第9章 异常_第5页
已阅读5页,还剩37页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第9章异常本章结构9.1异常概述9.2捕获与处理异常9.3触发异常9.4自定义异常9.5实验:正确设置密码9.6数据科学入门:解决八皇后问题本章小结学习目标了解Python异常的类型,能够说出常见的异常类型掌握异常捕获的方法,能够灵活使用多种方法捕获异常掌握异常处理的方法,能够应用多种语句处理异常掌握触发异常的机制,能够运用raise语句的方法手动触发异常掌握自定义异常类的方法,能够灵活捕捉与抛出自定义异常类Python也提供了处理异常的机制,可以让读者捕获并处理这些错误,让程序继续沿着一条不会出错的路径执行。本章从捕获与处理异常、触发异常及自定义异常等方面进行讲解。9.1异常概述程序运行时常会碰到一些错误,例如除数为0、年龄为负数、数组下标越界等,这些错误如果不能发现并加以处理,很可能会导致程序崩溃。本节从异常与异常类两方面进行讲解。9.1.1认识异常核心概念异常指的是程序执行过程中发生的导致程序无法继续执行的事件,通常引发操作系统异常处理流程来处理发生的事件。例如,计算除数为0时的程序,具体代码如下所示。a=5/0在Python程序中,用5除以0,并赋值给变量a,因为0作除数是没有意义的,所以运行后会产生错误,除数为0时返回的错误信息如图9-1所示。由图9-1可知,前两段指明了错误的位置,最后一句表示出错的类型。在Python中,把这种运行时产生错误的情况叫做异常。关键要点Python异常处理是一种有效的机制,可以处理各种错误和异常情况,提高程序的稳定性和可靠性在实际开发中,合理利用异常处理可以提升程序的健壮性和可维护性图9-1除数为0时返回的错误信息9.1.2异常类核心概念Python提供了一系列的内置异常类,不同类型的异常可以用于捕获和处理不同的错误情况。常见的异常类如表9-1所示。表9-1常见的异常类异常类名称基类说明BaseExceptionobject所有异常类的直接或间接基类ExceptionBaseException所有非退出异常的基类SystemExitBaseException程序请求退出时抛出的异常KeyboardInterruptBaseException用户中断执行(通常是按下Ctrl+C)时抛出GeneratorExitBaseException生成器发生异常,通知退出ArithmeticErrorException所有数值计算错误的基类FloatingPointErrorArithmeticError浮点运算错误OverflowErrorArithmeticError数值运算超出最大限制ZeroDivisionErrorArithmeticError除零导致的异常AssertionErrorException断言语句失败AttributeErrorException对象没有这个属性表9-1常见的异常类(续)异常类名称基类说明EOFErrorException读取超过文件结尾OSErrorExceptionI/O相关错误的基类ImportErrorException导入模块/对象失败LookupErrorException查找错误的基类IndexErrorLookupError序列中没有此索引KeyErrorLookupError映射中没有这个键MemoryErrorException内存溢出错误NameErrorException未声明、未初始化对象UnboundLocalErrorNameError访问未初始化的本地变量ReferenceErrorException弱引用试图访问已经垃圾回收了的对象关键要点在表9-1中,BaseException是异常的顶级类,但用户定义的类不能直接继承这个类,而是要继承Exception。Exception类是与应用相关异常的顶层基类,除了系统退出事件类(SystemExit、KeyboardInterrupt和GeneratorExit)之外,几乎所有用户定义的类都应该继承自这个类,而不是BaseException类。表9-1常见的异常类(续)异常类名称基类说明RuntimeErrorException一般的运行时错误NotImplementedErrorRuntimeError尚未实现的方法SyntaxErrorException语法错误IndentationErrorSyntaxError缩进错误TabErrorIndentationErrorTab和空格混用SystemErrorException一般的解释器系统错误TypeErrorException对类型无效的操作ValueErrorException传入无效的参数关键要点在表9-1中,BaseException是异常的顶级类,但用户定义的类不能直接继承这个类,而是要继承Exception。Exception类是与应用相关异常的顶层基类,除了系统退出事件类(SystemExit、KeyboardInterrupt和GeneratorExit)之外,几乎所有用户定义的类都应该继承自这个类,而不是BaseException类。9.2捕获与处理异常Python异常处理机制会涉及as、try、except、else、finally等关键字。本节将针对捕获与处理异常的方法进行讲解。9.2.1try...except语句核心概念为了防止程序运行中遇到异常而意外终止,可以通过try…except语句实现异常处理。try...except语句的语法格式如下。try:语句块1except异常类型:语句块2当try语句块中某条语句出现异常时,程序就不再执行try语句块中后面的语句,而是直接执行except语句块。下面通过一个示例演示通过try...except语句实现异常处理。在创建好的"Python程序设计基础"项目中创建Chapter09文件夹,在Chapter09文件夹中创建一个名为tryExcept.py的文件,具体代码如文件9-1所示。文件9-1tryExcept.py1.try:2.a=float(input('请输入被除数:'))3.b=float(input('请输入除数:'))4.print(a,'/',b,'运算结果为',a/b)5.print('运算结束')6.exceptZeroDivisionError:7.print('除数不能为0')8.print('程序结束')在上述代码中,第6行代码获取ZeroDivisionError异常,即除数为零错误。try...except语句运行结果文件tryExcept.py的运行结果如图9-2所示。图9-2文件tryExcept.py的运行结果由图9-2可知,程序运行时,输入被除数与除数分别为26与8,结果为3.25。再次运行文件tryExcept.py时,输入被除数与除数分别为2和0,运行结果如图9-3所示。图9-3再次运行文件tryExcept.py的运行结果关键要点从两次运行结果可看出,程序没有触发异常与触发异常执行的流程并不一致。程序中一旦发生异常,就不会执行try语句块中剩余的语句,而是直接执行except语句块。另外,本程序捕获并处理了异常,因此,当输入的除数为0时,程序可以正常结束,而不是终止运行。捕获并处理多种异常核心概念文件tryExcept.py只能捕捉except后面的异常类,如果发生其他类型异常,程序依然会终止。例如,运行文件tryExcept.py,输入ab后回车,则程序出现错误,为了保证程序正常运行,此时就需要捕获并处理多种异常,其语法格式如下。try:语句块1except异常类型1:语句块2except异常类型2:语句块3…下面通过一个示例演示通过try...except语句捕获并处理多种异常。在Chapter09文件夹中创建一个名为tryExcept.py的文件,具体代码如文件9-2所示。文件9-2tryExcept.py1.try:2.a=float(input('请输入被除数:'))3.b=float(input('请输入除数:'))4.print(a,'/',b,'结果为',a/b)5.print('运算结束')6.exceptZeroDivisionError:7.print('除数不能为0')8.exceptValueError:9.print('传入参数无效')10.print('程序结束')在上述代码中,第8行代码增加了捕获ValueError异常的语句,即数值错误异常。多种异常运行结果及全部异常捕获文件TryExceptD.py的运行结果如图9-4所示。图9-4文件tryExceptD.py的运行结果由图9-4可知,当输入被除数为ab时,返回"传入参数无效",因为ab不是数字。关键要点在程序中,虽然开发者可以编写处理多种异常的代码,但异常是防不胜防的,很有可能再出现其他异常,此时就需要捕获并处理所有可能发生的异常。在文件TryExceptD.py第10行前添加的程序如下。except:print('其他错误')通过except语句可以处理除ZeroDivisionError异常和ValueError异常外的其他所有异常。9.2.2使用as获取异常信息核心概念为了区分不同的异常,可以使用as关键字来获取异常信息,其语法格式如下。try:#可能出现异常的语句except异常类名as异常对象名:#处理异常的语句通过异常对象名便可以访问异常信息。下面通过一个示例演示通过as关键字获取异常信息的使用方法。在Chapter09文件夹中创建一个名为error.py的文件,对文件TryExceptD.py的第6行代码进行修改,具体代码如文件9-3所示。文件9-3error.py1.try:2.a=float(input('请输入被除数:'))3.b=float(input('请输入除数:'))4.print(a,'/',b,'结果为',a/b)5.print('运算结束')6.exceptZeroDivisionErrorase:7.print('除数不能为0')8.print("异常信息:",e)9.exceptValueErrorasv:10.print('传入参数无效')11.print("参数异常信息:",v)12.print('程序结束')在上述代码中,第6、9行代码使用as关键字获取异常信息;第8、11行代码打印获取的异常信息。as获取异常信息运行结果文件error.py的运行结果如图9-5所示。图9-5文件error.py的运行结果由图9-5可知,输入被除数与除数分别为5和0。输出的异常信息为floatdivisionbyzero。文件error.py的运行结果如图9-6所示。图9-6再次运行文件error.py的运行结果再次运行文件error.py,由图9-6可知,当输入被除数为ab时,返回"传入参数无效",输出的参数异常信息为couldnotconvertstringtofloat:'ab'。异常信息的简化写法及获取所有异常核心概念在文件error.py中第6~11行代码还可以修改为如下代码形式。except(ZeroDivisionError,ValueError)ase:print("异常信息:",e)如果程序需要获取所有异常信息,则可以使用如下语法格式。try:#可能出现异常的语句exceptBaseExceptionas异常对象名:#处理异常的语句所有的异常类都继承自BaseException类,因此上述语句可以获取所有异常信息。在文件error.py中第6~11行代码还可以修改为如下代码形式。exceptBaseExceptionase:print("异常信息:",e)关键要点上述语句可以获取所有异常,但不建议在程序中直接捕获所有异常,因为它会隐藏所有程序员未想到并且未做好准备处理的错误。9.2.3try...except...else语句核心概念try...except...else语句还有一个可选的else子句,如果使用这个子句,那么必须放在所有的except子句之后,else子句将在try子句没有发生任何异常的时候执行,语法格式如下。try:#可能出现异常的语句exceptBaseExceptionas异常对象名:#处理异常的语句else:#未捕获到异常执行的语句如果try语句内出现了异常,则执行except语句块,否则执行else语句块。下面通过一个示例演示通过try...except...else语句实现异常处理。在Chapter09文件夹中创建一个名为TryElse.py的文件,具体代码如文件9-4所示。文件9-4TryElse.py1.try:2.a=float(input('请输入被除数:'))3.b=float(input('请输入除数:'))4.result=a/b5.print('运算结束')6.exceptBaseExceptionase:7.print(type(e),e)8.else:9.print(a,'/',b,'运算结果为',result)10.print('程序结束')在上述代码中,第6行代码使用BaseException类和as语句获取所有异常信息;第9行代码使用else语句执行未捕获到异常执行的语句。try...except...else运行结果当除数为0时,文件TryElse.py的运行结果如图9-7所示。图9-7当除数为0时的运行结果由图9-7可知,当输入除数为0时,捕获ZeroDivisionError异常,程序只执行了try和except语句,不运行else语句。当被除数或除数为ab时,文件TryElse.py的运行结果如图9-8所示。图9-8当被除数或除数为ab时的运行结果由图9-8可知,当输入被除数为ab时,捕获ValueError异常,程序也执行了try和except语句,不运行else语句。当被除数与除数分别为5和2时,文件TryElse.py的运行结果如图9-9所示。图9-9当被除数与除数分别为5和2时文件TryElse.py的运行结果由图9-9可知,当输入被除数5、除数2时,程序正常运行,先执行try下的语句块,再执行else下的语句块。9.2.4try...finally语句核心概念Python异常处理机制还提供了一个finally语句,通常用来为try块中的程序做扫尾清理、资源回收工作。try...finally语句的语法格式如下。try:#可能出现异常的语句finally:#无论是否发生异常都会执行的语句try...finally语句无论是否发生异常都将执行最后的代码,finally语句只要求和try搭配使用,else语句块将在try块成功执行且没有引发异常时执行,包括try块中有return语句。如果在try块中引发了异常,则else语句块不会执行。下面通过一个示例演示通过try...finally语句实现资源的回收。在Chapter09文件夹中创建一个名为tryFinally.py的文件,具体代码如文件9-5所示。文件9-5tryFinally.py1.try:2.f=open('test.txt','a+')3.i=14.whileTrue:5.str=input('请输入第%d行字符串(按Q结束):'%i)6.ifstr.upper()=='Q':7.break8.f.write(str+'\n')9.i+=110.exceptKeyboardInterrupt:11.print('程序中断!(Ctrl+F2)')12.finally:13.f.close()14.print('文件关闭')15.print('程序结束')在上述代码中,第4~9行代码使用无限循环结构接收键盘输入的字符串,在接收到键盘输入的字符串Q时,程序跳出无限循环结构。try...finally语句运行结果接收到字符串Q程序结束时文件tryFinally.py的运行结果如图9-10所示。图9-10接收到字符串Q程序结束时文件tryFinally.py的运行结果由图9-10可知,当提示输入第3行字符串时,在键盘中输入Q,此时程序跳出无限循环结构,再执行finally语句。程序中断时文件tryFinally.py的运行结果如图9-11所示。图9-11程序中断时文件tryFinally.py的运行结果由图9-11可知,当提示输入第3行字符串时,在键盘中按下Ctrl+F2键,此时引发KeyboardInterrupt异常,程序立即执行except语句,之后再执行finally语句。with...as语句与异常处理语句的综合应用核心概念with...as语句可作为try...finally语句处理异常的替代,其语法格式如下。with表达式[as变量名]:with语句块with...as语句用于定义一个有终止或清理行为的情况,如释放线程资源、文件、数据库连接等,在这些场合下使用with语句将使代码更加简洁。已经学习了try-except语句、try-except-else语句和try-finally语句,在实际开发中,经常需要将3种语句结合起来使用,具体代码如下所示。try:#可能出现异常的语句except异常类名as异常对象名:#处理特定异常的语句except:#处理多个异常的语句else:#未捕获到异常执行的语句finally:#无论是否发生异常都会执行的语句关键要点程序先执行try语句块,若try语句块中的某一语句执行时发生异常,则程序跳转到except语句,从上到下判断抛出的异常是否与except后面的异常类相匹配,并执行第一个匹配该异常的except后面的语句块;若try语句块中发生了异常,但是没有找到匹配的异常类,则执行不带任何匹配类型的except语句块;若没有发生任何异常,则程序在执行完try语句块后直接进入else语句块;最后,无论程序是否发生异常,都会执行finally语句块。9.3触发异常触发异常有两种情况,一种是程序执行中因为错误自动触发异常,另一种是显式地使用raise或assert语句手动触发异常。Python捕获与处理这两种异常的方式是相同的。本节主要介绍手动触发异常。9.3.1raise语句核心概念Python使用raise语句可以手动抛出一个指定的异常,其语法结构如下所示。raise[Exception[,args[,traceback]]]其中,用[]括起来的为可选参数,其作用指的是定抛出的异常名称,以及异常信息的相关描述。Exception是异常的类型,args是异常参数的值。参数是可选的;如果没有提供,则异常参数为None。最后一个参数traceback也是可选的,如果存在,则是用于异常的追溯对象。raise语句有3种用法,下面将进行详细的讲解。1.通过类名触发异常核心概念raise语句通过类名触发异常的方法只需指明异常类便可创建异常类的实例对象并触发异常,其语法格式如下。raise异常类名例如,手动触发语法错误异常,则可以使用以下代码。raiseSyntaxError手动触发语法错误异常的运行结果如图9-12所示。由图9-12可知,抛出了SyntaxError异常,定位了出现异常的位置,即第1行代码。图9-12手动触发语法错误异常的运行结果2.通过异常类的实例对象触发异常核心概念raise语句通过异常类的实例对象触发异常的方法只需指明异常类的实例对象便可触发异常,其语法格式如下。raise异常类的实例对象例如,手动触发除零导致的异常,则可以使用以下语句。raiseZeroDivisionError()手动触发除零导致的异常的运行结果如图9-13所示。图9-13手动触发除零导致的异常的运行结果由图9-13可知,抛出了ZeroDivisionError异常,定位了出现异常的位置,即第2行代码。此外,该方法还可以指定异常信息,具体代码如下所示。raiseZeroDivisionError('除数为零!')手动触发指定异常信息的运行结果如图9-14所示。图9-14手动触发指定异常信息的运行结果由图9-14可知,抛出了ZeroDivisionError异常,触发指定的异常信息,返回的异常信息为"ZeroDivisionError:除数为零!"。3.重新触发异常核心概念raise语句还可以重新触发异常,具体代码如下所示。1.try:2.raiseZeroDivisionError3.except:4.print('捕捉到异常!')5.raise#重新触发刚才发生的异常raise语句重新触发异常的运行结果如图9-15所示。由图9-15可知,程序执行了except语句块中的代码,其中的raise语句会重新触发ZeroDivisionError异常,但此时异常对象并未被捕获或处理,因此程序终止运行。关键要点raise语句用于中断程序的正常流程,并引发一个异常。当异常被引发时,Python解释器将在调用堆栈中查找最近的异常处理程序,如果找到,则执行该处理程序;否则,程序将终止并打印异常的相关信息。图9-15raise语句重新触发异常的运行结果9.3.2assert语句核心概念assert语句又称断言,是有条件地触发异常,assert语句的主要功能是帮助程序员调试程序,以保证程序运行的正确性,因此它一般在开发调试阶段使用,其语法格式如下。assert表达式[,参数]该语法格式中,当表达式为真时,不触发异常;当表达式为假时,触发AssertionError异常。下面通过一个示例演示通过assert语句实现对程序的测试。通过计算除法的运行结果的不同手动触发AssertionError异常,并在AssertionError后将参数部分作为异常信息的一部分给出。在Chapter09文件夹中创建一个名为assertion.py的文件,使用try...except语句捕获并处理异常。具体代码如文件9-6所示。文件9-6assertion.py1.try:2.a=float(input('请输入被除数:'))3.b=float(input('请输入除数:'))4.asserta>=b,'被除数大于除数'5.result=a/b6.exceptBaseExceptionase:7.print(e.__class__.__name__,':',e)8.print('程序结束')在上述代码中,第4行代码使用assert语句对程序进行测试,给定了参数部分为字符串“被除数大于除数”。assert语句运行结果当除数大于被除数时文件assertion.py的运行结果如图9-16所示。图9-16当除数大于被除数时文件assert.py的运行结果由图9-16可知,当输入被除数为5,除数为8时,返回AssertionError异常。给定了assert语句的参数,在AssertionError后将参数部分作为异常信息的一部分给出。当除数大于被除数时文件assert.py的运行结果如图9-17所示。图9-17程序中断时文件assert.py的运行结果由图9-17可知,当输入被除数为8,除数为5时,不执行except语句。9.4自定义异常Python中内置的异常类毕竟有限,用户有时根据需求需设置其他异常,如学生成绩不能为负数、限定密码长度等。本节将针对Python自定义异常类的使用方法进行讲解。核心概念在Python中,可以通过继承Exception类来自定义自己的异常类。自定义异常类可以用于在特定情况下抛出和捕获异常。定义一个异常类的代码具体如下。classMyCustomException(Exception):pass上述代码定义了一个名为MyCustomException的自定义异常类,读者可以在需要的地方抛出这个异常,并且可以使用try...except语句来捕获它。异常类继承自Exception类,可以直接继承或间接继承。下面通过一个示例演示通过自定义异常类实现抛出与捕获异常。自定义异常类示例在Chapter09文件夹中创建一个名为exception.py的文件,具体代码如文件9-7所示。文件9-7exception.py1.classMyCustomException(Exception):2.pass3.defdivide_numbers(a,b):4.ifb==0:5.raiseMyCustomException("Divisionbyzeroisnotallowed")6.returna/b7.try:8.result=divide_numbers(10,0)9.print(result)10.exceptMyCustomExceptionase:11.print("Anerroroccurred:",str(e))在上述代码中,divide_numbers()函数用于计算两个数字的除法。如果除数b为0,则会抛出MyCustomException异常,并显示错误消息。在try...except语句中,读者捕获这个异常并打印错误消息。文件exception.py的运行结果如图9-18所示。图9-18文件exception.py的运行结果由图9-18可知,当除数为10,被除数为0时,抛出MyCustomException异常并显示错误消息,即通过自定义异常类实现了对异常的抛出和捕获。自定义异常类的属性扩展核心概念自定义异常类可以包含自定义的属性和方法,以提供更多的信息和行为。例如,读者可以给MyCustomException类添加一个记录错误发生时间的属性,具体代码如文件9-8所示。文件9-8dateError.py1.importdatetime2.classMyCustomException(Exception):3.def__init__(self,message):4.self.message=message5.self.timestamp=datetime.datetime.now()6.try:7.raiseMyCustomException("Anerroroccurred")8.exceptMyCustomExceptionase:9.print("Errormessage:",e.message)10.print("Timestamp:",e.timestamp)在上述代码中,MyCustomException()类的构造函数添加了一个timestamp属性,它记录了错误发生的时间。在捕获异常时,读者可以访问这个属性来获取更多的信息。关键要点自定义异常类命名一般以Error或Exception为后缀。通过自定义异常类,读者可以更好地组织和处理程序中的异常,以便更好地理解和调试代码。文件dateError.py的运行结果如图9-19所示。图9-19文件dateError.py的运行结果拓展阅读:回溯最后的异常核心概念回溯是一个报告,其中包含在代码中某个特定点上执行的函数调用,回溯的名称包括堆栈跟踪、堆栈回溯、向后追溯等。当触发异常时,Python可以回溯异常并提示许多信息,这可能会给程序员定位异常位置带来不便,因此,Python中可以使用sys模块中exc_info()函数来回溯最后一次异常信息,该函数返回一个元组(type,value/message,traceback),每个元素的具体含义如下所示。type:异常的类型。value/message:异常的信息或者参数。traceback:包含调用栈信息的对象。使用sys.exc_info()函数虽然可以获取最后触发异常的信息,但是难以直接确定触发异常的代码位置。9.5实验:正确设置密码【实验目的】学会自定义异常类,在类里封装函数。学会在函数里使用无限循环结构并设置跳出循环的结构。学会使用try...except...else结构来捕获校验密码时可能出现的异常。学会使用raise语句触发异常。【实验要求与内容】用户自定义异常类,创建异常类型PasswordError()。定义set_password()函数,使用无限循环结构,定义input()函数,提示用户输入密码。使用通过try...except...else结构来捕获异常,如果用户输入长度<6,返回抛出异常;如果密码有空格,返回抛出异常。输入用户信息,调用set_password()函数来设置密码。实验步骤1:定义PasswordError异常类型在Chapter09文件夹中创建一个名为password.py文件,创建异常类型PasswordError(),设置参数为Exception,具体代码如文件9-9所示。文件9-9password.py1.classPasswordError(Exception):2.pass在上述代码中,读者定义了一个PasswordError()异常类型,用来表示密码设置失败的错误情况,自定义异常类继承自Exception类。实验步骤2:定义密码函数在文件password.py中,定义set_password()函数,用于设置密码,具体代码如下。1.defset_password(username):2.whileTrue:3.password=input("请设置密码:")4.try:5.iflen(password)<6:6.raisePasswordError("密码长度不能小于6位")7.if''inpassword:8.raisePasswordError("密码不能包含空格")9.exceptPasswordErrorase:10.print("密码设置失败:",e)11.else:12.print("密码设置成功!")13.break14.returnpassword在set_password()函数中,通过try...except...else结构来捕获校验密码时可能出现的异常,如果有异常就会提示密码设置失败,并让用户重新输入密码;如果没有异常就会打印密码设置成功,并跳出循环。实验步骤3:输入用户信息在主程序中,定义全局变量,并调用set_password()函数,具体代码如下。1.username=input("请输入用户名:")2.password=set_password(username)3.print(f"欢迎{username}加入我们,您的密码是{password}。")在上述代码中,读者首先提示用户输入用户名,然后调用set_password()函数来设置密码,并将得到的密码赋值给变量password。最后,读者通过print()函数来输出欢迎语和密码信息。实验结果与小结【实验结果】文件password.py的运行结果如图9-20所示。图9-20设置密码的运行结果由图9-20可知,用户输入的用户名为"小千",输入密码为1234时,提示"密码设置失败:密码长度不能小于6位";输入的密码为123456时,提示"密码设置成功!",最后一行是用户和密码信息"欢迎小千加入我们,您的密码是123456。"。【实验小结】通过这个案例代码,可以看到异常处理机制可以提高程序代码的健壮性,让程序更加稳定和可靠。读者还可以根据具体的业务需求来为不同的异常类型定制相应的处理逻辑,以提升程序的用户友好性。9.6数据科学入门:解决八皇后问题9.6.1问题描述八皇后问题是由国际象棋棋手于1848年提出的问题,是回溯算法的典型案例。下面主要讲解回溯法和递归法解决八皇后问题的方法。核心概念在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语言可以编程解决此问题。递归算法和回溯算法的思想如图9-21所示。如图9-21所示,圆圈代表皇后所放的位置,这里如果将棋盘转化为二维矩阵进行遍历比较麻烦,考虑到棋盘的每一行不能同时存在一个以上的皇后,所以将棋盘转化为一个具有八个元素的列表,而皇后的位置(i,j)对应的是列表中(元素的索引值,元素的值),因此放置皇后的操作变成了在列表中的每个位置填值操作,很明显的一个条件是列表中不能有相同的值。图中的[0,4,7,5,2,6,1,3]表示皇后的位置。图9-21递归法和回溯法的思想9.6.2回溯法核心概念回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为"回溯点"。回溯法解决的问题都可以抽象为树形结构,如图9-22所示。回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。回溯算法模板框架如下。voidbacktracking(参数){if(终止条件){存放结果;return;}for(选择:本层集合中元素(树中节点孩子的数量就是集合的大小)){处理节点;backtracking(路径,选择列表);//递归回溯,撤销处理结果}}回溯算法中函数返回值一般为void。图9-22回溯法解决问题的结构回溯法代码实现在Chapter09文件夹中创建一个名为backtracking.py的文件,具体代码如文件9-10所示。文件9-10backtracking.py1.importitertools2.importnumpyasnp3.N=84.cnt_comb=05.cnt_ans=06.forseqinitertools.permutations([iforiinrange(N)]):7.cnt_comb+=18.mat=np.zeros((N,N),dtype=int)#生成N*N的零矩阵9.foriinrange(N):#生成摆法:一行一行地放皇后10.mat[i][seq[i]]=111.foriinrange(N):#根据斜线之和筛选摆法12.ifsum([mat[i+j][j]forjinrange(N-i)])>1:#左下斜线之和13.break14.ifsum([mat[j][i+j]forjinrange(N-i)])>1:#右上斜线之和15.break16.ifsum([mat[j][N-1-i-j]forjinrange(N-i)])>1:#左上斜线之和17.break18.ifsum([mat[N-1-j][i+j]forjinrange(N-i)])>1:#右下斜线之和19.break20.else:21.cnt_ans+=122.print('第{}个解,此序列为:{},对应的棋盘如下所示:'.format(cnt_ans,[i+1foriinseq]))23.print(mat)24.print('{}皇后问题共有{}!即{}种组合,其中有{}个解。'.format(N,N,cnt_comb,cnt_ans))在上述代码中,第6~23行代码遍历N皇后的所有组合N!。回溯法运行结果文件backtracking.py部分运行结果如图9-23所示。图9-23文件backtracking.py部分运行结果拖动图9-23右侧的滑动条至页面底部,如图9-24所示。图9-24拖动图9-23右侧的滑动条至页面底部由图9-24可知,通过回溯法解决八皇后问题共有8!即40320种组合,其中有92个解。9.6.3递归法核心概念Python递归法是一种在函数内部调用自身的算法设计方法。通过递归,可以将一个大问题拆解为更小的子问题,并重复解决子问题,最终得到原始问题的解决方案。以下是Python中使用递归的一般步骤。定义递归函数:首先需要定义一个递归函数,该函数在内部会调用自身,此函数用于解决子问题,并返回结果。定义终止条件:为了避免无限循环,递归函数必须定义一个终止条件。当达到终止条件时,递归函数不再调用自身,而是返回一个最终结果。分解问题:在递归函数内部,将原始问题分解为更小的子问题。每个子问题都可以通过调用递归函数来解决。递归调用:在递归函数内部,通过调用自身来解决子问题。这样可以将大问题逐步分解为小问题,直到达到终止条件。合并结果:在递归函数返回结果的时候,需要将所有子问题的结果进行合并,得到原始问题的解决方案。关键要点需要注意的

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论