目录

 

概述

问题的解空间

解空间概述(solution space)

解空间与回溯法

约束条件

解空间树(状态空间树)

回溯法的设计思想

什么是回溯法

算法设计思想

回溯法的求解过程

回溯法的算法描述

回溯法的非递归描述框架

回溯法的递归描述框架

回溯法与其他算法设计方法的比较

回溯法的时空性能

时间性能分析

空间性能分析

图问题中的回溯法

图着色问题

问题描述

问题分析

算法设计

算法复杂度分析

组合问题中的回溯法

八皇后问题

问题描述

问题分析

算法设计

时空复杂性

0/1背包问题

问题描述

问题分析

算法设计

算法分析


概述

  • 问题的解空间

  • 解空间概述solution space

一个复杂问题的解在使用回溯法求解时,通常可表示为满足某个约束条件的解向量X={x1x2,…,xn},其中分量xi可以有多个取值,表示为xiSi, Sixi的取值候选集。

X中各个分量xi所有可能取值的组合构成问题的解向量空间,简称解空间  

对于任何一个问题,可能解的表示方式和它相应的解释隐含了解空间及其大小。

例如,对于有n个物品的0/1背包问题,其可能解的表示为一个向量{x1, x2, …, xn},其中xi=1(1≤in)表示物品i装入背包,xi=0表示物品i没有装入背包,当n=3时,其解空间是:

{ (0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0), (1, 1, 1) }

  • 解空间与回溯法

回溯法实际上一个类似穷举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”(即回退),尝试别的路径。 回溯法有“通用解题法”之称。它适合于解一些组合数较大的最优化问题。

解空间也就是进行穷举的搜索空间,所以,解空间中应该包括所有的可能解

确定正确的解空间很重要,如果没有确定正确的解空间就开始搜索,可能会增加搜索很多无用解,或者根本就搜索不到正确的解。

用回溯法求解的问题通常分两类:

求一个(或全部)可行解;

求最优解。

可行解:解空间中满足约束条件的解向量。

最优解一般来说,解任何问题都有一个目标,在约束条件下使目标函数取得最优的可行解称为该问题的最优解

  • 约束条件

用回溯法求解,它的解一般要求满足一组综合约束条件,这些约束条件分为以下两类:

显式约束:限定每个xi只从一个给定的集合Si上取值。 

设Si={ai1, ai2, …, aimi}集合Si的大小是mi对于X=(x1, x2, …, xn),由于分量数据结构&算法学习笔记——回溯法 (1≤in),则所有可能的解向量构成解空间大小为:m=m1m2mn    

显式约束条件常见的例子

当 xi=0xi=1        即  Si={0,1}

当 数据结构&算法学习笔记——回溯法数据结构&算法学习笔记——回溯法

注意:解空间”是指满足显式约束条件的向量组成的空间,并非一般意义上的解。

隐式约束:规定解空间中那些实际上满足目标约束的向量。

数据结构&算法学习笔记——回溯法

  • 解空间树(状态空间树)

1、定义解空间的树结构

问题的解空间一般用解空间树Solution Space Trees,也称状态空间树)的方式组织,树的根结点位于第1层,表示搜索的初始状态,第2层的结点表示对解向量的第一个分量做出选择后到达的状态,1层到第2层的边上标出对第一个分量选择的结果,依此类推,从树的根结点到叶子结点的路径(也可能是根节点到任何一个树中结点,但不含搜索失败的结点),就构成了解空间的一个可能解。

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

2、解空间树的特点

树中的结点:求解过程的一个状态

树中的边:标示 xi 的一个可能的值

解向量: 由根结点到任意(或叶)结点的路径定义

解空间:由根结点到所有(或叶)结点的路径定义

注意:问题的解空间树是虚拟的,并不需要在算法运行时构造一棵真正的树结构,然后再在该解空间树中搜索问题的解,而是只存储从根结点到当前结点的路径。

实际上,有些问题的解空间因过于复杂或状态过多难以画出来。

3、解空间树两种类型

(1子集树Subset Trees):当所给问题是从n个元素的集合中找出满足某种性质的子集时,相应的解空间树称为子集树。

在子集树中,|S1|=|S2|=…=|Sn|=c,即每个结点有相同数目的子树,通常情况下c=2,所以,子集树中共有2n个叶子结点,因此,遍历子集树需要Ω(2^n)时间。

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

 (2)排列树(Permutation Trees)

当所给问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。

在排列树中,通常情况下,|S1|=n|S2|=n-1|Sn|=1,所以,排列树中共有n!个叶子结点,因此,遍历排列树需要Ω(n!)时间。 

