递归算法的总结与应用。

学完了算法与数据结构。该总结下了

总结案例一(基本思想)汉诺塔问题。

def moveTower(height,fromPole,withPole,toPole):
    if height>=1:
        moveTower(height-1,fromPole,toPole,withPole)
        moveDisk(height,fromPole,toPole)
        moveTower(height-1,withPole,fromPole,toPole)
def moveDisk(disk,fromPole,toPole):
    print(f"Moving disk{disk}from {fromPole} to {toPole}")
moveTower(3,"#1","#2","#3")


'''
基本思想就是:先把起始柱上的n-1个盘子移动到中间柱子,再把起始柱子上的最后一个移动到目标柱。之后再把中间上的所有盘子移动到目标柱。(这是最小规模)
递归体:最小规模
结束条件:目标柱盘子数小于1


使用递归算法时需要明确递归体和递归出口。递归体就是事件中需要重复操作的最小规模。递归出口就是最小规模到最理想情况下出现的结果
'''



总结案例二(了解递归机制)全排列问题(同深度优先遍历,回溯算法)

a=list(map(int,input().split()))
def use(y):
    vist = [True] * len(y)
    temp = [0] * len(y)
    #递归出口
    def dfs(position):
        if position == len(y):
            print(temp)
            return
        for index in range(len(y)):#控制方向。从第一个方向开始穷尽。
            if vist[index] == True:
                temp[position] = y[index]
                vist[index] = False
                dfs(position + 1)  #继续深度探索此方向。
                vist[index] = True  # 回溯,非常重要
    dfs(0)
    return
use(a)

'''
关注注释部分。这里有个回溯。理解这里要了解到,递归语句其实就是一个阻塞语句,

等同于线程里面的sleep语句。只有运行完整个递归语句,才会执行下一条语句。
理解完这个规则,相信你也会很好的理解深度优先算法。

深度优先算法就是先深度搜索一条线索,直到某条语句走不通后,会回溯到某一点,

而当前的某一点的阻塞已经结束,则进行了下一条语句或者下一个递归的开始。

这就是回溯的作用。


回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。是一种组织
得井井有条的,能避免不必要搜索的穷举式搜索法。在骑士周游问题中,普通的深度优先遍历

找到一个解的耗时大概10几分钟。但是采用一种启发式的规则,即选优搜索,

可以使性能达到ms级别。
这种启发式规则常用于人工智能中。


深度优先搜索的应用举例:拓扑排序
'''

有时候,我们必须根据各种事物间的依赖关系来确定一种可接受的执行顺序。比如,在大学里必须满足一些先决条件才能选的课程,或者一个复杂的项目,其中某个特定的阶段必须在其他阶段开始之前完成。要为这一类问题建模,可以采用优先级图,其采用的是有向图的思路。在优先级图中,顶点代表任务,而边代表任务之间的依赖关系。以必须先完成的任务为起点,以依赖于此任务的其他任务为终点,画一条边即可。

如下图所示,它表示7门课程及其先决条件组成的一份课程表:S100没有先决条件,S200需要S100,S300需要S200和M100,M100没有先决条件,M200需要M100,M300需要S300和M200,并且S150没有先决条件同时也不是先决条件。

在这里插入图片描述

通过对这些课程执行拓扑排序,深度优先搜索有助于确定出一中可接受的顺序。

拓扑排序将顶点排列为有向无环图,因此所有的边都是从左到右的方向。

正规来说,有向无环图G=(V,E)的拓扑排序是其顶点的一个线性排序,

以便如果G中存在一条边(u,v),那么线性顺序中u出现在v的前面

,在许多情况下,满足此条件的顺序有多个。

可以通过一张图来理解全排列的过程,等效于迷宫问题。其中for循环控制的就是方向A、B、C。

在这里插入图片描述

总结案例三(递归的应用之穷举算法)穷举的找零算法。

def recMC(coinValueList,change):
    minCoins=change
    if change in coinValueList:
        return 1
    else:
        for i in [c for c in coinValueList if c<=change]:
            numCoin=1+recMC(coinValueList,change-i)
            if numCoin<minCoins:
                minCoins=numCoin
    return minCoins
