生成图-深度优先搜索/宽度优先搜索

来源:互联网 发布:js 浮点数计算误差 编辑:程序博客网 时间:2024/06/09 23:54

问题提出:

        考虑如下图所示的简单图所表示的缅因州的道路系统。在冬天里保持道路通路通畅的唯一方式就是经常扫雪。高速公路部分希望只扫尽可能少的道路上的雪,而确保总是存在连接任何两个乡镇的干净道路。如何才能做到这点呢?


        最简单的方式就是一个哈密顿图,形成一个回路,这当然可以。假设一个回路有n个点,则会有n条边;但对于树,n个点对应n-1条边,所以我可以去寻找一颗树即可。有人说那我们不形成一个圈不就是n-1了么?同学,一条线也是树的一种。将问题剥离成一个数学问题就是,如何从一个图中找到树。树有什么特征呢?这就要问什么叫做树了,树就是每对顶点之间存在唯一简单通路的无向图。所以针对这个图中的每对顶点之间也应该是有通路的,即简单连通图。这里有两种方法,即深度优先搜索和宽度优先搜索。

1.深度优先搜索


       此算法的思路就是先找一个主线,然后回头找到每一个点的分支。这就是回溯法,所谓回溯法,就是每一点都有相同的特征,但状态不一样。当从头遍历到尾后,要原路返回,每次返回的那个点状态不一样。仔细想想,这个递归的思想是一致的,用for i=1:N表示遍历每一种状态,用if判断每一种状态所要执行的动作,每次执行的动作模式是一样,即递归,故我们就有了如下代码:
//d为图的邻近矩阵,p为所生成树的邻近矩阵,//n为图中点的个数,w为要遍历的顶点void visit(int **d,int **p,int *T,int n,int w){cout<<w<<" ";//输出遍历的点T[w]=1;//已经遍历的点,要加进新增的树中for(int i=0;i<n;i++){//遍历每个点的所有状态,即分支if(d[w][i]==1&&T[i]!=1){//如果有通路,且点不在树中,则记录边,同时访问该分支p[w][i]=1;//记录边visit(d,p,T,n,i);//访问该分支}}}void DFS(int **d,int **p,int n){int *T=new int[n];//建立新建树的顶点集memset(T,0,sizeof(int)*n);visit(d,p,T,n,0);//第一个作为初始值,即从第一个点开始遍历}

即结果为:

2.宽度优先搜索
        宽度优先搜索(广度优先搜索)简单的说,波的传播,一个点开始振动,会带动周边所有的点振动,然后周边的点也以同样的方式扩散。但宽度优先搜索是不往回传,即只向没有振动的点扩散。所以其思想就是:
从图的顶点中任意地选择一个根,然后添加与这个顶点相关联的所有边。在这个阶段所添加的新顶点成为生成树在1层上的顶点。下一步,按顺序访问1层上的每个顶点,只要不产生简单回路,就将与这个顶点相关联的每条边添加到树里。这样就产生了树在2层上的顶点。如此来,直到已经添加了所有顶点。从以上分析,很容易可以看出,这并非回溯可以做到的,所以得按照其本身思路实现。
        那其本身的思路如何去实现呢?我们从一个点扩散开来,把周边的点记录下来,然后再一个点一个点的重复此工作。所以我们需要空间保存所找到的周边的点,然后等待处理,同时把已经找到的点加入到树当中,所以还要一个空间保存树的节点。这样,我们就可以设T为树,L为所找到的周边的点。每次取出L的第一个点来进行处理,同时处理过的点给删除掉;处理过程中,每找到一个邻点,则加入到L的末尾上。这实际上就是队列,所以可以用队列来保存L。当队列中的尚未处理的点没了,则结束了。下面的代码中,是有点问题的,这里并没有采用队列,只是用数组索引,目的是为了免去在L搜索第i个点是否在L中存在,就直接将索引的值用1代替,1代表存在。这样其结果就是处理的点是按照索引由小到大的顺序处理的,不太符合其思想。其代码如下:
