图论(一):图结构与图的遍历

目录

一 图的结构

二 图的广度优先遍历

2.1 原理

2.2 具体实现:

 2.3 复杂度分析:

三 图的深度优先遍历

3.1 原理:

3.2 具体实现:

3.3 复杂度分析

四 测试样例


一 图的结构

        一般我们用 G(V,E)这个集合表示一个图,V代表图中所有顶点的集合,E代表图中所有顶点的边集合。

        在计算机中,我们可用两种数据结构来表示图,这两种数据结构各有优劣:

  • 邻接链表:

        邻接链表用包含|V|条链表的数组Adj构成,每个节点有一条链表,对于每个节点 u\in V ,邻接链表Adj[u]包含所有与节点u相连的节点。保存权重只需在节点内加个权重值即可。

        当图是一个稀疏图时,临界链表占据的空间小,|V|个顶点与2|E|条边,总空间复杂度为 \Theta (|V|+|E|),因此是稀疏图时我们选择用邻接链表表示图。缺点是找到某两个节点是否相邻较慢。

  • 邻接矩阵:

        邻接矩阵使用一个二维数组保存。抽象为矩阵A,矩阵内元素a_{ij} 有:

         

         有权图时可将1换为权值。邻接矩阵所消耗的空间为\Theta (|V|^2) ,在稠密的图(|E|很大)时我们更多选择邻接矩阵,同时邻接矩阵判断两个节点是否是邻居只需要常数时间,这也是它的优势

二 图的广度优先遍历

2.1 原理

        给定一个图G(V,E)和一个源节点s,广度优先搜索可以发现从源节点s到达的所有节点。广度优先搜索的核心思想是:发现所有距离源节点s为k的所有节点后才会发现距离源节点s为k+1的节点。在遍历的过程中,我们可以获得以下信息并保存下来:

  • 源节点s到每个可到达节点的距离(最少的边数)
  • 每个节点在此遍历下的前一个遍历到的节点
  • 一棵广度优先搜索树

        我们用队列来存储遍历到的节点,为了控制算法,每个节点都有三种颜色状态,用color[]数组表示:

  • 白色:该节点未被找到
  • 灰色:该节点被找到但还在队列中
  • 黑色:该节点被找到且其邻居都被遍历到队列中

伪代码如下:

2.2 具体实现

/**
 * 图的算法
 * BFS 广度优先搜索,能够得到每个顶点到起始点的距离d[],以及每个顶点广度优先搜索树顺序下的前驱结点p[]
 * DFS 深度优先算法,能得到每个顶点的前驱节点,以及顶点的发现时间与递归结束时间
 */
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

typedef enum {WHITE,GRAY,BLACK} color;//白色代表未被发现;灰色代表该节点已被发现但是该节点没有寻找完;黑色代表已经处理完

typedef vector<vector<int>> Graph;

int time;//DFS的全局时间

/**
 * 图的广度优先搜索
 * @param g 图结构:邻接链表实现
 * @param start 起始节点
 * @param d 每个顶点到起始点的距离d[]
 * @param p 每个顶点广度优先搜索树顺序下的前驱结点p[]
 * @param c 颜色节点,辅助作用
 */
void BFS(Graph g,int start,int d[],int p[],int c[]){
    //init
    for(int i=1;i<g.size();i++){
        p[i] = 0;//没有前驱节点
        if(i != start){//不是起始节点
            c[i] = WHITE;
            d[i] = INT_MAX;
        } else{
            c[i] = GRAY;
            d[i] = 0;
        }
    }
    queue<int> queue;
    queue.push(start);//将起始节点放入栈中
    while(!queue.empty()){
        int front = queue.front();//取队列首节点
        queue.pop();
        for(int adjacency_vertex_seq:g[front]){//对该节点的邻接节点遍历
            if(c[adjacency_vertex_seq] == WHITE){
                c[adjacency_vertex_seq] = GRAY;//该节点已被遍历
                d[adjacency_vertex_seq] = d[front]+1;
                p[adjacency_vertex_seq] = front;
                queue.push(adjacency_vertex_seq);//进队列
            }
        }
        c[front] = BLACK;//队首节点的邻居已经全部遍历完成
    }
}

 2.3 复杂度分析

        该算法花费时间T(n)主要有以下几个部分组成:

  • 初始化操作:O(V)
  • 每个节点入队与出队的操作:O(V)
  • 每个节点出队时扫描该节点的邻接链表,所有邻接链表的长度为\Theta (E),用于扫描邻接链表的总时间为O(E)

        因此广度优先搜索的时间复杂度为:O(V+E)

        空间复杂度主要在邻接链表的花费与队列的空间。