print(recMC([1,5,10,25],63))
'''
仔细观察可以发现,这个算法是一个有过多重复计算的穷举算法。它将货币从[1,5,10,25]中,如果存在则直接取,返回1,否则则重新在[1,5,10,25]中从头一个一个算最小值。
这是一个非常耗时的操作,将近3s多才能算出。
但是这样做的好处是可以把所有结果都穷尽,有些问题还必须用穷举算法。
穷举算法的大致思想就是:将所有可能的路径都走尽。


'''




总结案例四——记忆化/函数值缓存(递归的应用之穷举算法的优化)穷举优化的找零算法。

#减少重复计算(记忆化/函数值缓存)用一个表将计算过的中间结果保存起来,计算之前查表看是否被计算
#如果被记录,则直接使用表中数据,终止重新计算(迭代for计算过程)
def recMC(coinValueList,change,knowResults):
    minCoins=change
    if change in coinValueList:
        knowResults[change]=1       #记录最优解
        return 1
    elif knowResults[change]>0:
        return knowResults[change]  #查表成功,直接使用最优解
    else:
        for i in [c for c in coinValueList if c<=change]:
            numCoin=1+recMC(coinValueList,change-i,knowResults)
            if numCoin<minCoins:
                minCoins=numCoin
                knowResults[change]=minCoins
    return minCoins
print(recMC([1,5,10,25],63,[0]*64))

'''
可见,通过用一个表将中间的结果保存下来,减少了重复一些重复的计算调用for循环,这大大提高了性能。将算法时间复杂度降到ms。

'''

总结案例五——递归算法之贪心算法

def change(valueList, change):
    numdic = {}
    # 注意要排序,保证面值较大的在前面
    valueList.sort(reverse=True)
    num = 0
    for c in [i for i in valueList if i <= change]:
        num += change // c
        newChange = change % c
        numdic[str(c)] = change // c
        if newChange == 0:
            break
        else:
            change = newChange

    return num, numdic

num, dic = change([1,2,5,10], 16)
print(num)
print(dic)
'''
注意,这解只能是次优解,不一定是最优解。
因为当[1,2,5,21,10,25]为货币,找零63时,贪心算法为25,25,10,2,1为5,而最优解为21,21,21为3.

'''







总结案例六—动态规划找零问题


'''
动态规划中最主要的思想是:
从最简单的情况开始到达所需找零的循环
其每一步都依靠以前的最优解来得到本步骤的最优解,直到得到答案.前面的状态影响后面的状态。

选择的贪心策略必须具备无后效性(即某个状态以前的过程不会影响以后的状
态,只与当前状态有关)。所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪
心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法
的主要区别.



找零的动态规划:从最简单的“1分钱找零”的最优解开始,
逐步递加上去,直到我们需要的找零数,在找零递加的过程中,
设法保持每一分钱的递加都是最优解,一直加到求解找零钱数,
自然得到最优解
递加的过程能保持最优解的关键是,其依赖于最少钱数最优解的简单计算
而更少钱数的最优解已经得到了。


动态规划的核心:定义状态。找到状态方程。采用递推的方法。
找零规划的状态:从0开始到n(找零钱数)结束的有向图问题。
状态方程:从0到某一状态的最优解可以通过前面某个状态的最优解得到,而前面状态的最优解已经得到
所以可以得到某个状态的最优解。这符合动态规划的特点。形象与图的最短路径问题,可以采用狄克斯特算法
来解决这个问题。
'''
def dpMakeChange(coinValueList,change,minCoins):
    #从1分开始到chhange逐个计算最少硬币数
    canOverF=min(coinValueList)#用于判断从0开始能否到达某个状态值。前期最优解的判断。  因为是个定值,所以与递推公式无关,可以放在递推公式外面
    for cents in range(1,change+1):  #从0起点出发到另一个存在点的最小距离于最小货币有关。
        canOverL = False  # 用于判断中间状态值是否可以到达。因为其于每个状态点有关,故当状态点转变时,它会重新回到原点。而状态点判断的整个过程是其改变的另个因素
                          #故放在状态点与过程之间。
        if cents >= canOverF:
            # 1.初始化一个最大值
            coinCount = cents
        else:
            minCoins[cents] = -1000
        #2.减去每个货币,向后查最少货币数,同时记录总的最小数。逐步逼近最小的货币数。动态选择到最小货币数,而不是采用计算完后比较的方法得出最小货币数。
        for j in [c for c in coinValueList if c<=cents]:#把每个减去的货币达到的状态的每个最小货币数计算出来,如果比原先的最小数小,则记录下来,用这个再和后面的状态最优解值比较。
            if minCoins[cents-j]+1<=coinCount and minCoins[cents-j]+1>0:
                canOverL=True
                coinCount=minCoins[cents-j]+1
        # 3.得到当前最少硬币数,记录到表中
        if canOverL==True:
            minCoins[cents] = coinCount
        else:                    #当不能通过时标记为-1000。
            minCoins[cents] = -1000
    if minCoins[cents]<0:
        return -1
        #返回最后一个结果
    return minCoins[cents]