void BFS(int **d,int **p,int n){int v;int *T=new int[n];//保存已经生成树的节点int *L=new int[n];//保存每一个树顶点的邻居,memset(T,0,sizeof(int)*n);memset(L,0,sizeof(int)*n);int iL=1;//L的初始长度为1T[0]=1;//只包含顶点0的树L[0]=1;//把初始点0放入尚未处理顶点的表L中while(iL!=0){//如果没有要处理的点,即尚未处理的点没了,则停止v=0;while(v<n&&!L[v++]);//寻找尚未处理顶点,由于索引结果多了个1,后面要减掉L[--v]=0;iL--;//删除L中所寻找的点,表示即将寻找此点的所有邻点cout<<v<<" ";//输出即将处理的点for(int i=0;i<n;i++){if(d[v][i]==1&&T[i]!=1&&L[i]!=1){//如果v与i之间相连,且v不在树T和尚未处理集合L中,则记录下来,作尚未处理的点p[v][i]=1;//记录边T[i]=1;//加入到树中L[i]=1;//加入尚未处理点集合L中,等待处理iL++;//L长度要加1}}}}
运行结果:

实际方案结果:

        现在来分析两者的时间复杂度。两种算法中每个顶点都要和其它的顶点比较一遍,即O(n^2),但要处理的次数是边数e,即n-1,所以两种算法的时间复杂度都为O(n^2)。但两者又有区别,深度优先搜索使用递归,即栈,而宽度搜索使用队列。

3.网络解释:

        下面是网络中的对这两种方法的解释:

3.1

深度优先搜索与广度优先搜索算法有何区别呢?
  通常深度优先搜索法不全部保留结点,扩展完的结点从数据库中弹出删去,这样,一般在数据库中存储的结点数就是深度值,因此它占用空间较少。所以,当搜索树的结点较多,用其它方法易产生内存溢出时,深度优先搜索不失为一种有效的求解方法。
  广度优先搜索算法,一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此,程序设计中,必须考虑溢出和节省内存空间的问题。但广度优先搜索法一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索要快些

3.2

在说两种算法之前先说说什么叫“搜索”:
可能很多人对搜索的想法有点不对,很多人认为搜索是对已知的一棵树或者是已知的图进行搜索,所以我们常常把搜索和遍历给搞混了,但是其实搜索针对的并不是已知的,这并不代表搜索不能用于已知的,搜索一般用于未知的树,或者未知的图,而我们仅仅是知道这个树或图的产生规则。这个时候才会产生深度优先搜索和广度优先搜索。

然后说一下深度优先搜索和广度优先搜索的区别以及适用范围:

广度优先搜索:广度优先搜索是按照树的层次进行的搜索,如果此层没有搜索完成的情况下不会进行下一层的搜索。

深度优先搜索:深度优先搜索是按照树的深度进行搜索的,所以又叫纵向搜索,在每一层只扩展一个节点,直到为树的规定深度或叶子节点为止。这个便称为深度优先搜索。

我先来说说两种算法的不同点。广度优先搜索,适用于所有情况下的搜索,但是深度优先搜索不一定能适用于所有情况下的搜索。因为由于一个有解的问题树可能含有无穷分枝,深度优先搜索如果误入无穷分枝(即深度无限),则不可能找到目标节点。所以,深度优先搜索策略是不完备的。

适用范围:这点很重要,因为知道两者的适用范围对于编程人员很有好处,至少可以少走弯路。(这些都是开个人观点,有缺少的欢迎补充)

广度优先搜索适用范围:在未知树深度情况下,用这种算法很保险和安全。在树体系相对小不庞大的时候,广度优先也会更好些。

深度优先搜索适用范围:刚才说了深度优先搜索又自己的缺陷,但是并不代表深度优先搜索没有自己的价值。在树深度已知情况下,并且树体系相当庞大时,深度优先搜索往往会比广度优先搜索优秀,因为比如8*8的马踏棋盘中,如果用广度搜索,必须要记录所有节点的信息,这个存储量一般电脑是达不到的。然而如果用深度优先搜索的时候却能在一个棋盘被判定出来后释放之前的节点内存。

当让具体情况还是根据具体的实际问题而定,并没有哪种绝对的好。所以,理解这两种算法的本质是关键。

最后我说说关于找最优解的问题,这种问题如果不依靠其他的辅助算法来说,其实对于广度优先搜索和深度优先搜索来说是一样的,说白了找最优解就是个遍历过程,所以没有哪种算法找最优解更好。但是如果有辅助的启发式算法或者别的算法就另当别论了。

3.3

深度优先搜索法的特点是:
(1)从上面几个实例看出,可以用深度优先搜索的方法处理的题目是各种各样的。有的搜索深度是已知和固定的,如例题4—4、4—5、4—6;有的是未知的,如例题4—7、例题4—8,有的搜索深度是有限制的,但达到目标的深度是不定的。但也看到,无论问题的内容和性质以及求解要求如何不同,但它们的程序结构都是相同的,即都是深度优先算法(一)和深度优先算法(二)中描述的算法结构,不相同的仅仅是存储节点数据结构和产生规则以及输出要求。
(2)深度优先搜索法有递归以及非递归两种设计方法。一般的,当搜索深度较小、问题递归形式较明显时,用递归方法设计的较好,它可以使得程序结构更简捷易懂。但当搜索深度较大时,如例题4—5、4—6,当数据量较大时,由于系统堆栈容量的限制,递归易产生溢出,用非递归方法设计比较好。
(3)深度优先搜索方法有广义和狭义两种理解。广义的理解是,只要最新产生的节点(即深度最大的节点)先进行扩展的方法,就称为深度优先搜索方法。在这种理解情况下,深度优先搜索算法有全部保留和不全部保留产生的节点的两种情况。而狭义的理解是,仅仅只保留全部产生节点的算法。本书取前一种广义的理解。不保留全部节点的算法属于一般的回溯算法范畴。保留全部节点的算法,实际上是在数据库中产生一个节点之间的搜索树,因此也属于图搜索算法的范畴。
(4)不保留全部节点的深度优先搜索法,由于把扩展完的节点从数据库中弹出删除,这样,•一般在数据库中存储的节点数就是深度值,因此它占用的空间较少。
所以,当搜索树的节点较多,用其他方法易产生内存溢出时,深度优先搜索法不失为一种有效的算法。
(5)从输出结果可看出,深度优先找到的第一个解并不一定是最优解。例如例题4—8得最优解COST=13,但第一个解却是COST=17。
如果要求出最优解的话,一种方法将是后面要介绍到的动态规划法,另一种方法是修改原算法:把原输出过程的地方改为记录过程,即记录达到当前目标的路径和相应的路程值,并与前面已记录的值进行比较,保留其中最优的,等全部搜索完成后,才把保留的最优解输出。
广度优先搜索法的显著特点是:
(1)在产生新的子节点时,深度越小的节点越先得到扩展,即先产生它的子节点。为使算法便于实现,存放节点的数据库一般用队列的结构。
(2)无论问题性质如何不同,利用广度优先搜索法解题的基本算法是相同的。但数据库中每一节点内容、产生式规则,根据不同的问题,有不同的内容和结构。就是同一问题也可以有不同的表示方法。
(3)当节点到根节点的费用(有的数称为耗散值)和节点的深度成正比时,特别是当每一节点到根节点的费用等于深度时,用广度优先法得到的解是最优解。但如果不成正比,则得到的解不一定是最优解。这一类问题要求出最优解,一种方法是使用后面要介绍的其他方法求解,另外一种方法是改进前面深度(或广度)优先搜索方法:找到一个目标后,不是立即退出,而是记录下目标节点路径和费用。如果有多个目标节点,就加以比较,留下较优的节点。把所有可能的路径都搜索完后,才输出记录的最有路径。
(4)广度优先搜索算法,一般需存储产生的所有节点,占的存储空间要比深度优先大的多。因此程序设计中,必须考虑溢出和节省内存空间的问题。
(5)比较深度优先和广度优先两种搜索法,广度优先搜索法一般无回溯操作,即人栈和出栈的操作,所以运行速度比深度优先搜索法要快些。
总之,一般情况下,深度优先搜索法占内存少但速度较慢,广度优先搜索法占内存较多但速度较快,在距离与深度成正比的情况下能较快地求出最优解。因此在选择用那种算法时,要综合考虑,决定取舍。
0 0
原创粉丝点击