深度优先与广度优先

文章转载自:http://blog.csdn.net/a45872055555/article/details/37543795


今天做了道题目,《手机键盘输入》当按下23时,输出[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]。

其实说白了,也就是全排列问题,将2代表的abc,和3代表的def输出组合的字符。

我是按照普通方法,递归来写的,觉得这道题目也只是考验编程,考验递归的。没太多考虑,当上网看别人都提到DFS(深度优先算法)后,才意识到,是有章法可循的。以前只以为深度优先和广度优先只是在图的遍历的时候才能用到,想不到这些也只是工具,看你怎么去应用到你的算法里面。


好了,不多说了,基于上述原因,参考《数据结构》和《算法导论》将广度优先和深度优先总结一下。


这两个算法的共同点,都是要有标记!数据结构采用的是一个数组来标记每一个结点是否被遍历;算法导论是在结点中附设一个颜色值,来表示遍历的程度。其实质是一样的,都需要借助于标记,来实现。但在二叉树的遍历中就不需要了,因为不会形成回路。


一:深度优先算法

方案一:“数据结构”

其实这是树的先根遍历的扩展。



通过图(b)我们可以看到,v1-v4-v8-v5-v3-v6-v7。

只要遍历到某一个结点,有子节点就先遍历子节点,当“子树”遍历完了以后,再遍历另一个“子树”,只是由于存在回路,所以遍历另一个子树的时候,会访问已经访问过的点, 所以就需要标记来判断。

代码如下:

  1. Boolean visited[MAX];           //设置标志数组  
  2. Status (*VisitFunc)(int v);     //全局变量函数,方便在DFS中调用DFSTraverse中传进的参数  
  3.   
  4. void DFSTraverse(Graph G,Status (*Visit)(int v))  
  5. {  
  6.     VisitFunc = Visit;  
  7.     for(v = 0;v < G.vexnum;++v)     //初始化,将标志数组全设为FALSE  
  8.         visited[v] = FALSE;  
  9.     for(v = 0;v < G.vexnum;++v)     //对所有的结点进行遍历,找到切入点,就调用DFS进行深度优先遍历  
  10.         if(!visited[v])  
  11.             DFS(G,v);  
  12. }  
  13.   
  14. void DFS(Graph G,int v)  
  15. {  
  16.     visited[v] = TRUE;  //给第v个结点标记,并访问该结点。  
  17.     VisitFunc(v);  
  18.     for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)) //对v的所有子节点w进行深度优先遍历  
  19.         if(!visited[w])  
  20.             DFS(G,w);  
  21. }  
Boolean visited[MAX];           //设置标志数组
Status (*VisitFunc)(int v);     //全局变量函数,方便在DFS中调用DFSTraverse中传进的参数

void DFSTraverse(Graph G,Status (*Visit)(int v))
{
    VisitFunc = Visit;
    for(v = 0;v < G.vexnum;++v)     //初始化,将标志数组全设为FALSE
        visited[v] = FALSE;
    for(v = 0;v < G.vexnum;++v)     //对所有的结点进行遍历,找到切入点,就调用DFS进行深度优先遍历
        if(!visited[v])
            DFS(G,v);
}

void DFS(Graph G,int v)
{
    visited[v] = TRUE;  //给第v个结点标记,并访问该结点。
    VisitFunc(v);
    for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)) //对v的所有子节点w进行深度优先遍历
        if(!visited[w])
            DFS(G,w);
}

说明:

1:这个题目写的很简化,在visited[v]中v是一个确定的数值,表示数组下标。但在VisitFunc(v)中,v为结点。虽然程序写法简化,但更能突出我们要解决的问题。

2:其实DFS和二叉树的先序遍历很相像:

  1. visit(p);  
  2. PreOrderTraverse(p->left);  
  3. PreOrderTraverse(p->right);  
visit(p);
PreOrderTraverse(p->left);
PreOrderTraverse(p->right);

其实就是将二叉树的确定性,转化成图子节点的不确定性,通过for循环来实现。

