数据结构 13.递归、回溯、分治、动态规划

本文详细介绍了数据结构中的递归、回溯、分治和动态规划四种核心算法。递归用于解决自调用问题,回溯法是一种有组织的搜索技术,通过剪枝避免无效搜索;分治法将大问题分解为小问题解决,动态规划通过存储子问题解来避免重复计算,适用于求解最优化问题。文中还给出了各类算法的解题思路、经典实例和代码实现。
摘要由CSDN通过智能技术生成

一、 递归

https://blog.csdn.net/weixin_41725746/article/details/90241793.

二、回溯

1.回溯的概念

1.回溯算法就是一种有组织的系统最优化搜索技术,可以看作蛮力法穷举搜索的改进。回溯法常常可以避免搜索所有可能的解,所以它适用于求解组织数量较大的问题。

2.解空间树: 问题的解空间一般使用解空间树的方式来组织,树的根节点位于第1层,表示搜索的初始状态,依次向下排列。

3.解空间树的动态搜索: 在搜索至树中任一节点时,先判断该节点对应的部分是否是满足约束条件,或者是否超出目标函数的界,也就是判断该节点是否包含问题的最优解。如果肯定不包含,则跳过对该节点为根的子树的搜索,即所谓的剪枝;否则,进入该节点为根的子树,继续按照深度优先策略搜索。(这也是为什么回溯可以避免搜索所有的解)

4.在搜索过程中,通常采用两种策略避免无效搜索:
(1)用约束条件剪除得不到的可行解的子树
(2)用目标函数剪取得不到的最优解的子树
(这两种方式统称为:剪枝函数)

5.在用回溯法求解问题时,常常遇到两种典型的解空间树:
(1)子集树:但所有的问题是从n个元素的集合中找出满足某种性质的子集时,相应的解空间树成为子集树
(2)排列树:当所给出问题是确定n个元素满足某种性质的排列时,相应的解空间称为排列树。

6.回溯法的一般步骤:
(1)设置初始化的方案(给变量赋初始值,读入已知数据等)
(2)变换方式去试探,若全部试完侧转(7)
(3)判断此法是否成功(通过约束函数),不成功则转(2)
(4)试探成功则前进一步再试探
(5)正确方案还是未找到则转(2)
(6)以找到一种方案则记录并打印
(7)退回一步(回溯),若未退到头则转(2)
(8)已退到头则结束或打印无解

7.回溯法的优点在于其结构明确,可读性强,易于理解,而且通过对问题的分析可以大大提高运行效率。

2. 回溯相关练习

51.N皇后

https://leetcode-cn.com/problems/n-queens/
问题描述: n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
fig1
上图为8皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:

