深度优先遍历和广度优先遍历代码实现

本文详细介绍了图的概念,包括无向图和有向图的表示方法,如邻接矩阵和邻接表。接着,讨论了图的两种遍历算法——深度优先搜索(DFS)和广度优先搜索(BFS),并提供了C++实现。DFS适用于寻找路径,而BFS适用于找到最近的节点。最后,针对图的不连通性给出了遍历整个图的策略。
摘要由CSDN通过智能技术生成

直接跳到 3. 看代码,注释较为详细
理论知识参考
代码参考(写的太好了!)

1. 什么是图

  • 表示 “多对多” 的关系
  • 包含:1. 一组顶点:通常用V(vertex) 表示顶点集合;2. 一组边:通常用E(edge) 表示边的集合。
    • 边是定点对:(v, w)∈E,其中v, w∈V
    • 有向边<v, w> 表示从 v 指向 w 的边(单行边)

表示方法

  1. 邻接矩阵表示法

    在这里插入图片描述

    好处:

    • 直观、简单、好理解
    • 方便检查任意一对的顶点间是否存在边
    • 方便找任一顶点的所有 “邻接点” (有边直接相连的顶点)
    • 方便计算任一顶点的 “度” (从该点发出的边数为 “出度” ,指向该点的边数为 “入度”)
      • 无向图:对应行(或列)非零元素的个数
      • 有向图:对应行非零元素的个数是 “出度” ;对应列非零元素的个数是 “入度”

    缺点:

    • 浪费空间:存稀疏图(点很多而边很少)有大量无效元素。(对稠密图特别是完全图还是很合算的)
    • 浪费时间:统计稀疏图中一共有多少条边
  2. 邻接表

    G[N] 为指针数组,对应矩阵每行一个链表,只存非0元素

    在这里插入图片描述

    特点:

    • 方便找任一顶点的所有 “邻接点”
    • 节约稀疏图的空间
      • 需要N个头指针 + 2E个结点(每个结点至少两个域)
    • 方便计算任一顶点的 “度” ?
      • 对无向图:是的
      • 对有向图:只能计算 “出度” ;需要构造 “逆邻接表”(存指向自己的边)来方便计算 “入度”
    • 不方便检查任意一对顶点间是否存在边

2. 图的遍历

  • 深度优先搜索(Depth First Search,DFS):一条道走到黑,然后回退!(基于栈)

    // 伪码描述--类似于树的先序遍历
    void DFS(vertex V) {
        visited[V] = true;
        for ( V 的每个邻接点 W ) 
            if (!visited[W]) 
                DFS(W);
    }
    

    若有N个顶点、E条边,时间复杂度是:

    • 用邻接表存储图,有O(N+E)
    • 用邻接矩阵存储图,有O(N²)
  • 广度优先搜索(Breadth First Search, BFS):广泛撒网,共同前行!(基于队列)

    // 伪码描述
    void BFS(vertex V) {
        visited[V] = true;
        enqueue(V, Q);
        while (!isEmpty(Q)) {
            V = dequeue(Q);
            for ( V 的每个邻接点 W)
                if (!visited[W]) {
                    visited[W] = true;
                    enqueue(W, Q);
                }
        }
    }
    

    若有N个顶点、E条边,时间复杂度是:

    • 用邻接表存储图,有O(N+E)
    • 用邻接矩阵存储图,有O(N²)

图不连通怎么办?

每调用一次DFS(V),就把 V 所在的连通分量遍历一遍。BFS也是一样。

void ListComponents(Graph G) {
    for (each V in G)
        if (!visited[v]) {
            DFS(V);	// or BFS(V);
        }
}

3. 代码实现

3.1 广度优先遍历

邻接矩阵实现:

#include <iostream>
#include <queue>
using namespace std;

typedef int ElemType;
#define MAXNUM 100
bool visited[MAXNUM];   // 定义visited数组,记录顶点的访问情况
queue<ElemType> q;  // 队列用来记录相邻结点

// !!!邻接矩阵表示图的广度优先遍历 !!!

// 邻接矩阵存储表示
struct AMGraph {
    ElemType vertex[MAXNUM];  // 顶点表
    int matrix[MAXNUM][MAXNUM];   // 邻接矩阵
    int vernum, edgenum;     // 当前顶点数和边数
};

// 找到顶点v对应下标
int locateVertex(AMGraph& G, ElemType v) {
    
    for (int i = 0; i < G.vernum; ++i) {
        if (G.vertex[i] == v) {
            return i;
        }
    }
    return -1;
}

// 采用邻接矩阵表示法,构建无向图G
bool createUDG(AMGraph& G) {
    int i, j, k;
    ElemType v1, v2;
    cin >> G.vernum >> G.edgenum;    // 输入总顶点数和边数

    for (i = 0; i < G.vernum; ++i) {
        cin >> G.vertex[i];   // 输入顶点表
    }

    // 初始化邻接矩阵边,0 表示顶点i和j之间无边
    for (i = 0; i < G.vernum; ++i) {
        for (j = 0; j < G.edgenum; ++j) {
            G.matrix[i][j] = 0;   
        }
    }

    // 1 表示两顶点之间有边
    for (k = 0; k < G.edgenum; ++k) {
        cin >> v1 >> v2;
        i = locateVertex(G, v1);   // 找到顶点i的下标
        j = locateVertex(G, v2);   // 找到顶点j的下标

        G.matrix[i][j] = G.matrix[j][i] = 1;    // 即i和j之间有边
    }
    return true;
}