3:DFS可以应用在树的先序遍历当中。


方案二:“算法导论”

1:遍历过程

只要可能,就在图中尽量深入,总是对最近发现的结点v的出发边进行探索,直到该结点的所有出发边都被发现为止。然后回溯到v的父节点(可能有多个,但之前遍历过程中,从哪个点过来的,就是v的父节点),然后搜索该前驱结点的出发边。知道从源节点可以达到的所有的出发边都遍历完。如果在图中还有没被遍历的结点, 任选一个,重复上述过程。直到所有的结点遍历完为止。

2:当发现一个结点v时,记录其前驱结点u,并设置父节点指针v.π = u; 同样设置三种颜色,白色表示尚未被遍历的结点,灰色表示已经被遍历,但其子节点还没有被遍历完全,黑色表示该结点以及所有的子节点都被遍历完全,要返回到父节点。

3:给每个结点设置时间戳。v.d记录第一次被发现的时间,v.f记录对v扫描完成时的时间戳。其中time为全局变量


代码:

  1. DFS(G)  
  2. {  
  3.     for(u = 0;u < G.vexnum;++u){    //初始化所有的结点信息,父指针设为空,颜色全为白色  
  4.         u.color = WHITE;  
  5.         u.π = NULL;  
  6.     }  
  7.     time = 0;  
  8.     for(u = 0;u < G.vexnum;++u)     //开始遍历所有的结点,以防存在多个不相接的图  
  9.         if(u.color == WHITE)  
  10.             DFS_VISIT(G,u);  
  11. }  
  12.   
  13. DFS_VISIT(G,u){  
  14.     time = time + 1;    //每次赋值前都要增加1,time为全局变量  
  15.     u.d = time;  
  16.     u.color = GRAY;     //当访问了该结点,就将该节点设为灰色,接下来遍历其子节点。其实不要该灰色指示也行  
  17.     for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)) //处理子节点  
  18.         if(w.color == WHITE){  
  19.             w.π = u;  
  20.             DFS_VISIT(G,w);  
  21.         }  
  22.     u.color = BLACK;  
  23.     time = time + 1;  
  24.     u.f = time;  
  25. }  
DFS(G)
{
    for(u = 0;u < G.vexnum;++u){    //初始化所有的结点信息,父指针设为空,颜色全为白色
        u.color = WHITE;
        u.π = NULL;
    }
    time = 0;
    for(u = 0;u < G.vexnum;++u)     //开始遍历所有的结点,以防存在多个不相接的图
        if(u.color == WHITE)
            DFS_VISIT(G,u);
}

DFS_VISIT(G,u){
    time = time + 1;    //每次赋值前都要增加1,time为全局变量
    u.d = time;
    u.color = GRAY;     //当访问了该结点,就将该节点设为灰色,接下来遍历其子节点。其实不要该灰色指示也行
    for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)) //处理子节点
        if(w.color == WHITE){
            w.π = u;
            DFS_VISIT(G,w);
        }
    u.color = BLACK;
    time = time + 1;
    u.f = time;
}


生成的图



性质:

1、其生成的前驱制图G形成一个由多棵树所构成的森林,这是因为深度优先树的结构与DFS_VISIT的递归调用结构完全对应。

2、结点的发现时间和完成时间具有括号化结构。也就是只要两个结点的时间有重叠,毕竟一个结点的时间段包含在另一个结点的时间段内。



二:广度优先搜索

类似于树的按层次遍历的过程。

方案一:“数据结构”

先访问根结点,然后访问与跟相连的所有结点v1-vn,然后访问与v1-vn直接相连的所有结点(除去已经访问过的结点)。这就要用队列来时间:先访问某结点,然后将该结点如队列。从队列出结点,然后依次访问该结点的所有子节点(除去已经访问过的结点),并将这些刚刚访问结点入队列(这样将提前访问过的结点就不用入队列了,其子节点已经提前放入队列当中)。