三 图的深度优先遍历

3.1 原理

        深度优先搜索所使用的策略是:只要可能,就在图中“深入”。如果一个节点 v 有邻居节点,那就遍历 v 的邻居节点,当 v 所有子节点都被遍历完成后,搜索则回溯到 v 的前驱节点。

        我们使用递归来实现一直深入的形式,每个节点同样有三种颜色,与BFS类似:

  • 白色:该节点未被找到
  • 灰色:该节点被找到但还在遍历它的子节点
  • 黑色:该节点的子节点都被遍历,回溯到该节点

        通过一次DFS遍历,我们能捕捉以下信息:

  • 一个节点被发现的时间,用d[]存储
  • 一个节点变成黑色的时间,用f[]存储
  • 节点的前驱节点,用p[]存储

伪代码如下:

3.2 具体实现

/**
 * 图的算法
 * BFS 广度优先搜索,能够得到每个顶点到起始点的距离d[],以及每个顶点广度优先搜索树顺序下的前驱结点p[]
 * DFS 深度优先算法,能得到每个顶点的前驱节点,以及顶点的发现时间与递归结束时间
 */
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

typedef enum {WHITE,GRAY,BLACK} color;//白色代表未被发现;灰色代表该节点已被发现但是该节点没有寻找完;黑色代表已经处理完

typedef vector<vector<int>> Graph;

int time;//DFS的全局时间

void DFS_VISIT(int node,Graph g,int d[],int f[],int p[],int c[]){
    c[node] = GRAY;//正在DFS
    d[node] = time++;//全局时间+1
    for(int adjacency_vertex_seq:g[node]){//遍历所有邻居节点
        if(c[adjacency_vertex_seq] == WHITE){
            p[adjacency_vertex_seq] = node;//设置子节点的前驱结点
            DFS_VISIT(adjacency_vertex_seq,g,d,f,p,c);
        }
    }
    c[node] = BLACK;//该节点结束遍历
    f[node] = time++;
}

/**
 * 图的宽度优先搜索
 * @param g 图结构,邻接链表实现
 * @param d 一个节点被发现的时间,时间单位为1
 * @param f 一个节点及其子节点处理结束的时间
 * @param p 顶点的前驱节点
 * @param c 辅助:颜色数组
 */
void DFS(Graph g,int d[],int f[],int p[],int c[]){
    //init
    for(int i=1;i<g.size();i++){
        c[i] = WHITE;//所有节点未被发现
        p[i] = 0;//没有前驱节点
    }
    time = 1;
    //开始遍历
    for(int i=1;i<g.size();i++){
        if(c[i] == WHITE){
            DFS_VISIT(i,g,d,f,p,c);
        }
    }

}

3.3 复杂度分析

        先不考察DFS_VISIT(),该算法花费时间T(n)主要有以下几个部分组成:

  • 初始化操作:O(V)
  • 设置每个节点颜色为白色:\Theta (V)

        对于每个节点,DFS_VISIT()恰好能调用一次,伪代码第4-7行执行次数为 |Adj[v]| 。而\sum_{v\in V}|Adj[v]|=\Theta (E) ,因此深度优先搜索的时间复杂度为\Theta (V+E)

四 测试样例

  • BFS

        

  • DFS

        

 测试代码:

/**
 * 图的算法
 * BFS 广度优先搜索,能够得到每个顶点到起始点的距离d[],以及每个顶点广度优先搜索树顺序下的前驱结点p[]
 * DFS 深度优先算法,能得到每个顶点的前驱节点,以及顶点的发现时间与递归结束时间
 */
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

typedef enum {WHITE,GRAY,BLACK} color;//白色代表未被发现;灰色代表该节点已被发现但是该节点没有寻找完;黑色代表已经处理完

typedef vector<vector<int>> Graph;

int time;//DFS的全局时间

/**
 * 图的广度优先搜索
 * @param g 图结构:邻接链表实现
 * @param start 起始节点
 * @param d 每个顶点到起始点的距离d[]
 * @param p 每个顶点广度优先搜索树顺序下的前驱结点p[]
 * @param c 颜色节点,辅助作用
 */