// 用邻接矩阵表示图的广度优先遍历
void BFS_AM(AMGraph& G, ElemType v0) {
    int i, j, v, w;
    i = locateVertex(G, v0);   // 找到v0对应的下标
    cout << v0 << " ";
    visited[i] = 1;     // 标记v0已被访问
    q.push(v0);         // v0入队

    while (!q.empty()) {
        v = q.front();          // 输出队头元素,将要访问邻接点
        i = locateVertex(G, v); // 得到顶点v的下标
        q.pop();                // 将队头元素出队

        for (j = 0; j < G.vernum; ++j) {
            w = G.vertex[j];      // 在j位置上的顶点,先保存下来
            // 如果顶点v和w之间有边且顶点w未被访问
            if (G.matrix[i][j] && !visited[j]) {
                cout << w << " ";   // !!!遍历 !!!
                q.push(w);      // 顶点w入队
                visited[j] = 1; // 顶点w已被访问
            }
        }
    }
}

邻接表实现:

#include <iostream>
#include <queue>
using namespace std;

typedef int ElemType;
#define MAXNUM 100
bool visited[MAXNUM];   // 定义visited数组,记录顶点的访问情况
queue<ElemType> q;  // 队列用来记录相邻结点

// !!! 用邻接表表示图的广度优先搜索 !!!
// 邻接表存储表示

// 表结点
struct EdgeNode {
    int locateVer;     // 该边所指向的顶点的位置
    EdgeNode* nextEdge;   // 指向下一条边的指针
    int info;       // 和边有关的信息,如权值
};

// 顶点结点
struct VerNode {
    ElemType data;
    EdgeNode* firstEdge;  // 指向第一条依附在该顶点的边的指针
};

struct ALGraph {
    VerNode vertex[MAXNUM];   // 表头结点表
    int vernum, edgenum;     // 总顶点数和边数
};

// 找到顶点对应的下标
int locateVertex(ALGraph& G, ElemType v) {

    for (int i = 0; i < G.vernum; ++i) {
        if (G.vertex[i].data == v) {
            return i;
        }
    }
    return -1;
}

bool createALG(ALGraph& G) {
    int i, j, k;
    ElemType v1, v2;
    cin >> G.vernum >> G.edgenum;    // 输入总顶点数和总边数

    for (i = 0; i < G.vernum; ++i) {
        cin >> G.vertex[i].data;  // 输入顶点值
        G.vertex[i].firstEdge = nullptr;   // 初始化每个表头结点的指针域
    }

    // 输入各边,构造邻接表
    for (k = 0; k < G.edgenum; ++k) {
        cin >> v1 >> v2;            // 输入一条边的两侧顶点
        i = locateVertex(G, v1);   // 找到顶点i的下标
        j = locateVertex(G, v2);   // 找到顶点j的下标

        // 操作表结点
        EdgeNode* p1 = new EdgeNode;  // 创建一个边结点
        p1->locateVer = j;         // 邻接点域为j
        p1->nextEdge = G.vertex[i].firstEdge;
        G.vertex[i].firstEdge = p1;

        // 因为一条边对应两个顶点,直接生成另一对称的新的表结点
        EdgeNode* p2 = new EdgeNode;
        p2->locateVer = i;
        p2->nextEdge = G.vertex[j].firstEdge;
        G.vertex[j].firstEdge = p1;
    }
    return true;
}

// 采用邻接表表示图的广度优先遍历----操作与邻接矩阵表示类似
void BFS_AL(ALGraph& G, ElemType v0) {
    int i, j, v;
    EdgeNode* p = nullptr;
    cout << v0 << " ";

    i = locateVertex(G, v0);
    visited[i] = 1;
    q.push(v0);

    while (!q.empty()) {
        v = q.front();      // 输出队列中的第一个顶点
        i = locateVertex(G, v); // 计算其下标
        q.pop();        // 顶点v出队

        // 遍历顶点v的邻接点
        for (p = G.vertex[i].firstEdge; p; p = p->nextEdge) {
            j = p->locateVer;   // 记录该顶点下标
            // 如果顶点p未被访问
            if (!visited[j]) {
                cout << G.vertex[j].data << " ";    // 访问顶点p
                visited[j] = 1;                     // 标记p已被访问
                q.push(G.vertex[j].data);           // 将顶点p入队
            }
        }
    }
}

3.2 深度优先遍历

邻接矩阵实现

#include <iostream>
#include <queue>
using namespace std;

typedef int ElemType;
#define MAXNUM 100
bool visited[MAXNUM];   // 定义visited数组,记录顶点的访问情况
queue<ElemType> q;  // 队列用来记录相邻结点

