搜索与回溯算法介绍
一、概述:
计算机常用算法大致有两大类:一类叫蛮干算法,一类叫贪心算法。
前者常使用的手段就是搜索,对全部解空间进行地毯式搜索,直到找到指定解或最优解。
后者在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解。
二、搜索与回溯:
这里着重介绍搜索与回溯。
当很多问题无法根据某种确定的计算法则来求解时可以利用搜索与回溯的技术求解。
回溯是搜索算法中既带有系统性又带有跳跃性的一种控制策略。
它的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索。
在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,然后继续向前探索,如此反复进行,直至得到解或证明无解。
如迷宫问题:进入迷宫后,先随意选择一个前进方向,一步步向前试探前进。
如果碰到死胡同,说明前进方向已无路可走,这时,首先看其它方向是否还有路可走,如果有路可走,则沿该方向再向前试探;如果已无路可走,则返回一步,再看其它方向是否还有路可走;如果有路可走,则沿该方向再向前试探。
按此原则不断搜索回溯再搜索,直到找到新的出路或从原路返回入口处无解为止。
【建立解空间】
问题的解应该如何描述,如何建立呢?问题的解空间:应用回溯法解问题时,首先应明确定义问题的解空间。
问题的解空间应到少包含问题的一个(最优)解。
借助图论的思想,可以用图来描述,图的定义为G,由顶点集和边集构成,顶点即实实在在的数据、对象,而边可以抽象为关系,即顶点间的关系,这种关系不一定非要在数据结构上表现出来,用数据结构的语言来描述,如果关系是一对一,则为线性表,如果关系是一对多,则为树,如果关系是多对多,则为图,如果完全没有关系,则为集合。
但在数据结构中这种关系不一定非要在数据的存储性质上一开始就表现出来,譬如,你可以用一个数组表示一个线性表,也可以表示完全二叉树,同样也可以用邻接表表示一个图,对于关系的描述不是数据结构本身的描述,而是算法的描述,正如数据结构是离不开特定的算法一样,不可分开单独而谈。
确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。
这个开始结点就成为一个活结点,同时也成为当前的扩展结点。
在当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点就成为一个新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向
纵深方向移动,则当前扩展结点就成为死结点。
换句话说,这个结点不再是一个活结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
运用回溯法解题通常包含以下三个步骤:(1)针对所给问题,定义问题的解空间;(2)确定易于搜索的解空间结构;(3)以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索;
【深度优先搜索】
(Depth First Search)是用栈的机制对图的顶点进行深度优先的搜索。
简易流程如下:
DFS(V0(起始顶点)) 访问V0 for(v=V0的第一个子结点到最后一个子结点(边所对的顶点))如果v未被访问,DFS(v);
搜索过程是先往结点深处搜索,一旦有子结点未访问就向下遍历。
这样的方法类似回溯算法,先往下试探,如果不行出退回(回溯)。
【递归回溯】
由于回溯法是对解空间的深度优先搜索,因此在一般情况下可用递归函数来实现回溯法如下:
procedure try(i:integer); var begin if i>n then 输出结
果 else for j:=下界 to 上界
do begin x[i]:=h[j]; if 可行{满足限界函数和约束条件} then begin 置值;
try(i+1); end; end; end;
说明: i是递归深度; n是深度控制,即解空间树的的高度;可行性判断有两方面的内容:不满约束条件则剪去相应子树;若限界函数越界,也剪去相应子树;两者均满足则进入下一层;
【回溯经典例题】
8皇后问题:在国际象棋地图上(8×8)放上8个皇后,使任意两个皇后都不在同一行或同一列或同一斜行,找出所有解。
由此可以推广为n皇后问题[即:在一个n×n的棋盘上,放置n个不能互相捕捉的“皇后”的所有布局]。
问题分析:每一个皇后的位置可以认为是一个顶点,而皇后之间不在同一行或同一列或同一斜行的性质认为是顶点之间的关系,我们可以用回溯试探的方法考虑:为找到一
个解,必须从空布局开始,每放置一个皇后要检查布局是否合理。
在合理的情况下,试探找下一个皇后的位置;如果布局不合理,就改变布局。
重复检查、试探或检查、改变位置,直到找到最后一个皇后的合理位置,这时就找到一个合理的布局。
重复以上过程直到无法再改变皇后位置为止,就可找到所有合理的布局。
用数组存储:int pos[8]; pos[0]表示第一个皇后的位置(0,1,...7)依次类推。
流程:
dfs(c)//c从0开始 for(v=0;v<8;++v) 如果pos[c]:=v满足条件,
dfs(c+1); 退回之后pos[c]:=0;
这跟书上的回溯算法不太一样,因为是采用深搜的方法写的,其实思想是一致的,要仔细体会。
附N皇后C语言代码:
/*p118.4*/ /*N Queen Problem*/ #include<stdio.h> #define MAX 100 int s[MAX],m1[MAX],m2[MAX],m3[MAX]; int n; long count=0; void
output_solution() { int
i,j; printf("No.%d\n",count); for(i=0;i<n;++i) { for(j=0;j<s[i];++j) printf("
"); printf("Q\n"); } printf("---------------\n" ); return; } void dfs(int c) { int
i; if(c==n) { count++;
output_solution(); } else { for(i=0;i<n;+ +i) { if(m1[i]==0 && m2[c+i]==0 &&
m3[n+i-c-1]==0) { s[c]=i; m1[i]=1; m2[c+i]=1; m3 [n+i-c-1]=1; dfs(c+1); m1[i]=0; m2[c+i]=0; m3[n+i-c-1]=0; } } } return; } int main()
{ scanf("%d",&n); dfs(0); printf("total:%d\n",count); return 0; }
IOCCC(全称:The International Obfuscated C Code Contest,即国际模糊C 代码大赛)是一个关于C程序的知名国际大赛。
在1991年获得最佳小程序的作品就是一个8皇后问题的程序。
现在就让大家见识一下。
但是这种“难于理解”的程序书写方式不是我们推荐的。
int v, i, j, k, l, s, a[99]; int main() { for(scanf("%d",&s); *a-s; v=a[j*=v]-a[i], k=i<s,
j+= (v=j<s&&(!k&&!!printf(2+"\n\n%c"-(!l<<!j),"
#Q"[l^v?( l^j)&1:2])&&++l||a[j]<s&&v&&v-i+j&&v+i-j&&v+i-j))&&!( l%=s),v||(i==j?a[i+=k]=0:++a[i])>=s*k&&++a[--i]) ; }。