第22章:图的基本算法—广度优先搜索和深度优先搜索

一:广度优先搜索

给定图G=(V,E)和一个可以识别的源结点s,广度优先搜索对图G的边进行系统系的探索来发现可以从源结点s到达的所有结点。该算法能够计算从源节点s到每个可到达的结点的距离(最少的边数),同时生成一棵“广度优先搜索树”。该树以源结点s为根结点,包含所有可以从s到达的结点。对于每个从源结点s可以到达的结点v,在广度优先搜索树里从结点s到结点v的简单路径对应的就是图G中从结点s到结点v的“最短路径”,即包含最少边数的路径。该算法既可以用于有向图,也可以用于无向图(暗示着该算法可以用于含有环的图)。

广度优先搜索之所以如此得名是因为该算法始终是将已发现结点和未发现结点之间的边界,沿其广度方向向外扩展,也就是说,算法需要在发现所有距离源节点s为k的所有结点之后,才会发现距离源结点s为k+1的其它结点。

代码如下:

//用的是邻接链表表示图,图的顶点用0,1,2...这些数字表示。
//下面代码用广度优先搜索算法计算源顶点到图中各顶点的最短距离(即最少边数)以及对应的路径。
vector<int> bfs(const vector<list<int>>& graph, int source)
{
        enum { WHITE,GRAY,BLACK};

        vector<int> color(graph.size(),WHITE);
        vector<int> distance(graph.size(),INT_MAX); //存放的是源顶点到图中各个顶点的最少>边数;
        //比如distance[3]=2表明源顶点vertex到顶点3的最少边数是2
        vector<int> pred(graph.size(),-1); //存放的是对应顶点的前驱结点predecessor;
        //比如pred[3]=2表明的是顶点3的前驱结点是顶点2;

        color[source]=GRAY;
        distance[source]=0;
        pred[source]=-1;

        queue<int> Q;
        Q.push(source);

        while(!Q.empty()){
                int vertex=Q.front();
                Q.pop();

                for(auto iter=graph[vertex].begin();iter!=graph[vertex].end();++iter)
                {
                        int tmp=*iter;
                        if(color[tmp]==WHITE){
                                color[tmp]=GRAY;
                                distance[tmp]=distance[vertex]+1;
                                pred[tmp]=vertex;
                                Q.push(tmp);
                        }
                }

                color[vertex]=BLACK;
        }

        return pred;
}

//输出源顶点s到图中某一个特定顶点v的含有最少边数的路径
void print_path(int source,int v,const vector<int>& pred)
{
        if(v==source)
                cout<<source;
        else if(pred[v]==-1)
                cout<<"no path from "<<source<<" to "<< v<<endl;
        else{
                print_path(source,pred[v],pred);
                cout<<" to "<<v;
        }
}

二:深度优先搜索

深度优先搜索正如其名字所表明的,只要可能,就在图中尽量“深入”。深度优先搜索总是对最近才发现的结点v的出发边进行搜索,直到该结点的所有出发边都被发现为止。一旦结点v的所有出发边都被发现,搜索则回溯到v的前驱结点来搜索该前驱结点的出发边,该过程一直持续到从源结点可以到达的所有结点都被发现为止。如果存在尚未被发现的结点,则深度优先搜索将从这些未被发现的结点中任选一个作为新的源结点,并重复同样的搜索过程。该算法重复整个过程,直到图中的所有结点都被发现为止。在这里必须要说明的是,因为广度优先搜索通常用来寻找从特定源结点出发的最短路径距离,而深度优先搜索则常常作为另一个算法里的一个子程序,所以在讨论广度优先搜索时,源结点的数量限制为一个,而深度优先搜索则可以有多个源结点。

因为深度优先搜索会有多个源结点,因此该算法会形成多棵深度优先树,这些树形成了深度优先森林。因为当我们搜索到一个结点时,我们会标记这个结点被发现了,因此每个结点仅在一棵深度优先树中出现,因此所有的深度优先树是不相交的。

除了创建一个深度优先搜索森林外,深度优先搜索算法还在每个结点盖上一个时间戳。每个结点v有两个时间戳:第一个时间戳v.d记录结点v第一次被发现的时间(涂上灰色的时候),第二个时间戳v.f记录的是搜索完成对v的邻接链表扫描的时间(涂上黑色的时候)。

