分享

五大常用算法之

 liang1234_ 2020-12-05

回溯法最优解排列组合解空间搜索中存在典型应用。

我们知道动态规划贪婪算法都要求无后效性,即子问题的解是当前的最优解,不能回退。当这种要求得不到满足时,一种的通常做法是采用回溯的方法进行求解。

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

基本思想

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。

若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束

一般步骤

  1. 定义一个解空间(子集树排列树二选一) 理解 重要

  2. 利用适于搜索的方法组织解空间。

  3. 利用深度优先法搜索解空间。

  4. 利用剪枝函数避免移动到不可能产生解的子空间。

检测

检测是判断是否剪枝的依据

  1. 约束函数-是否满足显约束(存在)

  2. 限界函数-是否满足隐约束(最优)

子集树模板

在这里插入图片描述
遍历子集树(完全二叉树),时间复杂度 O(2^n),可以分为两类题型:

  1. 如果解的长度是不固定的,那么解和元素顺序无关,即可以从中选择0个多个。例如:子集,迷宫,…

  2. 如果解的长度是固定的,那么解和元素顺序有关,即每个元素有一个对应的状态。例如:子集,8皇后,…

解空间的个数指数级别的,为2^n,可以用子集树来表示所有的解

适用于幂集子集0-1背包装载8皇后迷宫、…

子集树模板递归版

'''求集合{1, 2, 3, 4}的所有子集'''
class SubSetTree:
    def __init__(self, a):
        self.a = a      # 数据列表
        self.n = len(a) # 数据长度
        self.x = []     # 一个解
        self.X = []     # 一组解


    def conflict(self, k):    # 冲突检测:无
        return False


    # # 例子,冲突检测:奇偶性相同,且和小于8的子集
    # def conflict(self, k):
    #     # 根据部分解,构造部分集
    #     if len(self.x)==0:
    #         return False
    #     if 0 < sum(map(lambda y:y%2, self.x)) < len(self.x) or sum(self.x) >= 8: # 只比较 x[k] 与 x[k-1] 奇偶是否相间
    #         return True
    
    #     return False # 无冲突


    # 子集树递归模板
    def backtrack(self, k): # 到达第k个元素
        if k >= self.n:  # 超出最尾的元素
            self.X.append(self.x[:]) # 保存(一个解)
        else:
            for i in [1, 0]: # 遍历元素 a[k] 的两种选择状态:1-选择,0-不选
                if i==1:
                    self.x.append(self.a[k])
                if not self.conflict(k): # 剪枝
                    self.backtrack(k+1)
                if i==1:
                    self.x.pop()              # 回溯




    def SovleSubSet(self):
        self.backtrack(0)
        return self.X



if __name__ == '__main__':
    test = SubSetTree([1, 2, 3, 4])

    res = test.SovleSubSet()

    print(res)   # [[1, 2, 3, 4], [1, 2, 3], [1, 2, 4], [1, 2], [1, 3, 4], [1, 3], [1, 4], [1], [2, 3, 4], [2, 3], [2, 4], [2], [3, 4], [3], [4], []]

子集树模板迭代版

排列树模板

遍历排列树,时间复杂度O(n!)

解空间是由 n 个元素的排列形成,也就是说 n 个元素的每一个排列都是解空间中的一个元素,那么,最后解空间的组织形式是排列树

适用于:n个元素全排列旅行商、…

排列树模板递归版

在这里插入图片描述

'''求[1,2,3,4]的全排列'''


class PermTree:
    def __init__(self, data):
        self.n = len(data)
        self.x = data # 一个解
        self.X = []   # # 一组解      

    # # 冲突检测:无
    # def conflict(self, k):
    #     return False # 无冲突



    # 例子,冲突检测:元素奇偶相间的排列
    def conflict(self, k):
        if k==0:                   # 第一个元素,肯定无冲突
            return False
            
        if self.x[k-1] % 2 == self.x[k] % 2: # 只比较 x[k] 与 x[k-1] 奇偶是否相同
            return True
            
        return False # 无冲突
    

    # 排列树递归模板
    def backtrack(self, k): # 到达第k个位置 
        if k >= self.n:  # 超出最尾的位置
            self.X.append(self.x[:]) # 注意x[:]
        else:
            for i in range(k, self.n): # 遍历后面第 k~n-1 的位置
                self.x[k], self.x[i] = self.x[i], self.x[k]
                if not self.conflict(k):    # 剪枝
                    self.backtrack(k+1)
                self.x[i], self.x[k] = self.x[k], self.x[i] # 回溯


    def SovlePerm(self):
        self.backtrack(0)
        return self.X

            
# 测试
if __name__ == '__main__':
    test = PermTree([1,2,3,4])
    res = test.SovlePerm()
    print(res)   # [[1, 2, 3, 4], [1, 4, 3, 2], [2, 1, 4, 3], [2, 3, 4, 1], [3, 2, 1, 4], [3, 4, 1, 2], [4, 3, 2, 1], [4, 1, 2, 3]]

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多