*a,b=list(map(int,input().split()))
print(dpMakeChange(a,b,[0]*(b+1)))

总结案例七—动态规划视频拼接


'''
此问题的状态为从0到T的时间片段。找出最少的片段来拼接(0,T)
第一个最优解可以通过贪心的策略从(0,maxT1)找到。
之后就找(maxT1,maxT2)直到找到T。
此问题可以通过动态规划完成,因为最优解已经在前面得到,后面的状态与前面的最优解有关。

通过共性分析。建立共性表。之后确定最大共性序列,选出最小片段。
比如(0,maxT1)都满足共性a1,(0,maxT1-)都满足共性a2,但是maxT1->maxT1。则选择maxT1-。之后再
通过(maxT1-,maxT2)找出maxT2。直到得到最优解。如果不存在最优解,则会出现某个状态值不存在任何共性的情况(用某个标量来判断是否出现此情况。
若出现这种情况,则直接返回-1。
'''
import numpy as np
allHave=[2,3,5,7,11,13,17,19,23,29]   #在素数范围内的共性表,假定片段总数不超过10.
*a,b=map(int,input().split())
sum=len(a)//2
list2=np.array(a).reshape(sum,2)
class Have:  #构建状态点。
    def __init__(self,i):
        self.math=i
    myHave=1
lookList=[Have(i) for i in range(b+1)]#创建状态点列表,观察状态,进行判断
f={str(list2[i]):allHave[i] for i in range(len(list2))}#创建共性映射表。
#进行共性整合
for i in range(len(list2)):
    for j in [x for x in range(list2[i][0],list2[i][1]+1) if x<=b]:
        lookList[j].myHave = lookList[j].myHave * f[str(list2[i])]
#进行筛选判断。
def minList(lookList1,count):
    count=count
    if len(lookList1)==1:#当截取到最后一个时 ,结束。
        return count
    maxT = 0  # 表示最大选择点
    for i in f.values():#在所有共性中找出对当前状态最大的
        for j in lookList1:
            if j.myHave % i == 0:
                if j.math>=maxT:
                    maxT=j.math# 选出最大选择点。不能放在if条件中,这是因为会导致前面的判断直接被省略。
            else:
                 break  # break在达到maxT时直接跳到下一个共性。而continue达到maxT后向后考虑是否满足当前共性。
    if maxT==lookList1[0].math:#当不能到达时会出现一直调用统一个状态点的情况。因为此状态点被孤立了,找不到下一个点于其共态。
                             #判断出现这种情况,则可以判断不能到达终点。而这种状态的表现就是最大截取点一直为当前片段起点。
        return -1
    return minList(lookList[maxT:], count + 1)#观察参数,是否存在列表的切片存在问题。例如一直对一个列表进行不同长度
                                                   #的切片,造成逻辑上的错误。
print(minList(lookList,0))