深度优先搜索算法可以用于有向图,也可以用于无向图(暗示着可以用于含有环的图)。代码如下:

void dfs_visit(int source,unsigned int& time, vector<int>& color,vector<int>& pred,
vector<unsigned int>& d,vector<unsigned int>& f,const vector<list<int>>& graph)
{
        enum {WHITE,GRAY,BLACK};

        time++;
        d[source]=time;
        color[source]=GRAY;

        for(auto iter=graph[source].begin();iter!=graph[source].end();++iter)
        {
                int tmp=*iter;
                if(color[tmp]==WHITE){
                        pred[tmp]=source;
                        dfs_visit(tmp,time,color,pred,d,f,graph);
                }
                else if(color[tmp]==GRAY)
                        cout<<"边( "<<source<<", "<<tmp<<" )是后向边"<<endl;
                else{
                        if(d[source]<d[tmp])
                                cout<<"边( "<<source<<", "<<tmp<<" )是前向边"<<endl;
                        else
                                cout<<"边( "<<source<<", "<<tmp<<" )是横向边"<<endl;
                }
        }

        color[source]=BLACK;
        time++;
        f[source]=time;
}

pair<vector<unsigned int>,vector<unsigned int>> dfs(const vector<list<int>>& graph)
{
        enum {WHITE,GRAY,BLACK};
        vector<int> color(graph.size(),WHITE);
        vector<int> pred(graph.size(),-1);

        unsigned int time=0;
        vector<unsigned int> d(graph.size()); //记录的是图中各个顶点v第一次被发现的时间;
        vector<unsigned int> f(graph.size()); //记录的是搜索完成对顶点v的邻接链表扫描的时间;

        for(int v=0;v!=graph.size();++v) //对图中每个顶点v进行搜索;
                if(color[v]==WHITE)
                        dfs_visit(v,time,color,pred,d,f,graph);

        return make_pair(d,f);
}

括号化定理:在对有向或无向图G=(V,E)进行的任意深度优先搜索中,对于任意两个节点u和v来说,下面三种情况只有一种成立:

1. 区间[u.d,u.f]和区间[v.d,v.f]完全分离,在深度优先森林中,结点u不是结点v的后代,
结点v也不是结点u的后代;
2. 区间[u.d,u.f]完全包含在区间[v.d,v.f]之内,在深度优先树中,结点u是结点v的后代;
3. 区间[v.d,v.f]完全包含在区间[u.d,u.f]之内,在深度优先树中,结点v是结点u的后代。

边的分类:对于在图G上运行深度优先搜索算法所生成的深度优先森林,我们可以定义4中边的类型:

1:树边:为深度优先森林的边,如果结点v是因算法对边(u,v)的探索而首次被发现的,则(u,v)是一条树边;
2:后向边:后向边(u,v)是将结点u连接到其在深度优先树中一个祖先结点v的边。有向图可以有自循环,自循环被认为是后向边;
3:前向边:是将结点u连接到其在深度优先树种一个后代结点v的边(u,v);
4:横向边:指其它所有的边,这些边可以连接同一棵深度优先树中的结点,只要其中一个结点不是另外一个结点的祖先,也可以连接不同深度优先树中的两个结点。

深度优先搜索算法有足够的信息能够对边按照上述四种进行分类,这里的关键是,当第一次探索边(u,v)时,结点v的颜色能够告诉我们关于边的一些信息:

1:结点v为白色表明该条边(u,v)是一条树边;
2:结点v为灰色表明该条边(u,v)是一条后向边;
3:结点v为黑色表明该条边(u,v)是一条前向边或横向边。如果u.d<v.d,则边(u,v)是前向边,在u.d>v.d时,(u,v)为横向边。

上面的代码在对边进行分类时就利用了上述知识。

在对边进行分类时,无向图可能给我们带来一些模糊性,因为边(u,v)和边(v,u)实际上是同一条边,因此我们可以根据深度搜索算法是先探索到边(u,v)还是边(v,u)来进行分类。根据这个标准,我们会发现:在对无向图G进行深度优先搜索时,每条边要么是树边,要么是后向边。

一个有向图G=(V,E)是无环的当且仅当对其进行深度优先搜索不产生后向边。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值