Dotcpp  >  编程教程  >  Python算法  >  Python试探算法

Python试探算法

点击打开在线编译器,边学边练

        试探算法也称为回溯算法,它实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

1. 试探算法基础

        用回溯算法解决问题的一般步骤:

        1、 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。

        2 、确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。

        3 、以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。

        确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。

        基本思想总结起来就是:从一条路往前走,能进则进,不能进则退回来,换一条路再试,直到走出为止。

2. 八皇后问题

        问题描述:

        在8*8的国际象棋上,摆放八个皇后,使其不能相互攻击,即任意两个皇后不能在同一行,同一列,同意斜线上,问有多少种摆法?

        求解代码:

 n = int(input())
x = []#一个解
X = []#一组解
#检测冲突:判断x[k]是否与之前的x[0]~x[k-1]冲突
def conflict(k):
    global x
    for i in range(k):#遍历前面的x[0]~x[k-1]
        if x[i] == x[k] or abs(x[i]-x[k]) == abs(i-k):
            return True
    return False
#用子集树模板
def queens(k):
    global n
    global X,x
    if k >= n:
        X.append(x[:])#超出最低行时保存一个解
    else:
        for i in range(n):#遍历n~n-1列
            x.append(i)#皇后置于第i列,入栈
            if not conflict(k):#剪枝
                queens(k+1)
            x.pop()#出栈
#可视化解(X代表皇后)
def show(x):
    global n
    for i in range(n):
        print('. '*(x[i])+'Q '+'. '*(n-x[i]-1))
#测试
queens(0)
print(X[-1],'\n')
print(len(X))#解的个数
show(X[-1])

        这个问题的求解首先经过一次冲突检测,判断是否行内冲突,然后用子集数模板,在行的序数为最大值时保存当前解,不满足时遍历n~n-1列,最后通过Q代表皇后来实现可视化,最后输出一组数据来查看。

3. 迷宫问题

        问题描述:

        已知一个迷宫的出口,需要找到一个出口,如果存在这一的出路,输出它的路径。

        走迷宫的过程中呈九宫格方式,可以从上、下、左、右、左上、左下、右上和右下八个方向移动。

        代码如下:

 import pprint, copy=
# 迷宫(1是墙,0是通路)
maze = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [0, 0, 1, 0, 1, 1, 1, 1, 0, 1],
        [1, 1, 0, 1, 0, 1, 1, 0, 1, 1],
        [1, 0, 1, 1, 1, 0, 0, 1, 1, 1],
        [1, 1, 1, 0, 0, 1, 1, 0, 1, 1],
        [1, 1, 0, 1, 1, 1, 1, 1, 0, 1],
        [1, 0, 1, 0, 0, 1, 1, 1, 1, 0],
        [1, 1, 1, 1, 1, 0, 1, 1, 1, 1]]
m, n = 8, 10  # 8行,10列
entry = (1, 0)  # 迷宫入口
path = [entry]  # 一个解(路径)
paths = []  # 一组解
# 移动的方向(顺时针8个:N, EN, E, ES, S, WS, W, WN)
directions = [(-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)]
# 冲突检测
def conflict(nx, ny):
    global m, n, maze
    # 是否在迷宫中,以及是否可通行
    if 0 <= nx < m and 0 <= ny < n and maze[nx][ny] == 0:   #不越界,可以通行
        return False
    return True
# 套用子集树模板
def walk(x, y):  # 到达(x,y)格子
    global entry, m, n, maze, path, paths, directions
    if (x, y) != entry and (x % (m - 1) == 0 or y % (n - 1) == 0):  # 出口
        # print(path)
        paths.append(path[:])  # 直接保存,未做最优化
    else:
        for d in directions:  # 遍历8个方向(亦即8个状态)
            nx, ny = x + d[0], y + d[1]
            path.append((nx, ny))  # 保存,新坐标入栈
            if not conflict(nx, ny):  # 剪枝
                maze[nx][ny] = 2  # 标记,已访问
                walk(nx, ny)
                maze[nx][ny] = 0  # 回溯,恢复
            path.pop()  # 回溯,出栈
# 解的可视化(根据一个解x,复原迷宫路径,'2'表示通路)
def show(path):
    global maze
    maze2 = copy.deepcopy(maze)
    for p in path:
        maze2[p[0]][p[1]] = 2  # 通路
    pprint.pprint(maze)  # 原迷宫
    print()
    pprint.pprint(maze2)  # 带通路的迷宫
# 测试
walk(1, 0)
print(paths[-1], '\n')  # 看看最后一条路径
show(paths[-1])

        这个题的解题方式和上一题类似,首先进行一个冲突检测,判断当前路径是否处在迷宫当中,如果不越界就可以通行,然后套用子集数模板,定义函数来进行试探迷宫,如果当前坐标是出口,就保存下来,如果不是出口,遍历八个方向(八个状态),如果发生没有发生冲突检测就去除掉次坐标,然后出站,通过一次次试探最终找到出口,最后输出一个可视化的解来测试程序的正确性。

4. 总结

        上面是试探算法中较为经典的两个例题,当然还有很多问题可以通过试探算法进行求解,求解的过程中可能涉及到深度优先搜索和广度优先搜索,算法中的内容还有很多很多,数据结构能够帮助我们了解更深层次的内容,有兴趣的可以去学习一下数据结构相关知识,本章内容就到讲这里。



本文固定URL:https://www.dotcpp.com/course/315

上一课:

Python贪心算法

Python教程
第一章 Python入门
第二章 Python基础语法
第三章 Python入门语法
第四章 Python核心语法
第五章 Python函数
第六章 Python面向对象编程
第七章 Python模块
第八章 Python异常处理和程序调试
第九章 Python文件及目录操作
第十章 PythonGUI编程
第十一章 Python进程和线程
第十二章 Python数据库管理
第十三章 Python算法
第十四章 Python爬虫
第十五章 Python实战项目
第十六章 后记
Dotcpp在线编译      (登录可减少运行等待时间)