回溯法的一般描述
一般表达
可用回溯法求解的问题 P,通常要能表达为:对于已知的由 n 元组(x1,x2,…,
xn)组成的一个状态空间 E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},给定
关于 n 元组中的一个分量的一个约束集 D,要求 E 中满足 D 的全部约束条件的所有 n
元组。其中 Si 是分量 xi 的定义域,且 |Si| 有限,i=1,2,…,n。我们称 E 中满足
D 的全部约束条件的任一 n 元组为问题 P 的一个解。
解问题 P 的最朴素的方法就是枚举法,即对 E 中的所有 n 元组逐一地检测其是
否满足 D 的全部约束,若满足,则为问题 P 的一个解。但显然,其计算量是相当大
的。
规律
我们发现,对于许多问题,所给定的约束集 D 具有完备性,即 i 元组(x1,x2,…,
xi)满足 D 中仅涉及到 x1,x2,…,xi 的所有约束意味着 j(j<i)元组(x1,x2,…,
xj)一定也满足 D 中仅涉及到 x1,x2,…,xj 的所有约束,i=1,2,…,n。换句话
说,只要存在 0≤j≤n-1,使得(x1,x2,…,xj)违反 D 中仅涉及到 x1,x2,…,xj
的约束之一,则以(x1,x2,…,xj)为前缀的任何 n 元组(x1,x2,…,xj,xj+1,…,
xn)一定也违反 D 中仅涉及到 x1,x2,…,xi 的一个约束,n≥i>j。因此,对于约
束集 D 具有完备性的问题 P,一旦检测断定某个 j 元组(x1,x2,…,xj)违反 D 中
仅涉及 x1,x2,…,xj 的一个约束,就可以肯定,以(x1,x2,…,xj)为前缀的任
何 n 元组(x1,x2,…,xj,xj+1,…,xn)都不会是问题 P 的解,因而就不必去搜
索它们、检测它们。回溯法正是针对这类问题,利用这类问题的上述性质而提出来的
比枚举法效率更高的算法。
空间树
回溯法首先将问题 P 的 n 元组的状态空间 E 表示成一棵高为 n 的带权有序树 T,
把在 E 中求问题 P 的所有解转化为在 T 中搜索问题 P 的所有解。树 T 类似于检索树,
它可以这样构造:
设 Si 中的元素可排成 xi(1) ,xi(2) ,…,xi(mi-1) ,|Si| =mi,i=1,2,…,n。
从根开始,让 T 的第 I 层的每一个结点都有 mi 个儿子。这 mi 个儿子到它们的双亲的
边,按从左到右的次序,分别带权 xi+1(1) ,xi+1(2) ,…,xi+1(mi) ,i=0,1,2,…,
n-1。照这种构造方式,E 中的一个 n 元组(x1,x2,…,xn)对应于 T 中的一个叶
子结点,T 的根到这个叶子结点的路径上依次的 n 条边的权分别为 x1,x2,…,xn,
反之亦然。另外,对于任意的 0≤i≤n-1,E 中 n 元组(x1,x2,…,xn)的一个前缀
I 元组(x1,x2,…,xi)对应于 T 中的一个非叶子结点,T 的根到这个非叶子结点
的路径上依次的 I 条边的权分别为 x1,x2,…,xi,反之亦然。特别,E 中的任意一
个 n 元组的空前缀(),对应于 T 的根。
因而,在 E 中寻找问题 P 的一个解等价于在 T 中搜索一个叶子结点,要求从 T
的根到该叶子结点的路径上依次的 n 条边相应带的 n 个权 x1,x2,…,xn 满足约束
集 D 的全部约束。在 T 中搜索所要求的叶子结点,很自然的一种方式是从根出发,
按深度优先的策略逐步深入,即依次搜索满足约束条件的前缀 1 元组(x1i)、前缀 2
元组(x1,x2)、…,前缀 I 元组(x1,x2,…,xi),…,直到 i=n 为止。
在回溯法中,上述引入的树被称为问题 P 的状态空间树;树 T 上任意一个结点
被称为问题 P 的状态结点;树 T 上的任意一个叶子结点被称为问题 P 的一个解状态
结点;树 T 上满足约束集 D 的全部约束的任意一个叶子结点被称为问题 P 的一个回
答状态结点,它对应于问题 P 的一个解
[编辑本段]
用回溯法解题的一般步骤:
(1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
回溯法 C 语言举例
八皇后问题是能用回溯法解决的一个经典问题。
八皇后问题是一个古老而著名的问题。该问题是十九世纪著名的数学家高斯 185
0 年提出:在 8X8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇
后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
用回溯法解决八皇后问题的 C 语言程序
#include
#include
int col[9]={0},a[9];
int b[17],c[17];
main()
{
int m,good;
int
i,j,k;
char q;
for(i=0;i<17;i++)
{
if(i<9) a[i]=1;
b[i]=1;c[i]=1;
}
to quit\n");
good=1;
col[1]=1;
m=1;
while(col[0]!=1)
{
if(good)
if(m==8)
{
for(i=1;i<9;i++)
printf("col[%d] %d\n",i,col[i]);
printf("input
'q'
scanf("%c",&q);
getchar();
if(q=='q'||q=='Q') exit(0);
while(col[m]==8)
{
m--;
a[col[m]]=b[m+col[m]]=c[8+m-col[m]]=1;
}
a[col[m]]=b[m+col[m]]=c[8+m-col[m]]=1;
col[m]++;
}
else
{
a[col[m]]=b[m+col[m]]=c[8+m-col[m]]=0;
m++;
col[m]=1;
}
else
{
while(col[m]==8)
{
m--;
a[col[m]]=b[m+col[m]]=c[8+m-col[m]]=1;
}
col[m]++;
}
good=a[col[m]]&&b[m+col[m]]&&c[8+m-col[m]];
');writeln;end
}
}
用回溯法解决八皇后问题的 PASCAL 语言程序
integer;
program xy;
var
a:array[1..100]of
b,c,d:array[-100..100]of boolean;
n,i,j,k:integer;
procedure try(k:integer);
var i:integer;
begin
if k>n then begin for j:=1 to n do write(a[j],'
else begin
for i:=1 to n do
if (b[i])and(c[k+i])and(d[k-i]){找条件}
then begin a[k]:=i;
b[i]:=false;
c[k+i]:=false;
d[k-i]:=false;
try(k+1);
b[i]:=true;
c[k+i]:=true;
d[k-i]:=true;
end;
end;
end;
begin
readln(n);
fillchar(b,sizeof(b),true);
fillchar(c,sizeof(c),true);
fillchar(d,sizeof(d),true);
try(1);
readln;
end.
{b[i]是判断横和竖,c[k+i]斜线/,d[k-i]斜线}
回溯法解决迷宫问题
#include
#include
#define m 5
#define n 6
int sf=0;
int mase[m][n]={{0,0,0,1,0,0},{0,1,0,0,0,0},{0,1,1,1,1,0},{0,0,0,0,0,1},{1,0,1,1,
0,0}};
void search(int x,int y)
{
if((x==m-1)&&(y==n-1))
sf=1;
else
{
if((sf!=1)&&(y!=n-1)&&mase[x][y+1]==0)
search(x,y+1);
if((sf!=1)&&(x!=m-1)&&mase[x+1][y]==0)
search(x+1,y);
if((sf!=1)&&(y!=0)&&mase[x][y-1]==0)
search(x,y-1);
if((sf!=1)&&(x!=0)&&mase[x-1][y]==0)
search(x-1,y);
if(sf!=1)
mase[x][y]=1;
}
}
int main()
{
int
//clrscr();
search(0,0);
for(i=0;i递归法
递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采
用,为此在进一步介绍其他算法设计方法之前先讨论它。
能采用递归描述的算法通常有这样的特征:为求解规模为 N 的问题,设法将它分
解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规
模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更
小问题的解构造出规模较大问题的解。特别地,当规模 N=1 时,能直接得解。
递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规
模为 n)的求解推到比原问题简单一些的问题(规模小于 n)的求解。例如上例中,
求解 fib(n),把它推到求解 fib(n-1)和 fib(n-2)。也就是说,为计算 fib(n),必须先计算
fib(n-1)和 fib(n-2),而计算 fib(n-1)和 fib(n-2),又必须先计算 fib(n-3)和 fib(n-4)。依
次类推,直至计算 fib(1)和 fib(0),分别能立即得到结果 1 和 0。在递推阶段,必须要
有终止递归的情况。例如在函数 fib 中,当 n 为 1 和 0 的情况。
在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,
例如得到 fib(1)和 fib(0)后,返回得到 fib(2)的结果,……,在得到了 fib(n-1)和 fib(n-
2)的结果后,返回得到 fib(n)的结果。
在编写递归函数时要注意,函数中的局部变量和参数只是局限于当前调用层,当
递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简
单问题”层,它们各有自己的参数和局部变量。
由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的
执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法
编写程序。例如上例计算斐波那契数列的第 n 项的函数 fib(n)应采用递推算法,即从
斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第 n 项。
扩展阅读:
1.【问题】
2.编写计算斐波那契(Fibonacci)数列的第 n 项函数 fib(n)。
3.斐波那契数列为:0、1、1、2、3、……,即:
4. fib(0)=0;
5. fib(1)=1;
6. fib(n)=fib(n-1)+fib(n-2) (当 n>1 时)。
7.写成递归函数有:
8.int fib(int n)
9.{ if (n==0) return 0;
10. if (n==1) return 1;
11. if (n>1) return fib(n-1)+fib(n-2);
12.}
动态规划法
动态 规划法[dynamic programming method (DP)]是系统分析中一种常用的
方法 。在水 资源 规划中,往往 涉及到 地表 水库调 度、水 资源 量的合 理分配 、优 化调度
等问 题,而这 些问题 又可 概化为 多阶段 决策 过程问 题。动 态规 划法是 解决此 类问题 的
有效方法。动态规划法是 20 世纪 50 年代由贝尔曼(R. Bellman)等人提出,用来解
决多 阶段决 策过 程问题 的一种 最优 化方法 。所谓 多阶 段决策 过程,就是 把研究 问题 分
成若干个相互联系的阶段,由每个阶段都作出决策,从而使整个过程达到最优化。许多
实 际 问 题 利 用 动 态 规 划 法 处 理,常 比 线 性 规 划 法 更 为 有 效,特 别 是 对 于 那 些 离 散 型 问
题。实际上,动态规划法就是分多阶段进行决策,其基本思路是:按时空特点将复杂问
题 划 分 为 相 互 联 系 的 若 干 个 阶 段,在 选 定 系 统 行 进 方 向 之 后,逆 着 这 个 行 进 方 向,从 终
点 向 始 点 计 算,逐 次 对 每 个 阶 段 寻 找 某 种 决 策,使 整 个 过 程 达 到 最 优,故 又 称 为 逆 序 决
策过程。
[1]动态规划的基本思想
前文主要介绍了动态规划的一些理论依据,我们将前文所说的具有明显的阶段划
分和状态转移方程的动态规划称为标准动态规划,这种标准动态规划是在研究多阶段
决策问题时推导出来的,适合用于理论上的分析。在实际应用中,许多问题的阶段划
分并不明显,这时如果刻意地划分阶段法反而麻烦。一般来说,只要该问题可以划分
成规模更小的子问题,并且原问题的最优解中包含了子问题的最优解(即满足最优子
化原理),则可以考虑用动态规划解决。
动态规划的实质是分治思想和解决冗余,因此,动态规划是一种将问题实例分解
为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优
化问题的算法策略。
由此可知,动态规划法与分治法和贪心法类似,它们都是将问题实例归纳为更小
的、相似的子问题,并通过求解子问题产生一个全局最优解。其中贪心法的当前选择
可能要依赖已经作出的所有选择,但不依赖于有待于做出的选择和子问题。因此贪心
法自顶向下,一步一步地作出贪心选择;而分治法中的各个子问题是独立的(即不包
含公共的子子问题),因此一旦递归地求出各子问题的解后,便可自下而上地将子问
题的解合并成问题的解。但不足的是,如果当前选择可能要依赖子问题的解时,则难
以通过局部的贪心策略达到全局最优解;如果各子问题是不独立的,则分治法要做许
多不必要的工作,重复地解公共的子问题。
解决上述问题的办法是利用动态规划。该方法主要应用于最优化问题,这类问题
会有多种可能的解,每个解都有一个值,而动态规划找出其中最优(最大或最小)值
的解。若存在若干个取最优值的解的话,它只取其中的一个。但是首先要保证该问题
的无后效性,即无论当前取哪个解,对后面的子问题都没有影响.在求解过程中,该方法
也是通过求解局部子问题的解达到全局最优解,但与分治法和贪心法不同的是,动态
规划允许这些子问题不独立,(亦即各子问题可包含公共的子子问题)也允许其通过
自身子问题的解作出选择,该方法对每一个子问题只解一次,并将结果保存起来,避
免每次碰到时都要重复计算。
因此,动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的
子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第
一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。
3、动态规划算法的基本步骤
设计一个标准的动态规划算法,通常可按以下几个步骤进行:
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意这
若干个阶段一定要是有序的或者是可排序的(即无后向性),否则问题就无法用动态
规划求解。
(2)选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态
表示出来。当然,状态的选择要满足无后效性。