void BFS(Graph g,int start,int d[],int p[],int c[]){
    //init
    for(int i=1;i<g.size();i++){
        p[i] = 0;//没有前驱节点
        if(i != start){//不是起始节点
            c[i] = WHITE;
            d[i] = INT_MAX;
        } else{
            c[i] = GRAY;
            d[i] = 0;
        }
    }
    queue<int> queue;
    queue.push(start);//将起始节点放入栈中
    while(!queue.empty()){
        int front = queue.front();//取队列首节点
        queue.pop();
        for(int adjacency_vertex_seq:g[front]){//对该节点的邻接节点遍历
            if(c[adjacency_vertex_seq] == WHITE){
                c[adjacency_vertex_seq] = GRAY;//该节点已被遍历
                d[adjacency_vertex_seq] = d[front]+1;
                p[adjacency_vertex_seq] = front;
                queue.push(adjacency_vertex_seq);//进队列
            }
        }
        c[front] = BLACK;//队首节点的邻居已经全部遍历完成
    }
}

void DFS_VISIT(int node,Graph g,int d[],int f[],int p[],int c[]){
    c[node] = GRAY;//正在DFS
    d[node] = time++;//全局时间+1
    for(int adjacency_vertex_seq:g[node]){//遍历所有邻居节点
        if(c[adjacency_vertex_seq] == WHITE){
            p[adjacency_vertex_seq] = node;//设置子节点的前驱结点
            DFS_VISIT(adjacency_vertex_seq,g,d,f,p,c);
        }
    }
    c[node] = BLACK;//该节点结束遍历
    f[node] = time++;
}

/**
 * 图的宽度优先搜索
 * @param g 图结构,邻接链表实现
 * @param d 一个节点被发现的时间,时间单位为1
 * @param f 一个节点及其子节点处理结束的时间
 * @param p 顶点的前驱节点
 * @param c 辅助:颜色数组
 */
void DFS(Graph g,int d[],int f[],int p[],int c[]){
    //init
    for(int i=1;i<g.size();i++){
        c[i] = WHITE;//所有节点未被发现
        p[i] = 0;//没有前驱节点
    }
    time = 1;
    //开始遍历
    for(int i=1;i<g.size();i++){
        if(c[i] == WHITE){
            DFS_VISIT(i,g,d,f,p,c);
        }
    }

}

int main(){
    //第一个图
    Graph graph;
    vector<int> occupation = {};
    graph.push_back(occupation);
    vector<int> a = {2,5};//1
    graph.push_back(a);
    vector<int> b = {1,3,6,7};
    graph.push_back(b);
    vector<int> c = {2,4};
    graph.push_back(c);
    vector<int> d = {3,5};
    graph.push_back(d);
    vector<int> e = {1,4,6,7};
    graph.push_back(e);
    vector<int> f = {2,5};
    graph.push_back(f);
    vector<int> s = {2,5};//起始节点 7号
    graph.push_back(s);


    Graph graph2;
    graph2.push_back(occupation);
    vector<int> a2 = {2,5};
    graph2.push_back(a2);
    vector<int> b2 = {1,5,6};
    graph2.push_back(b2);
    vector<int> c2 = {4};
    graph2.push_back(c2);
    vector<int> d2 = {3,7};
    graph2.push_back(d2);
    vector<int> e2 = {1,2,6};
    graph2.push_back(e2);
    vector<int> f2 = {2,5};
    graph2.push_back(f2);
    vector<int> g2 = {4};//起始节点 7号
    graph2.push_back(g2);

    int distance1[graph.size()],distance2[graph.size()];
    int finish2[graph.size()];
    int predecessor1[graph.size()],predecessor2[graph.size()];
    int color1[graph.size()],color2[graph.size()];
    BFS(graph,7,distance1,predecessor1,color1);
    DFS(graph2,distance2,finish2,predecessor2,color2);

    cout<<"BFS"<<endl;
    for(int i=1;i<graph.size();i++){
        cout<<"node "<<i<<" distance from start: "<<distance1[i]<<" predecessor node: "<<predecessor1[i]<<endl;
    }

    cout<<"DFS"<<endl;
    for(int i=1;i<graph.size();i++){
        cout<<distance2[i]<<" "<<finish2[i]<<" "<<predecessor2[i]<<endl;
    }

}

 测试结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sunburst7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值