排列树与子集树最大的区别在于:

排列树的解包括整个集合S的元素

子集树的解只包括符合条件的集合S的子集

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

 

  • 回溯法的设计思想

  • 什么是回溯法

以深度优先的方式系统地搜索问题的解且结合剪枝函数的算法称为回溯法,它适合于解一些组合数较大的问题。

  • 算法设计思想

回溯法从根结点出发,按照深度优先策略遍历解空间树,搜索满足约束条件的解。在搜索至树中任一结点时,先判断该结点对应的部分解是否满足约束条件,或者是否超出限界函数(也称为目标函数)的界限,也就是判断该结点是否可能包含问题的答案解,如果肯定不包含,则跳过对以该结点为根的子树的搜索,即所谓剪枝Pruning);否则,进入以该结点为根的子树,继续按照深度优先策略搜索并进行判断。

若用回溯法求问题的所有解时,需要回溯到根结点,且根结点的所有可行的子树都要已被搜索完才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

 

回溯法的搜索过程涉及的结点(称为搜索空间)只是整个解空间树的一部分,在搜索过程中,通常采用两种策略避免无效搜索:

约束函数剪去得不到可行解的子树;

限界函数(目标函数)剪去得不到最优解的子树。

约束函数和限界函数(目标函数)的目的是相同的,都为了剪去不必要搜索的子树,减少问题求解所需实际生成的状态结点数,它们统称为剪枝函数pruning function

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

剪枝函数

当找到一个叶子结点时,求出其path对应的路径长度pathlen,通过比较,将最短路径长度保存在minpathlen

采用的剪枝策略是:当一个结点的当前路径长度pathlen>=minpathlen,该结点变成死结点。

数据结构&算法学习笔记——回溯法

 

  • 回溯法的求解过程

1、针对所给问题,定义问题的解向量和解空间

首先明确定义问题可能解的表现形式,进而定义解空间,问题的解空间应至少包含问题的一个(最优或最终)解。

2、设计剪枝函数---关键

通过约束条件或限界函数(目标函数)构建(利用显式约束和隐式约束)

3、构建解空间树,并进行深度优先搜索

深度优先方式结合剪枝函数搜索解空间树尽早避免无效检索。其中,深度优先方式可以选择递归回溯或者迭代回溯。

问题的解空间结构通常以树的形式表示,常用的两类典型的解空间树是子集树和排列树。

 

由于问题的解向量X=(x1, x2, …, xn)中的每个分量xi(1≤in)都属于一个有限集合Si={ai1, ai2, …, airi},因此,回溯法可以按某种顺序(例如字典序)依次考察笛卡儿积S1×S2×…×Sn中的元素。

初始时,令解向量X为空,然后,从根结点出发,选择S1的第一个元素作为解向量X的第一个分量,即x1= a11,如果X=(x1)是问题的部分解,则继续扩展解向量X,选择S2的第一个元素作为解向量X的第2个分量,否则,选择S1的下一个元素作为解向量X的第一个分量,即x1= a12。依此类推,一般情况下,如果X=(x1, x2, …, xi)是问题的部分解,则选择Si+1的第一个元素作为解向量X的第i+1个分量时,有下面三种情况:

