深度与广度 总结
1.深度优先搜索(回溯法)
[算法思路]深度优先搜索(DFS,Depth-First Search)是搜索的手段之一.它从某个状态开始,不断地转移状态直到无法转移,然后回退到前一步状态,继续转移到其他状态,如此不断重复,直到找到最终的解.根据深度优先搜索的特点,采用递归函数(隐式地利用了栈进行计算)实现比较简单.
[算法描述]void dfs(int step)
{
判断边界
尝试每一种可能for(i=1;i<=n;i++)
{
继续下一步 dfs(step+1);
}
返回
}
[算法效率]
深度优先搜索从最开始的状态出发,遍历所有可以到达的状态.由此可以对所有的状态进行操作,或列举出所有的状态.作为搜索算法的一种,DFS对于寻找一个解的NP(包括NPC)问题作用很大.
但是,搜索算法毕竟是时间复杂度是 O(n!) 的阶乘级算法,它的效率比较低,在数据规模变大时,这种算法就显得力不从心了. 关于深度优先搜索的效率问题,有多种解决方法.最具有通用性的是剪枝(prunning),也就是去除没有用的搜索分支. 有可行性剪枝和最优性剪枝两种.此外,对于很多问题,可以把搜索与动态规划(DP,dynamic programming)、完备匹配(匈牙利算法)等高效算法结合.2.宽度优先搜索(分支限界法)
[算法思路]
宽度优先搜索(BFS,Breadth-First Search)也是搜索的手段之一.他与深度优先搜索类似, 从某个状态出发搜索所有可以到达的状态. 根据宽度优先搜索的特点,采用队列实现比较简单.
[算法描述]
/*遍历连通图*/
void BFS(Graph G,int v)
{
/*按广度优先非递归遍历连通图G*/
cout<<v;vistited[v]=true; /*访问第v个顶点,并置访问标志数组相应分量值为true*/
InitQueue(Q); /*辅助队列Q初始化,置空*/
EnQueue(Q,v); /*v入队*/
while(!QueueEmpty(Q)) /*队列非空*/
{
DeQueue(Q,u); /*队头元素出队并置为u*/
for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w))
{ /*依次检查u的所有邻接点w,FirstAdjVex(G,u)表示u的第一个邻接点*/
/*NextAdjVex(G,u,w)表示u相对于w的下一个邻接点,w>=0表示存在邻接点*/
if(!vistited[w]) /*w为u的尚未访问的邻接顶点*/
{
cout<<w; vistited[w]=true; /*访问w,并置访问标志数组相应分量值为true*/
EnQueue(Q,w); /*w进队*/
}
}
}
}
[算法效率]
与深度优先不同之处在与搜索的顺序,宽度优先搜索总是先搜索距离初始状态近的状态.也就是说,它是按照开始状态->只需1次转移就可以到达的所有状态->只需2次转移就可以到达的所有状态->.....这样的顺序进行搜索.对于同一个状态,宽度优先搜索只经过一次,因此复杂度为
O(状态数*转移的方式).很容易地用来求最短路径、最少操作之类问题的答案.
广度搜索的判断重复如果直接判断十分耗时,我们一般借助哈希表来优化时间复杂度.3.Death-Breadth总结
宽度优先搜索与深度优先搜索一样,都会生成所有能够遍历到的状态,因此需要对所有状态进行处理时使用宽度优先也是可以的.但是递归函数可以很简短地编写,而且状态的管理也更简单,所以大多数情况下还是用深度优先搜索实现.反之,在求取最短路时深度优先搜索需要反复经过同样的状态,所以还是使用宽度优先搜索比较好.
宽度优先搜索会把状态逐个加入队列,因此通常需要与状态数成正比的内存空间.反之,深度优先搜索是与最大的递归深度成正比的.一般与状态数相比,递归的深度并不会太大,所以可以认为深度优先搜索更加节省内存.