#
#存在问题!!!
#使用测试:0 2 4 6 8 10 1 9 1 5 5 9 10
#真是太爽了,可算自己原创了一个算法。哈哈哈。开心。


总结案例八—广度优先搜索(分支限界法)


'''
“一马当先”

下过象棋的人都知道,马只能走'日'字形(包括旋转90°的日),现在想象一下,给你一个n行m列网格棋盘, 棋盘的左下角有一匹马,请你计算至少需要几步可以将它移动到棋盘的右上角,若无法走到,则输出-1. 如n=1,m=2,则至少需要1步;若n=1,m=3,则输出-1。



思路:
元素定义

temp:用来存储不同层的坐标,其中 temp[0] 表示上一层,temp[1] 表示当前层,temp[2] 表示下一层。
f:逻辑型值,用来记录是否有新的坐标被丢入 temp[2] 中。
t:用于记录层数。
流程解析

创建一个序列 temp=[[],[[0,0]],[]],分别表示上一层、当前层、下一层;
初始化 t=1,f=True;
若 f 为 True 进入以下流程,否则输出 -1;
令 f=False,依次遍历 temp[1] 里的每一个坐标,对每一个坐标采取全部 8 种移动方式,若移动到了 [m,n] 则输出层数 t;
若移动后仍在棋盘内且不存在 temp 里任何一层中,则将这个坐标加入到 temp[2] 中,且令 f=True;
遍历完成后,del temp[0] 以控制数据量,这个操作同时使 temp[1] 顶替temp[0],temp[2] 顶替 temp[1],然后使用 append 方法插入一个新的空序列作为 temp[2],t+=1;
回到第三步并重复
'''
n = 3
m = 5

def steps_count4():
    global m, n
    dx = [+2, +1, -1, -2, -2, -1, +1, +2]
    dy = [-1, -2, -2, -1, +1, +2, +2, +1]
    temp = [set(), set([(0, 0)]), set()]
    t = 1
    f = True
    while f:
        f = False
        for loc in temp[1]:
            for i in range(8):
                x = loc[0] + dx[i]
                y = loc[1] + dy[i]
                if (x,y) == (m, n):
                    return t
                elif x >= 0 and y >= 0 and x <= m and y <= n and not ((x,y) in temp[0] or (x,y) in temp[1] or (x,y) in temp[2]):
                    temp[2].add((x,y))
                    f = True
        t += 1
        del temp[0]
        temp.append(set())
    return -1
    
'''
广度优先搜索算法实现步
首先将根节点放入队列中。
从队列中取出第一个节点,并检验它是否为目标。
如果找到目标,则结束搜索并回传结果。
否则将它所有尚未检验过的直接子节点加入队列中。
若队列为空,表示整张图都检查过了——亦即图中没有欲搜索的目标。结束搜索并回传

“找不到目标”。

特点:,每一个活结点只有一次机会成为扩展
结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这
些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余
儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前
扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的
解或活结点表为空时为止。(概括来说,只能一层一层搜索,如果某层中有最优解,

则后面的其他解就不会出现了)

可以根据特点看出:广度优先搜索用于求最短路径问题

(在没有权值的情况下,等距离的情况下(权值的使用狄克斯特算法)

。或者用于求是否可以到达终点的问题。
应用:计算网络跳数:用来确定在互联网中从一个结点到另一个

结点(一个网络到其他网络的网关)的最佳路径。

一种建模方法是采用无向图,其中顶点表示网络结点,边代表结点之间的联接。

使用这种模型,可以采用广度优先搜索来帮助确定结点间的最小跳数。

'''



总说广度优先算法与深度优先算法:

**穷举**
在我们遇到的一些问题当中,有些问题我们不能够确切的找出数学模型,

即找不出一种直接求解的方法,解决这一类问题,

我们一般采用搜索的方法解决。搜索就是用问题的所有可能去试探,

按照一定的顺序、规则,不断去试探,直到找到问题的解,