// !!!邻接矩阵表示图的深度优先遍历 !!!

// 邻接矩阵存储表示
struct AMGraph {
    ElemType vertex[MAXNUM];  // 顶点表
    int matrix[MAXNUM][MAXNUM];   // 邻接矩阵
    int vernum, edgenum;     // 当前顶点数和边数
};

// 找到顶点v对应下标
int locateVertex(AMGraph& G, ElemType v) {

    for (int i = 0; i < G.vernum; ++i) {
        if (G.vertex[i] == v) {
            return i;
        }
    }
    return -1;
}

// 采用邻接矩阵表示法,构建无向图G
bool createUDG(AMGraph& G) {
    int i, j, k;
    ElemType v1, v2;
    cin >> G.vernum >> G.edgenum;    // 输入总顶点数和边数

    for (i = 0; i < G.vernum; ++i) {
        cin >> G.vertex[i];   // 输入顶点表
    }

    // 初始化邻接矩阵边,0 表示顶点i和j之间无边
    for (i = 0; i < G.vernum; ++i) {
        for (j = 0; j < G.edgenum; ++j) {
            G.matrix[i][j] = 0;
        }
    }

    // 1 表示两顶点之间有边
    for (k = 0; k < G.edgenum; ++k) {
        cin >> v1 >> v2;
        i = locateVertex(G, v1);   // 找到顶点i的下标
        j = locateVertex(G, v2);   // 找到顶点j的下标

        G.matrix[i][j] = G.matrix[j][i] = 1;    // 即i和j之间有边
    }
    return true;
}

void DFS_AM(AMGraph& G, int i) {
    int j;
    cout << G.vertex[i] << " "; // 输出顶点
    visited[i] = 1;             // 标记为已访问
    for (j = 0; j < G.vernum; ++j) {   
        // 如果两顶点之间有边且该顶点还未被访问,则递归调用DFS
        if (G.matrix[i][j] && !visited[j]) {    
            DFS_AM(G, j);
        }
    }
}

邻接表实现:

#include <iostream>
#include <queue>
using namespace std;

typedef int ElemType;
#define MAXNUM 100
bool visited[MAXNUM];   // 定义visited数组,记录顶点的访问情况
queue<ElemType> q;  // 队列用来记录相邻结点

// !!! 用邻接表表示图的深度优先搜索 !!!
// 邻接表存储表示

// 表结点
struct EdgeNode {
    int locateVer;     // 该边所指向的顶点的位置
    EdgeNode* nextEdge;   // 指向下一条边的指针
    int info;       // 和边有关的信息,如权值
};

// 顶点结点
struct VerNode {
    ElemType data;
    EdgeNode* firstEdge;  // 指向第一条依附在该顶点的边的指针
};

struct ALGraph {
    VerNode vertex[MAXNUM];   // 表头结点表
    int vernum, edgenum;     // 总顶点数和边数
};

// 找到顶点对应的下标
int locateVertex(ALGraph& G, ElemType v) {

    for (int i = 0; i < G.vernum; ++i) {
        if (G.vertex[i].data == v) {
            return i;
        }
    }
    return -1;
}

bool createALG(ALGraph& G) {
    int i, j, k;
    ElemType v1, v2;
    cin >> G.vernum >> G.edgenum;    // 输入总顶点数和总边数

    for (i = 0; i < G.vernum; ++i) {
        cin >> G.vertex[i].data;  // 输入顶点值
        G.vertex[i].firstEdge = nullptr;   // 初始化每个表头结点的指针域
    }

    // 输入各边,构造邻接表
    for (k = 0; k < G.edgenum; ++k) {
        cin >> v1 >> v2;    // 输入一条边的两侧顶点
        i = locateVertex(G, v1);   // 找到顶点i的下标
        j = locateVertex(G, v2);   // 找到顶点j的下标

        // 操作表结点
        EdgeNode* p1 = new EdgeNode;  // 创建一个边结点
        p1->locateVer = j;         // 邻接点域为j
        p1->nextEdge = G.vertex[i].firstEdge;
        G.vertex[i].firstEdge = p1;

        // 因为一条边对应两个顶点,直接生成另一对称的新的表结点
        EdgeNode* p2 = new EdgeNode;
        p2->locateVer = i;
        p2->nextEdge = G.vertex[j].firstEdge;
        G.vertex[j].firstEdge = p1;
    }
    return true;
}

void DFS_AL(ALGraph& G, int i) {
    int j;
    cout << G.vertex[i].data << " ";    // 访问该顶点
    visited[i] = 1;         // 标记为以访问
    // 是否可以不用申请一个空间?    
    // EdgeNode* p = new EdgeNode;     
    // p = G.vertex[i].firstEdge;
    EdgeNode* p = G.vertex[i].firstEdge;

    while (p) {
        j = p->locateVer;       // 该顶点的下标
        if (!visited[j]) {
            DFS_AL(G, j);
        }
        p = p->nextEdge;
    }
}
  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ClimberCoding

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

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

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

打赏作者

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

抵扣说明:

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

余额充值