• 搜索与回溯算法(一)


    简单深度优先搜索问题

    搜索与回溯是计算机解题中常用的算法,很多问题无法根据某种确定的计算法则来求解,可以利用搜索与回溯的技术求解。回溯是搜索算法中的一种控制策略。它的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索,如此反复进行,直至得到解或证明无解。
           如迷宫问题:进入迷宫后,先随意选择一个前进方向,一步步向前试探前进,如果碰到死胡同,说明前进方向已无路可走,这时,首先看其它方向是否还有路可走,如果有路可走,则沿该方向再向前试探;如果已无路可走,则返回一步,再看其它方向是否还有路可走;如果有路可走,则沿该方向再向前试探。按此原则不断搜索回溯再搜索,直到找到新的出路或从原路返回入口处无解为止。


    递归回溯法算法框架

     1 procedure Search(k:integer);
     2 begin
     3    for i:=1 to 算符种数 Do
     4      if  满足条件 then 
     5        begin
     6           保存结果
     7           if  到目的地 then 输出解
     8             else Search(k+1);
     9           恢复:保存结果之前的状态{回溯一步}
    10        end;
    11 end;
    递归回溯法算法框架[一]
     1 procedure Search(k:integer);
     2 begin
     3   if  到目的地 then 输出解
     4     else
     5        for i:=1 to 算符种数 Do
     6          if  满足条件 then 
     7            begin
     8               保存结果
     9               Search(k+1,参数表);
    10               恢复:保存结果之前的状态
    11           end;
    12 end;
    递归回溯法算法框架[二]
     1 procedure Search(k:integer);
     2 begin
     3   if  到目的地 then
     4      begin
     5         输出解;
     6         exit;
     7       end;
     8   for i:=1 to 算符种数 Do
     9        if  满足条件 then 
    10            begin
    11               保存结果
    12               Search(k+1,参数表);
    13               恢复:保存结果之前的状态    
    14           end;
    15 end;
    递归回溯法算法框架[三]

    【例1】求从1~4中任意挑出3个数的所有组合方案。

    1 For i:=1 to 4 do
    2   For j:=i+1 to 4 do
    3      For k:=j+1 to 4 do writeln(i,j,k);
    直接枚举
    1 Procedure DFS(k:integer);
    2   Var i:integer;
    3   Begin
    4       If k>3 then begin print(Ans);exit;end;
    5       For i:=1 to 4 do
    6         if i>Ans[k-1] then 
    7           begin Ans[k]:=I; DFS(k+1);end;
    8   End;
    深搜

    【例2】求从1~N中任意挑出m个数的所有组合方案。

    1 Procedure DFS(k:integer);
    2   Var i:integer;
    3   Begin
    4      If k>M then begin print(Ans);exit;end;
    5    For i:=1 to N do
    6       if i>Ans[k-1] then 
    7           begin Ans[k]:=I; DFS(k+1);end;
    8 End;
    深搜
     1 type arr=array[0..50] of integer;
     2 var  n,m,i:integer;
     3        ans:arr;
     4 procedure print(a:arr);
     5   begin
     6     for i:=1 to m do write(ans[i]); writeln;
     7   end;
     8 procedure dfs(k:integer);
     9   var i:integer;
    10   begin
    11     if k>m then begin print(ans);exit;end;
    12     for i:=ans[k-1]+1 to n do 
    13        begin  ans[k]:=I; dfs(k+1);  end;
    14   end;
    15 begin
    16   readln(n,m);
    17   dfs(1);
    18 end.
    完整程序

    【例3】求1,2,3三个数的全排列并输出。

    1 For i1:=1 to 3 do
    2   For i2:=1 to 3 do
    3     For i3:=1 to 3 do
    4      If (i1<>i2) and (i2<>i3)and(i3<>i1) 
    5        then writeln(i1,i2,i3);
    直接枚举
    1 For i1:=1 to 3 do
    2   For i2:=1 to 3 do
    3     If i2<>i1 then
    4       For i3:=1 to 3 do
    5        If (i2<>i3) and (i1<>i3) then
    6           writeln(i1,i2,i3);
    剪枝枚举
     1 S:=[];
     2     For i1:=1 to 3 do begin
     3       S:= S + [i1];
     4       For i2:=1 to 3 do
     5         If not (i2 in S) then begin
     6          S:= S + [i2];
     7          For i3:=1 to 3 do 
     8            If not(i3 in S) then writeln(i1,i2,i3);
     9          S:=S-[i2];
    10         End;
    11       S:=S-[i1];
    12      End;
    集合
     1 Procedure DFS(k:integer);
     2   Var i:integer;
     3   Begin
     4     If k>3 then begin print(Ans);exit;end;
     5     For i:=1 to 3 do 
     6       If not(i in S) then begin
     7         S:=S+[i];
     8         Ans[k]:=i;
     9         DFS(k+1);
    10         S:=S-[i];
    11       End; 
    12    End;
    深搜

    【例4】求从1~N中任意挑出m个数的所有排列方案。

     1 Procedure DFS(k:integer);
     2   Var i:integer;
     3   Begin
     4      If k>M then begin print(Ans);exit;end;
     5      For i:=1 to N do 
     6        If not(i in S) then begin
     7          S:=S+[i];
     8          Ans[k]:=i;
     9          DFS(k+1);
    10          S:=S-[i];
    11        End; 
    12   End;
    解法一
     1 Procedure DFS(s:se;k:integer);
     2   Var i:integer;
     3   Begin
     4      If k>M then begin print(Ans);exit;end;
     5      For i:=1 to N do 
     6        If not(i in S) then begin
     7          Ans[k]:=i;
     8          DFS(s+[i],k+1);
     9         End; 
    10   End;
    解法二

    由上边的程序代码,我们可以看出深搜算法其实也是枚举,只不过采用了递归结构使枚举的层数可以更多,代码结构更清晰罢了。

    注意:由于递归结构在pascal里使用的是系统栈,空间有限,所以尽量不要在子程序里定义太多变量甚至数组,否则递归层数多了容易造成系统栈溢出,建议能定义成全局变量的尽量用全局变量,如果使用了全局变量,注意递归后要恢复保存前的状态

    【例5】八皇后问题:要在国际象棋棋盘中放八个皇后,使任意两个皇后都不能互相吃。(提示:皇后能吃同一行、同一列、同一对角线的任意棋子)

    放置第i个(行)皇后的算法为:

     1 procedure Search(i);
     2 begin
     3   for 第i个皇后的位置=1 to 8 do;       //在本行的8列中去试
     4    if 本行本列允许放置皇后 then
     5     begin
     6      放置第i个皇后;
     7                   对放置皇后的位置进行标记;
     8      if i=8 then 输出                 //已经放完个皇后
     9         else Search(i+1);          //放置第i+1个皇后
    10      对放置皇后的位置释放标记,尝试下一个位置是否可行;
    11     end12 end
    View Code 

    【算法分析】
            显然问题的关键在于如何判定某个皇后所在的行、列、斜线上是否有别的皇后;

            可以从矩阵的特点上找到规律,如果在同一行,则行号相同;

                                                   如果在同一列上,则列号相同;

                                                   如果同在/ 斜线上的行列值之和相同;

                                                   如果同在\ 斜线上的行列值之差相同;

    从下图可验证:

    考虑每行有且仅有一个皇后,设一维数组A[1..8]表示皇后的放置:第i行皇后放在第j列,用A[i]=j来表示,即下标是行数内容是列数。例如:A[3]=5就表示第3个皇后在第3行第5列上。

          判断皇后是否安全,即检查同一列、同一对角线是否已有皇后,建立标志数组b[1..8]控制同一列只能有一个皇后,若两皇后在同一对角线上,则其行列坐标之和行列坐标之差相等,故亦可建立标志数组c[1..16]、d[-7..7]控制同一对角线上只能有一个皇后。
          如果斜线不分方向,则同一斜线上两皇后的行号之差的绝对值与列号之差的绝对值相同。在这种方式下,要表示两个皇后I和J不在同一列或斜线上的条件可以描述为:A[I]<>A[J] AND ABS(I-J)<>ABS(A[I]-A[J]){I和J分别表示两个皇后的行号}

     1 program ex5_4;
     2 var  a:array[1..8] of integer;
     3     b:array[1..8] of boolean;
     4     c:array[1..16] of boolean;
     5     d:array[-7..7] of boolean;
     6     sum:integer;
     7 procedure print;
     8 var   i:integer;
     9 begin
    10    inc(sum); writeln(' sum=',sum);    //方案数累加1
    11    for i:=1 to 8 do write(a[i]:4);    //输出一种方案
    12 end;
    13 procedure Search(t:integer);
    14 var   j:integer;
    15 begin
    16     for j:=1 to 8 do                 //每个皇后都有8位置(列)可以试放
    17        if b[j] and c[t+j] and d[t-j] then      //寻找放置皇后的位置
    18         begin                            //放置皇后,建立相应标志值
    19            a[t]:=j;                        //摆放皇后
    20         b[j]:=false;                    //宣布占领第j列
    21         c[t+j]:=false; d[t-j]:=false;   //占领两个对角线
    22       if t=8 then print                 //个皇后都放置好,输出
    23           else Search(t+1);                //继续递归放置下一个皇后
    24      b[j]:=true;              //递归返回即为回溯一步,当前皇后退出
    25      c[t+j]:=true;  d[t-j]:=true;
    26        end;
    27 end;
    28 BEGIN           
    29  fillchar(b,sizeof(b),#1);        //数组b、c、d初始化,赋初值True
    30  fillchar(c,sizeof(c),#1);
    31  fillchar(d,sizeof(d),#1);
    32  sum:=0;                     //用于统计方案数
    33  Search(1);             //从第1个皇后开始放置
    34 END.
    参考程序

    【例】任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。

    当n=7共14种拆分方法:

    7=1+1+1+1+1+1+1

    7=1+1+1+1+1+2

    7=1+1+1+1+3

    7=1+1+1+2+2

    7=1+1+1+4

    7=1+1+2+3

    7=1+1+5

    7=1+2+2+2

    7=1+2+4

    7=1+3+3

    7=1+6

    7=2+2+3

    7=2+5

    7=3+4

    total=14

     1 【参考程序】
     2 program ex5_3;
     3 var a:array[0..100]of integer;
     4       n,t,total:integer;
     5 procedure print(t:integer);
     6 var i:integer;
     7 begin
     8   write(n,'=');
     9   for i:=1 to t-1 do  write(a[i],'+');   
    10       //输出一种拆分方案
    11   writeln(a[t]);
    12   total:=total+1;                    
    13       //方案数累加1
    14 end;
    15 procedure Search(s,t:integer);
    16 var i:integer;
    17 begin
    18    for i:=1 to s do
    19      if (a[t-1]<=i)and(i<n) then       
    20    //当前数i要大于等于前1位数,
    21       且不过n
    22 begin
    23         a[t]:=i;                     
    24         //保存当前拆分的数i
    25         s:=s-a[t];                   
    26         //s减去数i, s的值将继续拆分
    27         if s=0 then print(t)          
    28         //当s=0时,拆分结束输出结果
    29           else Search(s,t+1);    
    30         //当s>0时,继续递归
    31         s:=s+a[t];                
    32         //回溯:加上拆分的数,
    33             以便产分所有可能的拆分
    34       end;
    35 end;
    36 BEGIN
    37   readln(n);
    38   Search(n,1);                     
    39       //将要拆分的数n传递给s
    40   writeln('total=',total);             
    41      //输出拆分的方案数
    42   readln;
    43 END.
  • 相关阅读:
    win7常用快捷键
    java中构造代码块、方法调用顺序问题
    eclipse项目改为maven项目导致svn无法比较历史数据的解决办法
    linux配置Anaconda python集成环境
    DataFrame对行列的基本操作实战
    驱动:电阻屏触摸芯片NS2009
    读书笔记:代码大全(第二版)
    资料:磁角度传感器芯片
    经验:FatFs文件系统实时写入
    笔记:CAN收发器-TJA1051T与TJA1051T/3调试总结
  • 原文地址:https://www.cnblogs.com/vacation/p/4856083.html
Copyright © 2020-2023  润新知