搜索算法---回溯
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
B3
A2 A1 A B1 B2 C2 C1
D3
D2 E1 D1
跳马问题
5.
从C2出发,有四条路径可以选择,选择D4, 从D4出发又有两条路径,选择E1错误,返 回D4选择E2,从E2出发有两条路径,先选 择F1错误,返回E2选择B,而B恰好是我们 要到达的目标点,至此,一条路径查找成功。
B3 A2 A1 A B1 B2 C2 E2 E1 B
一、回溯的概念
从问题的某种可能情况出发,搜索所有能到达的可能情况,然 后以其中一种可能的情况为新的出发点,继续向下探索,当所 有可能情况都探索过且都无法到达目标的时候,再回退到上一 个出发点,继续探索另一个可能情况,这种不断回头寻找目标 的方法称为“回溯法”。
一、回溯的概念
回溯算法是一种有条不紊的搜索问题答案的方法,是一种能避 免不必要搜索的穷举式的搜索算法,其基本思想就是穷举搜索。 常用于查找问题的解集或符合某些限制条件的最佳解集。
非递归算法(预习掌握):
procedure main;{非递归算法} var k:integer; begin x[1]:=0; {初始化,每次穷举下一列进行} k:=1; {从第一个皇后第一行开始} while k>0 do {行号大于0} begin x[k]:=x[k]+1;{枚举列,即第k个皇后在第k行的位置(列)} while (x[k]<=n)and(not (place(k,x[k]))) do inc(x[k]); if x[k]<= n then {第k皇后找到合适的列x[k]} if k=n then print else begin inc(k); {放置下一个皇后} x[k]:=0; {初始化,每次穷举下一列进行} end{if k=n} else {x[k]>n} dec(k);{第k个皇后无位置可放,回溯(重新放置第k-1个皇后)} end;{while} end;
方法二:
方法一的缺点:每次调用函数place(I,j)判断第i个皇后能否放在j列时: 有一个for循环判断,显然浪费时间。 function place(k,i:integer):boolean;{第k个皇后能否放在第i列} var j:integer; begin for j:=1 TO K-1 do if (x[j]=i) or (abs(x[j]-i)=abs(j-k)) then begin place:=false; exit end; place:=true; end;
递归算法:
procedure try(i:integer); var j:integer; begin if i=n+1 then print else for j:=1 to n do if a[j] and b[i+j] and c[i-j] then begin x[i]:=j; a[j]:=false; {列控制标志} b[i+j]:=false; {左下右上方斜线控制标志} c[i-j]:=false; {左上右下方斜线控制标志} try(i+1); {如果不能递归进行,既a[j] and b[i+j] and c[i-j]=false: 无法放置i+1个皇后,说明当前皇后i放置不正确,要回溯,消除标 志} a[j]:=true; b[i+j]:=true; c[i-j]:=true end; end;
A2 A1 A
跳马问题
2.
3.
当到达A1点后,又有三条线路可以选择, 于是再任意选择一条,到达B1。 从B1再出发,又有两条线路可以选择,先 选一条,到达C1。
B3 A2 A1 B1 A B2 C2 C1
跳马问题
4.
从C1出发,可以有三条路径,选择D1。但到了D1 以后,我们无路可走且D1也不是最终目标点,因 此,选择D1是错误的,我们退回C1重新选择D2。 同样D2也是错误的。再回到C1选择D3。D3只可 以到E1,但E1也是错误的。返回D3后,没有其他 选择,说明D3也是错误的,再回到C1。此时C1不 再有其他选择,故C1也是错误的,退回B1,选择 C2进行尝试。
procedure main; {非递归算法} var k:integer; begin x[1]:=0;{初始化,每次穷举下一列进行} k:=1; {从第一个皇后第一行开始} while k>0 do {行号大于0} begin if x[k]<>0 then {释放当前位置(回溯),当前放法不可取,一般是dec(k)后执行} begin a[x[k]]:=true; b[x[k]+k]:=true; c[k-x[k]]:=true; end; inc(x[k]);{枚举列,即第k个皇后在第k行的位置(列)} while (x[k]<=n)and not(a[x[k]] and b[x[k]+k] and c[k-x[k]]) do inc(x[k]); if x[k]<=n then if k=n then print else begin a[x[k]]:=false; b[x[k]+k]:=false; c[k-x[k]]:=false; inc(k); x[k]:=0; end else dec(k);{当x[k]>n,即无法放置时皇后k,回溯,重新放置皇后k-1} end; end;
二、回溯的一般描述(竞赛指导p263)
program 程序名; const maxdepth=xxx; type statetype=****; {状态类型定义} var stack:array[1..maxdepth] of statetype; {存当前路径} total:integer; {路径数} procedure make(k:integer); {递归搜索以stack[k]为初始接点的所有路径} var i:integer; {子节点个数} begin if stack[k-1]是目标状态 then begin total:=total+1; 输出当前路径stack[1]..stack[k-1]; exit; {回溯(如果只需要一条路径,则exit改为halt即可)}} end; for i:=算符最小值 to 算符最大值 do begin 算符i作用于生成stack[k-1]产生子状态stack[k]; if stack[k]满足约束条件 then make(k+1);{若不满足,则通过for循环换一个算符扩 end; end;
二、回溯的一般描述
begin total:=0; 初始化处理; make(1);{从初始状态出发} 打印路径数total; end;
三、回溯的一般步骤
首先需要为问题定义一个解空间(solution space),这个空间 必须至少包含问题的一个解; 然后我们需要组织解空间,以便它能被容易地搜索。典型的组 织方法是图或树。 最后对这个空间即可按深度优先的方法从开始节点进行搜索。
例1:数字排列问题(全排列)
P1358 《高级本》p3
全排列的另类实现方法(思考)
[算法描述] 1.1,2……N依次赋给a[1]至a[n],输出第一种排列; 2.构造下一种全排列,分四步完成: (1) i的初值为1,在a[1]至a[n]中搜索找出相应的i,使i是 a[k]>a[k-1]的k中最大的,即i=max{k|a[k]>a[k1],k=2,3…n}; (2) 在a[x]至a[n]中搜索找出相应的j,使j是a[k]>a[i-1]的k 中最大的,即j=max{k|a[k]>a[i-1],k=i,i+1…n}; (3) 交换a[i-1]与a[j]形成新的序列; (4) 对新的序列从 a[i+1]……a[n]进行逆序处理,输出相 应序列. 3.重复2直到i=1时结束
*
*
方法一:分析:
1、问题解的形式: x:array [1..n] of integer; {x[i]:第i个皇后放在第i行,第x[i]列,保证所有皇后不同行} 问题的解变成求(x[1],x[2],。。。X[n])
4皇后问题的解: (2,4,1,3), (3,1,4,2)
*
*
*
* *
*
* *
2、放置第k(1<=k<=n)个皇后的递归算法:
跳马问题
马走日字,当马一开始在黄点时,它下一步 可以到达的点有以下的八个,但由于题目规 定了只能往右走的限制,所以它只能走到四 个绿色点之一。
跳马问题
1.
当马一开始位于左下角的时候,根据规则, 它只有两条线路可以选择(另外两条超出棋 盘的范围),我们无法预知该走哪条,故任 意选择一条,到达A1。
if 第k个皇后能够放置在第i列 begin
放置第k个皇后在第j列(x[k]=i);
try(k+1); end;
end;
3、怎样判断:第k个皇后能否放置在第i列 :
function place(k,i:integer):boolean; {第k个皇后能否放在第i列} var j:integer; begin for j:=1 TO K-1 do if (x[j]=i) or (abs(x[j]-i)=abs(j-k)) then begin place:=false; exit end; place:=true; end;
4、输出解: procedure print; var j:integer; begin count:=count+1; write('answer',count,':'); for j:=1 to n-1 do write(x[j],' '); writeln(x[n]); end;
主程序:try(1)
例2 皇后问题(p1249)
题目来源:《高级本》p5 [问题描述]
在n×n的国际象棋盘上,放置n个皇后,使任何一个皇后都不能吃 掉另一个,要使任何一个皇后都不能吃掉另一个,需满足的条件是: 同一行、同一列、同一对角线上只能有一个皇后。求放置方法. 如:n=4时,有以下2种放置方法.
*
* * * * *
例3:跳马问பைடு நூலகம்(p1570 p1003)
在n×m棋盘上有一中国象棋中的马:
1. 2.
马走日字; 马只能往右走。
请你找出一条可行路径,使得马可以从棋盘 的左下角(1,1)走到右上角(n,m)。
跳马问题
输入:9 5 输出:
(1,1)->(3,2)->(5,1)->(6,3)->(7,1)->(8,3)->(9,5)
D4
F1
跳马问题
从上面的分析我们可以得知:
1.
2.
在无法确定走哪条线路的时候,任选一条线路 进行尝试; 当从某点出发,所有可能到达的点都不能到达 终点时,说明此点是一个死节点,必须回溯到 上一个点,并重新选择一条新的线路进行尝试。
var x:array[1..n] of integer; a:array[1..n] of boolean; {列控制标志:true:可以放,false:不能放} b:array[2..2*n] of boolean; {左下右上方斜线控制标志,true:可以放,false:不能放} c:array[1-n..n-1]of boolean; {左上右下方斜线控制标志,true:可以放,false:不能放} 初始时: fillchar(x,sizeof(x),0); fillchar(a,sizeof(a),true); fillchar(b,sizeof(b),true); fillchar(c,sizeof(c),true);
procedure
try(k);
{搜索第k个皇后所在的列x[k]=?,前k -1个已放好,即已求得x[1]…x[k-1] } var i:integer;
begin
if k=n+1 then else print(输出放置方案:数组x)
for i:=1
to
n do {搜索第k个皇后所在的列j}
then
搜索算法---回溯
搜索的本质
通用的解题法
一、两种题型: 1.简明的数学模型揭示问题本质。对于这一类试题,我们尽量用数学 方法求解。 2.对给定的问题建立数学模型,或即使有一定的数学模型,但采用数 学方法解决有一定困难。对于这一类试题,我们只好用模拟或搜索求 解。搜索的策略选择此时特别重要 二、搜索的本质: 搜索的本质就是逐步试探,在试探过程中找到问题的答案 三、搜索问题考察的范围 1.算法的实现能力 2.优化算法的能力
A2 A1 A B1 B2 C2 C1
D3
D2 E1 D1
跳马问题
5.
从C2出发,有四条路径可以选择,选择D4, 从D4出发又有两条路径,选择E1错误,返 回D4选择E2,从E2出发有两条路径,先选 择F1错误,返回E2选择B,而B恰好是我们 要到达的目标点,至此,一条路径查找成功。
B3 A2 A1 A B1 B2 C2 E2 E1 B
一、回溯的概念
从问题的某种可能情况出发,搜索所有能到达的可能情况,然 后以其中一种可能的情况为新的出发点,继续向下探索,当所 有可能情况都探索过且都无法到达目标的时候,再回退到上一 个出发点,继续探索另一个可能情况,这种不断回头寻找目标 的方法称为“回溯法”。
一、回溯的概念
回溯算法是一种有条不紊的搜索问题答案的方法,是一种能避 免不必要搜索的穷举式的搜索算法,其基本思想就是穷举搜索。 常用于查找问题的解集或符合某些限制条件的最佳解集。
非递归算法(预习掌握):
procedure main;{非递归算法} var k:integer; begin x[1]:=0; {初始化,每次穷举下一列进行} k:=1; {从第一个皇后第一行开始} while k>0 do {行号大于0} begin x[k]:=x[k]+1;{枚举列,即第k个皇后在第k行的位置(列)} while (x[k]<=n)and(not (place(k,x[k]))) do inc(x[k]); if x[k]<= n then {第k皇后找到合适的列x[k]} if k=n then print else begin inc(k); {放置下一个皇后} x[k]:=0; {初始化,每次穷举下一列进行} end{if k=n} else {x[k]>n} dec(k);{第k个皇后无位置可放,回溯(重新放置第k-1个皇后)} end;{while} end;
方法二:
方法一的缺点:每次调用函数place(I,j)判断第i个皇后能否放在j列时: 有一个for循环判断,显然浪费时间。 function place(k,i:integer):boolean;{第k个皇后能否放在第i列} var j:integer; begin for j:=1 TO K-1 do if (x[j]=i) or (abs(x[j]-i)=abs(j-k)) then begin place:=false; exit end; place:=true; end;
递归算法:
procedure try(i:integer); var j:integer; begin if i=n+1 then print else for j:=1 to n do if a[j] and b[i+j] and c[i-j] then begin x[i]:=j; a[j]:=false; {列控制标志} b[i+j]:=false; {左下右上方斜线控制标志} c[i-j]:=false; {左上右下方斜线控制标志} try(i+1); {如果不能递归进行,既a[j] and b[i+j] and c[i-j]=false: 无法放置i+1个皇后,说明当前皇后i放置不正确,要回溯,消除标 志} a[j]:=true; b[i+j]:=true; c[i-j]:=true end; end;
A2 A1 A
跳马问题
2.
3.
当到达A1点后,又有三条线路可以选择, 于是再任意选择一条,到达B1。 从B1再出发,又有两条线路可以选择,先 选一条,到达C1。
B3 A2 A1 B1 A B2 C2 C1
跳马问题
4.
从C1出发,可以有三条路径,选择D1。但到了D1 以后,我们无路可走且D1也不是最终目标点,因 此,选择D1是错误的,我们退回C1重新选择D2。 同样D2也是错误的。再回到C1选择D3。D3只可 以到E1,但E1也是错误的。返回D3后,没有其他 选择,说明D3也是错误的,再回到C1。此时C1不 再有其他选择,故C1也是错误的,退回B1,选择 C2进行尝试。
procedure main; {非递归算法} var k:integer; begin x[1]:=0;{初始化,每次穷举下一列进行} k:=1; {从第一个皇后第一行开始} while k>0 do {行号大于0} begin if x[k]<>0 then {释放当前位置(回溯),当前放法不可取,一般是dec(k)后执行} begin a[x[k]]:=true; b[x[k]+k]:=true; c[k-x[k]]:=true; end; inc(x[k]);{枚举列,即第k个皇后在第k行的位置(列)} while (x[k]<=n)and not(a[x[k]] and b[x[k]+k] and c[k-x[k]]) do inc(x[k]); if x[k]<=n then if k=n then print else begin a[x[k]]:=false; b[x[k]+k]:=false; c[k-x[k]]:=false; inc(k); x[k]:=0; end else dec(k);{当x[k]>n,即无法放置时皇后k,回溯,重新放置皇后k-1} end; end;
二、回溯的一般描述(竞赛指导p263)
program 程序名; const maxdepth=xxx; type statetype=****; {状态类型定义} var stack:array[1..maxdepth] of statetype; {存当前路径} total:integer; {路径数} procedure make(k:integer); {递归搜索以stack[k]为初始接点的所有路径} var i:integer; {子节点个数} begin if stack[k-1]是目标状态 then begin total:=total+1; 输出当前路径stack[1]..stack[k-1]; exit; {回溯(如果只需要一条路径,则exit改为halt即可)}} end; for i:=算符最小值 to 算符最大值 do begin 算符i作用于生成stack[k-1]产生子状态stack[k]; if stack[k]满足约束条件 then make(k+1);{若不满足,则通过for循环换一个算符扩 end; end;
二、回溯的一般描述
begin total:=0; 初始化处理; make(1);{从初始状态出发} 打印路径数total; end;
三、回溯的一般步骤
首先需要为问题定义一个解空间(solution space),这个空间 必须至少包含问题的一个解; 然后我们需要组织解空间,以便它能被容易地搜索。典型的组 织方法是图或树。 最后对这个空间即可按深度优先的方法从开始节点进行搜索。
例1:数字排列问题(全排列)
P1358 《高级本》p3
全排列的另类实现方法(思考)
[算法描述] 1.1,2……N依次赋给a[1]至a[n],输出第一种排列; 2.构造下一种全排列,分四步完成: (1) i的初值为1,在a[1]至a[n]中搜索找出相应的i,使i是 a[k]>a[k-1]的k中最大的,即i=max{k|a[k]>a[k1],k=2,3…n}; (2) 在a[x]至a[n]中搜索找出相应的j,使j是a[k]>a[i-1]的k 中最大的,即j=max{k|a[k]>a[i-1],k=i,i+1…n}; (3) 交换a[i-1]与a[j]形成新的序列; (4) 对新的序列从 a[i+1]……a[n]进行逆序处理,输出相 应序列. 3.重复2直到i=1时结束
*
*
方法一:分析:
1、问题解的形式: x:array [1..n] of integer; {x[i]:第i个皇后放在第i行,第x[i]列,保证所有皇后不同行} 问题的解变成求(x[1],x[2],。。。X[n])
4皇后问题的解: (2,4,1,3), (3,1,4,2)
*
*
*
* *
*
* *
2、放置第k(1<=k<=n)个皇后的递归算法:
跳马问题
马走日字,当马一开始在黄点时,它下一步 可以到达的点有以下的八个,但由于题目规 定了只能往右走的限制,所以它只能走到四 个绿色点之一。
跳马问题
1.
当马一开始位于左下角的时候,根据规则, 它只有两条线路可以选择(另外两条超出棋 盘的范围),我们无法预知该走哪条,故任 意选择一条,到达A1。
if 第k个皇后能够放置在第i列 begin
放置第k个皇后在第j列(x[k]=i);
try(k+1); end;
end;
3、怎样判断:第k个皇后能否放置在第i列 :
function place(k,i:integer):boolean; {第k个皇后能否放在第i列} var j:integer; begin for j:=1 TO K-1 do if (x[j]=i) or (abs(x[j]-i)=abs(j-k)) then begin place:=false; exit end; place:=true; end;
4、输出解: procedure print; var j:integer; begin count:=count+1; write('answer',count,':'); for j:=1 to n-1 do write(x[j],' '); writeln(x[n]); end;
主程序:try(1)
例2 皇后问题(p1249)
题目来源:《高级本》p5 [问题描述]
在n×n的国际象棋盘上,放置n个皇后,使任何一个皇后都不能吃 掉另一个,要使任何一个皇后都不能吃掉另一个,需满足的条件是: 同一行、同一列、同一对角线上只能有一个皇后。求放置方法. 如:n=4时,有以下2种放置方法.
*
* * * * *
例3:跳马问பைடு நூலகம்(p1570 p1003)
在n×m棋盘上有一中国象棋中的马:
1. 2.
马走日字; 马只能往右走。
请你找出一条可行路径,使得马可以从棋盘 的左下角(1,1)走到右上角(n,m)。
跳马问题
输入:9 5 输出:
(1,1)->(3,2)->(5,1)->(6,3)->(7,1)->(8,3)->(9,5)
D4
F1
跳马问题
从上面的分析我们可以得知:
1.
2.
在无法确定走哪条线路的时候,任选一条线路 进行尝试; 当从某点出发,所有可能到达的点都不能到达 终点时,说明此点是一个死节点,必须回溯到 上一个点,并重新选择一条新的线路进行尝试。
var x:array[1..n] of integer; a:array[1..n] of boolean; {列控制标志:true:可以放,false:不能放} b:array[2..2*n] of boolean; {左下右上方斜线控制标志,true:可以放,false:不能放} c:array[1-n..n-1]of boolean; {左上右下方斜线控制标志,true:可以放,false:不能放} 初始时: fillchar(x,sizeof(x),0); fillchar(a,sizeof(a),true); fillchar(b,sizeof(b),true); fillchar(c,sizeof(c),true);
procedure
try(k);
{搜索第k个皇后所在的列x[k]=?,前k -1个已放好,即已求得x[1]…x[k-1] } var i:integer;
begin
if k=n+1 then else print(输出放置方案:数组x)
for i:=1
to
n do {搜索第k个皇后所在的列j}
then
搜索算法---回溯
搜索的本质
通用的解题法
一、两种题型: 1.简明的数学模型揭示问题本质。对于这一类试题,我们尽量用数学 方法求解。 2.对给定的问题建立数学模型,或即使有一定的数学模型,但采用数 学方法解决有一定困难。对于这一类试题,我们只好用模拟或搜索求 解。搜索的策略选择此时特别重要 二、搜索的本质: 搜索的本质就是逐步试探,在试探过程中找到问题的答案 三、搜索问题考察的范围 1.算法的实现能力 2.优化算法的能力