试完了也没有找到解,那就是无解,试探时一定要试探完所有的情况(实际上就是穷举)

 [2]对于问题的第一个状态,叫初始状态,要求的状态叫目标状态。
搜索就是把规则应用于实始状态,在其产生的状态中,直到得到一个目标状态为止。
产生新的状态的过程叫扩展(由一个状态,应用规则,产生新状态的过程)
搜索的要点:(1)初始状态;
(2)重复产生新状态;
(3)检查新状态是否为目标,是结束,否转(2); [1] 
如果搜索是以接近起始状态的程序依次扩展状态的,叫宽度优先搜索。
如果扩展是首先扩展新产生的状态,则叫深度优先搜索。
深度优先搜索
深度优先搜索用一个数组存放产生的所有状态。
(1) 把初始状态放入数组中,设为当前状态;
(2) 扩展当前的状态,产生一个新的状态放入数组中,同时把新产生的状态设为当前状态;
(3) 判断当前状态是否和前面的重复,如果重复则回到上一个状态,产生它的另一状态;
(4) 判断当前状态是否为目标状态,如果是目标,则找到一个解答,结束算法。
(5) 如果数组为空,说明无解。
搜索是人工智能中的一种基本方法,是一项非常普遍使用的算法策略,

能够解决许许多多的常见问题,在某些情况下我们很难想到高效的解法时,

搜索往往是可选的唯一选择。

按照标准的话来讲:搜索算法是利用计算机的高性能来有目的的穷举一个问题的部分

或所有的可能情况,从而求出问题的解的一种方法。搜索虽然简单易学易于理解,

但要掌握好并写出速度快效率高优化好的程序却又相当困难,总而言之,

搜索算法灵活多变,一般的框架很容易写出,但合适的优化却要根据实际情况来确定。

在搜索算法中,深度优先搜索(也可以称为回溯法)是搜索算法里最简单也最常见的
**系统算法*

所有的算法实现上来看,都可以划分成两个部分──控制结构和产生系统。

正如前面所说的,搜索算法简而言之就是穷举所有可能情况并找到合适的答案

,所以最基本的问题就是罗列出所有可能的情况,这其实就是一种产生式系统。

 [2]我们将所要解答的问题划分成若干个阶段或者步骤,当一个阶段计算完毕,
 
下面往往有多种可选选择,所有的选择共同组成了问题的解空间,

对搜索算法而言,将所有的阶段或步骤画出来就类似是树的结构(如图)。
从根开始计算,到找到位于某个节点的解,

回溯法(深度优先搜索)作为最基本的搜索算法,

其采用了一种“一只向下走,走不通就掉头”的思想(体会“回溯”二字)

深度优先搜索
深度优先搜索通常用于模拟游戏(以及现实世界中类似游戏的情况).在典型的游戏中,您可以选择几种可能的操作之一.每种选择都会导致进一步的选择,每种选择都会导致进一步的选择,等等,形成一种不断扩展的树形图形.
例如在像国际象棋这样的游戏中,当你决定做出什么样的动作时,你可以在心理上想象一个动作,然后你的对手的可能反应,然后是你的反应,等等.您可以通过查看哪种移动可以获得最佳结果来决定做什么.

只有游戏树中的某些路径才能赢得胜利.有些会导致你的对手获胜,当你达到这样的结局时,你必须备份或回溯到前一个节点并尝试不同的路径.通过这种方式,您可以探索树,直到找到成功结束的路径.然后沿着这条路径前进.

广度优先搜索
广度优先搜索具有一个有趣的属性:它首先找到距起点一个边缘的所有顶点,然后找到两个边缘的所有顶点,依此类推.如果您试图找到从起始顶点到给定顶点的最短路径,这将非常有用.您启动BFS,当您找到指定的顶点时,您知道到目前为止您已跟踪的路径是该节点的最短路径.如果路径较短,BFS就已经找到了.

广度优先搜索可用于查找邻居节点的点对点像BitTorrent网络,GPS系统找到附近的地点,社交网站找人在规定的距离之类的东西.

** 之后还有,先总结到这里,会不断优化这篇博客 。**

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值