输入: 4
输出: [
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解释: 4 皇后问题存在两个不同的解法。

解题思路:
1、从1行开始,逐行扫描,避免了同行判断;
2、针对每一行的扫描,枚举列的全部可能;
3、剪枝的要点,判断同列、判断斜向左下和右上(x + y固定)、判断斜向右下和左上(x-y固定);

代码实现

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        res = []
        s = "." * n
        def backtrack(i, tmp,col,z_diagonal,f_diagonal):
            if i == n:
                res.append(tmp)
                return 
            for j in range(n):
                if j not in col and i + j not in  z_diagonal and i - j not in f_diagonal:
                    backtrack(i+1,tmp + [s[:j] + "Q" + s[j+1:]], col | {
   j}, z_diagonal |{
   i + j} , f_diagonal |{
   i - j}  ) 
            
        backtrack(0,[],set(),set(),set())    
        return res

利用回溯算法求解 0-1 背包问题

问题描述: 给定N个物品和一个背包。物品i的重量是Wi,其价值位Vi ,背包的容量为C。问应该如何选择装入背包的物品,使得放入背包的物品的总价值为最大?
代码实现:

bestV=0
curW=0
curV=0
bestx=None
 
def backtrack(i):
    global bestV,curW,curV,x,bestx
    if i>=n:
        if bestV<curV:
            bestV=curV
            bestx=x[:]
    else:
        if curW+w[i]<=c:
            x[i]=True
            curW+=w[i]
            curV+=v[i]
            backtrack(i+1)
            curW-=w[i]
            curV-=v[i]
        x[i]=False
        backtrack(i+1)

        
if __name__=='__main__':
    n=5
    c=10
    w=[2,2,6,5,4]
    v=[6,3,5,4,6]
    x=[False for i in range(n)]
    backtrack(0)
    print(bestV) # 15
    print(bestx) # [True, True, False, False, True

三、分治

1.分治的概念

在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……

2.分治算法的基本步骤

分治法在每一层递归上都有三个步骤:

step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

step3 合并:将各个子问题的解合并为原问题的解。

3.分治相关练习

利用分治算法求一组数据的逆序对个数

摘自:https://blog.csdn.net/contr4l_/article/details/80611675
题目描述: 对于一个序列a1,a2,a3…an, 若存在i,j,使得i<j且ai>aj,则称为一个逆序对

输入: 一个list对象
输出: list中逆序对数目

解题思路: 一个朴素的想法是这样的:依次遍历list中每一个元素,对每一个元素,查找其之后的每一个元素并与其比较,出现逆序对则计数+1,时间复杂度为O(n^2)——(n-1)+(n-2)+…+1。

但对于这个算法,实际上有隐含的条件被我们忽略了,例如当我们发现a2<a1时,那么对于和a2比较过的所有元素,与a2形成逆序对的所有元素实际上也与a1形成逆序对(除a2外),实际上时间复杂度可以精简到O(nlogn)。

我们可以换一个思路来考虑这个问题:对于一个a1<a2<a3…<an的有序序列,逆序对是为0的,那么我们实际上是要进行一个排序,当发现一次逆序,计数+1,而排序算法的最优解法为O(nlogn)

代码实现:

def Count(lst1, lst2,count): 
    i = 0
    j = 0
    res = []
    while i < len(lst1) and j < len(lst2):
        if lst1[i] <= lst2[j]:
            res.append(lst1[i])
            i += 1
        else:
            res.append(lst2[j])
            count += len(lst1)-i # 当右半部分的元素先于左半部分元素进入有序列表时,逆序对数量增加左半部分剩余的元素数
            j += 1
    res += lst1[i:]
    res += lst2[j:]
    return res,count
    
def InversionNum(lst):
    # 改写归并排序,在归并排序中,每当R部分元素先于L部分元素插入原列表时,逆序对数要加L剩余元素数
    if len(lst) == 1:
        return lst,0
    else:
        n = len(lst) // 2
        lst1,count1 = InversionNum(lst[0:n])
        lst2,count2 = InversionNum(lst[n:len(lst)])
        lst,count = Count(lst1,lst2,0)
        return lst,count1+count2+count
  
 
print(InversionNum([11,10,9,8,7,6,5,4,3,2,1]))  # 输出为:[1,2,3,4,5,6,7,8,9,10,11] 55

四、动态规划

摘自https://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741374.html

1. 动态规划的概念

动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

2.基本思想与策略

基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

与分治法最大的差别是: 适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。

3. 适用的情况

能采用动态规划求解的问题的一般要具有3个性质:

  • (1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
  • (2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
  • (3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)

4.动态规划相关练习

利用动态规划求解 0-1 背包问题

问题描述: 给定N个物品和一个背包。物品i的重量是Wi,其价值位Vi ,背包的容量为C。问应该如何选择装入背包的物品,使得放入背包的物品的总价值为最大?
解题思路:
①不超过背包容量,有:Σw[i]<=C (i表示第i件物品);
②对于“将前i件物品放入容量为j的背包中”这个子问题,现只考虑第i件物品放还是不放,如果放了的话,问题转换为“前i-1件物品放入剩下j-w[i]容量的背包中”,如果不放,问题转换为“前i-1件物品放入容量为j的背包中”。这两种情况的优者为当前问题的最优解;
③根据②的分析,得到如下递推公式:
formula
其中,0<=i<=N,0<=j<=W.
④例子
5个物品的重量和价值分别为(5,12),(4,3),(7,10),(2,3),(6,6),背包容量15。

初始化表:
table
根据公式填表:
table
随便选一个来分析,比如上表圈中位置f[3][9]表示前3件物品放入容量为9的背包中,最高价值为15

  • 15如何来的呢?
    考虑第三件物品放还是不放入背包,若放入背包f[3][9]=f[2][9-w[3]]+v[3]=f[2][2]+v[3]=0+10=10
    若不放入背包,f[3][9]=f[2][9]=15
    因为15>10,所有第三件物品不放入背包,前3件物品放入容量为9的背包得到的最高价值是15

代码实现:

def bag(n, c, w, v):
    """
    测试数据:
    n = 5  物品的数量,
    c = 15 书包能承受的重量,
    w = [5, 4, 7, 2, 6] 每个物品的重量,
    v = [12, 3, 10, 3, 6] 每个物品的价值
    """
    # 置零,表示初始状态
    value = [[0 for j in range(c + 1)] for i in range(n 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值