访问过程是:v1-v2-v3-v4-v5-v6-v7-v8。


  1. void BFSTraverse(Graph G,Status(*Visit)(int v))  
  2. {  
  3.     for(u = 0;u < G.vexnum;++u) //初始化  
  4.         visited[u] = FALSE;  
  5.     InitQueue(Q);  
  6.     for(v = 0;v < G.vexnum;++v){    //遍历所有的结点  
  7.         if(!visited[v]){  
  8.             visited[v] = TRUE;  
  9.             Visit(v);   //先访问再入队列  
  10.             EnQueue(Q,v);  
  11.             while(!QueueEmpty(Q)){  
  12.                 DeQueue(Q,u);  
  13.                 for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)){  
  14.                     if(!visited[w]){  
  15.                         visited[w] = TRUE;  
  16.                         Visit(w);  
  17.                         EnQueue(Q,w);  
  18.                     }  
  19.                 }  
  20.             }  
  21.         }  
  22.     }  
  23. }  
void BFSTraverse(Graph G,Status(*Visit)(int v))
{
    for(u = 0;u < G.vexnum;++u) //初始化
        visited[u] = FALSE;
    InitQueue(Q);
    for(v = 0;v < G.vexnum;++v){    //遍历所有的结点
        if(!visited[v]){
            visited[v] = TRUE;
            Visit(v);   //先访问再入队列
            EnQueue(Q,v);
            while(!QueueEmpty(Q)){
                DeQueue(Q,u);
                for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)){
                    if(!visited[w]){
                        visited[w] = TRUE;
                        Visit(w);
                        EnQueue(Q,w);
                    }
                }
            }
        }
    }
}

方案二:“算法导论”

该算法可以得到最短路径。

  1. BFS(G,s)  
  2. {  
  3.     for(u = 0;u < G.vexnum;++u){  
  4.         u.color = WHITE;  
  5.         u.d = 10000;  
  6.         u.π = NULL;  
  7.     }  
  8.     s.color = GRAY;  
  9.     s.d = 0;  
  10.     s.π = NULL;  
  11.     Q = 空  
  12.     EnQueue(Q,s);  
  13.     while(!IsEmpty(Q)){  
  14.         u = DeQueue(Q);  
  15.         for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)){  
  16.             if(w.color == WHITE){  
  17.                 w.color = GRAY;  
  18.                 w.d = u.d + 1;  
  19.                 w.π = u;  
  20.                 EnQueue(Q,w);  
  21.             }  
  22.         }  
  23.         u.color = BLACK;  
  24.     }  
  25. }  
BFS(G,s)
{
    for(u = 0;u < G.vexnum;++u){
        u.color = WHITE;
        u.d = 10000;
        u.π = NULL;
    }
    s.color = GRAY;
    s.d = 0;
    s.π = NULL;
    Q = 空
    EnQueue(Q,s);
    while(!IsEmpty(Q)){
        u = DeQueue(Q);
        for(w = FirstAdjVex(G,v); w >= 0;w = NextAdjVex(G,v,w)){
            if(w.color == WHITE){
                w.color = GRAY;
                w.d = u.d + 1;
                w.π = u;
                EnQueue(Q,w);
            }
        }
        u.color = BLACK;
    }
}

遍历后的图形:



因为我们有设置父节点指针,当通过广度优先遍历之后,我们从要找的叶节点反向遍历到根结点,就能得到最短的路径。

  1. PRINT_PATH(G,s,v)  
  2. {  
  3.     if(v == s)  
  4.         print s;  
  5.     else if(v.π == NULL){  
  6.         print ”no path form”s“to”v  
  7.     }else{  
  8.         PRINT_PATH(G,s,v.π)  
  9.         print v;  
  10.     }  
  11. }  
PRINT_PATH(G,s,v)
{
    if(v == s)
        print s;
    else if(v.π == NULL){
        print "no path form"s"to"v
    }else{
        PRINT_PATH(G,s,v.π)
        print v;
    }
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值