(1如果X=(x1, x2, …, xi1)是问题答案解,则输出这个解。如果问题只希望得到一个解,则结束搜索,否则继续搜索其他解;

(2如果X=(x1, x2, …, xi1)是问题的部分解,则继续构造解向量的下一个分量;

(3如果X=(x1, x2, …, xi1)既不是问题的部分解也不是问题的答案解,则存在下面两种情况:

① 如果xi+1= ai1k不是集合Si1的最后一个元素,则令xi+1= ai1k1,即选择Si+1的下一个元素作为解向量X的第i+1个分量;

如果xi+1= ai1k是集合Si1的最后一个元素,就回溯到X=(x1, x2, …, xi),选择Si的下一个元素作为解向量X的第i个分量,假设xi= aik,如果aik不是集合Si的最后一个元素,则令xi= aik1;否则,就继续回溯到X=(x1, x2, …, xi1);

 

  • 回溯法的算法描述

  • 回溯法的非递归描述框架

数据结构&算法学习笔记——回溯法

  • 回溯法的递归描述框架

由于递归算法的形参具有自动回退(回溯)的能力,设计更简便。

(1)解空间为子集树

数据结构&算法学习笔记——回溯法

【例】有一个含n个整数的数组a,所有元素均不相同,设计一个算法求其所有子集(幂集)。

例如,a[]={123},所有子集是:{}{3}{2}{23}{1}{13}{12}{123}(输出顺序无关)。

解:显然本问题的解空间为子集树,每个元素只有两种扩展,要么选择,要么不选择。

采用深度优先搜索思路。解向量为x[]x[i]=0表示不选择a[i]x[i]=1表示选择a[i]。

i扫描数组a,也就是说问题的初始状态是(i=0x的元素均为0),目标状态是(i=nx为一个解)。从状态(ix)可以扩展出两个状态:

不选择a[i]元素 --> 下一个状态为(i+1x[i]=0)。

选择a[i]元素 --> 下一个状态为(i+1x[i]=1)。

算法描述:

数据结构&算法学习笔记——回溯法

(2)解空间为排列树---回溯法的递归描述框架

数据结构&算法学习笔记——回溯法

【例】有一个含n个整数的数组a,所有元素均不相同,求其所有元素的全排列。

例如,a[]={123},得到结果是(123)、(132)、(231)、(213)、(312)、(321)。

【求解思路】考虑递归求解

设A={a0,a2,…,an-1}是要进行排列的n个元素,Ai=A-{ai}。

集合A中元素的全排列记为perm(A)。

 (ai)perm(Ai)表示在全排列perm(Ai)的每一个排列前加上前缀ai得到的排列。

A的全排列可归纳定义如下:

 

当n=1时,perm(A)=(a0),其中a0是集合A中唯一的元素;

当n>1时,perm(A)=(a1)perm(A1)(a2)perm(A2)(an-1)perm(An-1)

递归求解

perm(a,n,i)表示求a[i..n-1](共n-i个元素)的全排列

perm(a,n,0)表示求a[0..n-1]的全排列

perm(a,n,n-1)表示求a[n-1..n-1] (共1个元素)的全排列,排列就是该元素本身

perm(a,n,n)表示求a[n..n-1](共0个元素)的全排列

 

递归模型

perm(a,n,i)     输出一个排列          i=n

perm(a,n,i)     对于j=i~n-1:                    i<n

              a[i]a[j]交换位置;

              perm(a,n,i+1);

              a[i]a[j]交换位置(恢复环境) 

算法描述:

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

  • 回溯法与其他算法设计方法的比较

回溯法与分支限界法

使用剪枝函数深度优先生成解空间树中结点的方法称为回溯法backtracking);

广度优先生成结点,并使用限界函数的方法称为分支限界法branch-and-bound)。

回溯法与蛮力法

回溯法是蛮力法的改进,蛮力法构造全部解,而回溯法每次只构造可能解的一部分, 然后评估这个部分解, 如果这个部分解有可能导致一个答案解, 则对其进一步构造, 否则, 就不必继续构造这个部分解了。

回溯法与动态规划

有一类这样的问题,它可以分解,但是又不能构建明确的动态规划函数或是递归解法,此时可考虑用回溯法解决。

回溯法的优点在于其程序结构明确,可读性强,易于理解,而且通过对问题的分析可以大大提高运行效率。但是,对于可以构建动态规划函数迭代求解的问题,尽量不用回溯法,因为时间开销比较长。

 

  • 回溯法的时空性能

  • 时间性能分析

 通常以回溯算法的解空间树中的结点数作为算法的时间分析依据,假设解空间树共有n层。

第1层有m0个满足约束条件的结点,每个结点有m1个满足约束条件的结点;

第2层有m0m1个满足约束条件的结点,同理,第3层有m0m1m2个满足约束条件的结点。

第n层有m0m1mn-1个满足约束条件的结点,则采用回溯法求所有解的算法的执行时间为 

T(n)=m0+m0m1+m0m1m2+…+m0m1m2mn-1。 

回溯法实际上属于蛮力穷举法,所以一般不会有很好的最坏时间复杂性,遍历具有指数阶个结点的解空间树,在最坏情况下,时间代价肯定为指数阶。

回溯法的有效性往往体现在当问题 规模n很大时,在搜索过程中对问题的解空间树实行大量剪枝。但是,对于具体的问题实例,很难预测回溯法的搜索行为,特别是很难估计出在搜索过程中所产生的结点数,这是分析回溯法的时间性能的主要困难。

  • 空间性能分析

用回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。

在任何时刻,算法只保存从根结点到当前扩展结点的路径。

如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式地存储整个解空间则需要O(2^h(n))或O(h(n)!)内存空间。

 

图问题中的回溯法

  • 图着色问题

  • 问题描述

图着色问题描述为:给定无向连通图G=(V, E)和正整数m,求最小的整数m,使得用m种颜色对G中的顶点着色,使得任意两个相邻顶点着色不同。若一个图最少需要m种颜色才能使图中每条边连接的2个顶点着不同颜色,则称这个数m为该图的色数。

数据结构&算法学习笔记——回溯法

  • 问题分析

从顶点1开始,按m种颜色的排列顺序,首先选择第一种颜色,然后检查是否矛盾,即相邻的区域中是否已有该颜色,若不矛盾,则涂色,若矛盾,则选择下一个颜色,再判断,当m种颜色都不可能时,则需回溯。

当第1个顶点的颜色确定之后,依次对第二个、第三个顶点……进行处理,当所有顶点都涂上颜色之后,得到一种解法。

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

第一步: 定义解向量

由于用m种颜色为无向图G=(V, E)着色,其中,V的顶点个数为n,可以用一个n元组X=(x1, x2, …, xn)描述图的一种可能着色,其中,xi∈{1, 2, …, m}(1≤i≤n)表示赋予顶点i的颜色。

例如,7元组x(1,2,1,3,1,3,4)表示对具有7个顶点的无向图的一种4着色,顶点1着颜色1,顶点2着颜色2,顶点3着颜色1,如此等等。

第二步:设计剪枝函数

如果在n元组X中,所有相邻顶点都不会着相同颜色,就称此n元组为答案解,否则为无效解。

数据结构&算法学习笔记——回溯法

第三步:构建解空间树(解空间树)

回溯法求解图着色问题,首先把所有顶点的颜色初始化为0,然后依次为每个顶点着色。在图着色问题的解空间树中,如果从根结点到当前结点对应一个部分解,也就是所有的颜色指派都没有冲突,则在当前结点处选择第一棵子树继续搜索,也就是为下一个顶点着颜色1,否则,对当前子树的兄弟子树继续搜索,也就是为当前顶点着下一个颜色,如果所有m种颜色都已尝试过并且都发生冲突,则回溯到当前结点的父结点处,上一个顶点的颜色被改变,依此类推。

  • 算法设计

解向量:(x1, x2, … , xn)表示顶点i所着颜色x[i]

约束函数:顶点i与已着色的相邻顶点颜色不重复。

1图的m着色问题非递归算法

数据结构&算法学习笔记——回溯法

2图的m着色问题递归算法

数据结构&算法学习笔记——回溯法

  • 算法复杂度分析

m种颜色为一个具有n个顶点的无向图着色,共有m^n种可能的着色组合,因此解空间树是一颗完全m叉树,树中的每一个节点都有m棵子树,最后一层有m^n个叶子节点,每个叶子节点代表一种可能着色,因此最坏情况下的时间性能为O(m^n)

 

组合问题中的回溯法

  • 八皇后问题

  • 问题描述

八皇后问题是十九世纪著名的数学家高斯于1850年提出的。问题是:在8×8的棋盘上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上。可以把八皇后问题扩展到n皇后问题,即在n×n的棋盘上摆放n个皇后,使任意两个皇后都不能处于同一行、同一列或同一斜线上。

  • 问题分析

第一步: 解向量

分析:N=4时,下图是一组解

显然,棋盘的每一行上可以而且必须摆放一个皇后,所以,n皇后问题的可能解用一个n元向量X=(x1, x2, …, xn)表示,其中,1≤in并且1≤xin,即i个皇后放在第i行第xi列上

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

利用约束条件,只需一维数组即可!

int  x[n] ;

x[i]i表示第i行皇后,x[i]的值表示第i行上皇后放在的列号

 

例如,下图八皇后问题的的一个解可以表示为

数据结构&算法学习笔记——回溯法

第二步:约束条件剪枝函数

不同行:数组x的下标保证不重复

不同列:x[i] x[j] ( 1<=i,j<=n;ij)

不同对角线:abs(x[i]-x[j])abs(i-j)

填到第K行时,就与前1~(K-1)行都需要进行比较

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

第三步:解空间树(解空间树)——排列树

将搜索过程中的每个状态用树的形式表示出来!画出状态树对书写程序有很大帮助!

解空间

xi 表示第 i 个皇后的列位置, 问题的解空间是 (x1,x2,…,xn), 既然没有两个皇后可以被放在同一行, 则解空间的大小为 n^n

解空间可以被组织成一个子集树。若每个皇后被限制在不同行, 解空间大小可以从 n^n 减少到n!解空间可以被组织成一棵排列树

排列树: n=4

数据结构&算法学习笔记——回溯法

回溯法求解4皇后问题的搜索过程

数据结构&算法学习笔记——回溯法

四皇后问题的解空间树(解空间树)

数据结构&算法学习笔记——回溯法

  • 算法设计

算法1——n皇后问题非递归算法

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

算法2N皇后问题递归算法

数据结构&算法学习笔记——回溯法

n皇后求解的回溯算法小结:

①用数组x[]存放皇后的位置,x[k]表示第k个皇后放置的位置,n皇后问题的一个解是x[1],x[2],…,x[n],数组x的下标从1开始。

②先放置第1个皇后,然后依2、3、…、n的次序放置其他皇后,当第n个皇后放置好后产生一个答案解。为了找所有解,还需要继续试探第n个皇后的下一个位置。

③第k(k<n)个皇后放置后,接着放置第k+1个皇后,在试探第k+1个皇后的位置时,都是从第1列开始的。

 

④当第k个皇后试探了所有列都不能放置时,则回溯到第k-1个皇后,此时与第k-1个皇后的位置x[k-1]有关,如果第k-1个皇后的列号小于n即x[k-1]<n,则将其移到下一列,继续试探;否则再回溯到第k-2个皇后,依此类推。

⑤若第1个皇后的所有位置回溯完毕,则算法结束。

⑥放置第k个皇后应与前面已经放置的k-1个皇后不发生冲突。

  • 时空复杂性

该算法中每个皇后都要试探n列,共n个皇后,其解空间是一棵子集树,不同于前面一般的二叉树子集树,这里每个结点可能有n棵子树,对应的算法时间复杂度为O(n^n)。

利用显式约束排除两个皇后在同一行或同一列的方法,可能解是(1,2,……,n)的一个排列,因此对应的解空间树中有n!个叶子节点,所以算法的时间复杂度可以降为O(n!) 。

不考虑输入所占用的存储空间,需要用O(n)的空间来存放解向量。

 

  • 0/1背包问题

  • 问题描述

有n个重量分别为{w1w2wn}的物品,它们的价值分别为{v1v2vn},给定一个容量为W的背包。

设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且重量和恰好为W具有最大的价值。

  • 问题分析

用x[1..n]数组存放最优解,其中每个元素取10x[i]=1表示第i个物品放入背包中,x[i]=0表示第i个物品不放入背包中。

为了更清楚地描述算法,将这些给定的算法输入设计成全局变量。

这是一个求最优解问题。

对第i层上的某个分枝结点,对应的状态为Knapsack(itwtvop),其中tw表示装入背包中的物品总重量,tv表示背包中物品总价值,op记录一个解向量。该状态的两种扩展如下:

(1)选择第i个物品放入背包:op[i]=1tw=tw+w[i]tv=tv+v[i],转向下一个状态Knapsack (i+1twtvop)。该决策对应左分枝。

(2)不选择第i个物品放入背包:op[i]=0tw不变,tv不变,转向下一个状态Knapsack (i+1twtvop)。该决策对应右分枝。

叶子结点表示已经对n个物品做了决策,对应一个解。对所有叶子结点进行比较求出满足tw=W的最大tv(maxv表示),对应的最优解op存放到x中。

例:0/1背包问题(W=6):

数据结构&算法学习笔记——回溯法

  • 算法设计

数据结构&算法学习笔记——回溯法

1解空间为子集树递归的算法

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

改进:左剪枝

对于第i层的有些结点,tw+w[i]已超过了W,显然再选择w[i]是不合适的。如第2层的(54)结点,tw=5w[2]=3,而tw+w[2]>W,选择物品2进行扩展是不必要的,可以增加一个限界条件进行剪枝,如若选择物品i会导致超重即tw+w[i]>W,就不再扩展该结点,也就是仅仅扩展tw+w[i]≤W的左孩子结点。

数据结构&算法学习笔记——回溯法

数据结构&算法学习笔记——回溯法

2解空间为子集树递归的算法(含左剪枝)

数据结构&算法学习笔记——回溯法

改进:右剪枝

rw表示考虑第i个物品时剩余物品的重量。

当不选择物品i时,若tw+rw<W(注意rw中包含w[i]时,也就是说即使选择后面的所有物品,重量也不会达到W,因此不必要再考虑扩展这样的结点,也就是说,对于右分枝仅仅扩展tw+rw>W的结点(注意不包含等于)。

如第2层的(00)结点,此时tw=0rw=6(物品234的重量和)tw+rw=6不大于W(此时又不选择物品2),所以不必扩展其右孩子结点。

数据结构&算法学习笔记——回溯法

3解空间为子集树递归的算法(含左、右剪枝)

数据结构&算法学习笔记——回溯法

  • 算法分析

该算法不考虑剪枝时解空间树中有2(n+1)-1个结点,对应的算法时间复杂度为O(2^n)。

 

相关文章: