图的遍历之DFS与BFS

参考:https://wangkuiwu.github.io/2013/04/10/iterator

深度优先搜索DFS

图的深度优先遍历(Depth First Search)和树的先序遍历有一定相似之处,它的基本思想是:假设初始状态是图中所有顶点均未被访问,则从某个顶点 v 出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和 v 有路径相通的顶点都被访问到。 若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

显然,深度优先搜索是一个递归地过程。

无向图的深度优先搜索

以下面的无向图为例,对无向图深度优先搜索进行演示:

图的遍历-图1

图 G1

对图 G1 进行深度优先遍历,从顶点 A 开始。

图的遍历-图2

其访问步骤如下:

第一步:访问 A。

第二步:访问 C(A 的邻接结点)。在第 1 步访问 A 之后,接下来应该访问的是 A 的邻接点,即 C, D, F 中的一个,但在本文的实现中,顶点A, B, C, D, E, F, G 是按照顺序存储,C 在 D 和 F 的前面,因此,先访问 C。

第三步:访问 B(C 的邻接点)。在第 2 步访问 C 之后,接下来应该访问 C 的邻接点,即 B 和 D 中的一个(因为 A 被访问过,所以不算),而由于 B 在 D 之前,先访问 B。

第四步:访问 D(C 的邻接点)。在第 3 步访问了 C 的邻接点 B 之后,B 已经没有未被访问的邻接点;因此,返回到访问 C 的另一个邻接点 D。

第五步:访问 F(A 的邻接点)。前面已经访问了 A,并且访问完了 A 的邻接点 C 的所有邻接点(包括递归的邻接点在内);因此,此时返回到访问 A 的另一个邻接点 F。

第六步:访问 G(F 的邻接点)。

第七步:访问 E(G 的邻接点)。

因此,访问顺序是:A -> C -> B -> D -> F -> G -> E

有向图的深度优先搜索

以下面有向图为例,来对有向图深度优先搜索进行演示:

图的遍历-图3

图 G2

对上面的图 G2 进行深度优先遍历,从顶点 A 开始。

图的遍历-图4

其访问步骤如下:

第一步:访问 A。

第二部:访问 B。在访问了 A 之后,接下来应该访问的是 A 的出边的另一个顶点,即顶点 B。

第三步:访问 C。在访问了 B 之后,接下来应该访问的是 B 的出边的另一个顶点,即顶点 C, E, F,在本文实现的图中,顶点 A, B, C, D, E, F, G 按照顺序存储,因此先访问 C。

第四步:访问 E。接下来访问 C 的出边的另一个顶点,即顶点 E。

第五步:访问 D。接下来访问 E 的出边的另一个顶点,即顶点 B, D。顶点 B 已经被访问过了,因此访问顶点 D。

第六步:访问 F。接下来应该回溯到访问 B 的出边的另一个顶点 F。

第七步:访问 G。

因此,访问顺序是:A -> B -> C -> E -> D -> F -> G

广度优先搜索BFS

广度优先搜索算法(Breadth First Search)又称为“宽度优先搜索”或“横向优先搜索”,简称 BFS。它的基本思想是:从图中某顶点 v 出发,在访问了 v 之后依次访问 v 的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

换句话说,广度优先搜索遍历图的过程是以 v 为起点,由近至远,依次访问和 v 有路径相通且路径长度为 1, 2, … 的顶点。

无向图的广度优先搜索

下面以“无向图”为例,来对广度优先搜索进行演示。还是以上面图 G1 为例进行说明。

图的遍历-图5

第一步:访问 A。

第二部:依次访问 C, D, F。在访问了 A 之后,接下来访问 A 的邻接点。前面已经说过,在文本实现中,顶点 A, B, C, D, E, F, G 是按照顺序存储的,C 在 D 和 F 的面前,因此,先访问 C。在访问完 C 之后,再依次访问 D, F。

第三步:访问 B, G。再第 2 步访问完 C, D, F 之后,再依次访问它们的邻接点。首先访问 C 的邻接点 B,再访问 F 的邻接点 G。

第四步:访问 E。再第三步访问完 B, G 之后,再依次访问它们的邻接点。只有 G 有邻接点 E,因此访问 G 的邻接点 E。

因此访问顺序是:A -> C -> D -> F -> B -> G -> E

有向图的广度优先搜索

下面以“有向图”为例,来对广度优先搜索进行演示。还是以上面的图 G2 为例进行说明。

图的遍历-图6

第一步:访问 A。

第二部:访问 B。

第三步:依次访问 C, E, F。在访问了 B 之后,接下来访问 B 的出边的另一个顶点,即 C, E, F。前面已经说过,在文本实现中,顶点 A, B, C, D, E, F, G 是按照顺序存储的,因此会先访问 C,再依次访问 E, F。

第四步:依次访问 D, G。在访问完 C, E, F 之后,再依次访问它们的出边的另一个顶点。还是按照 C, E, F 的顺序访问,C 的已经全部访问过了,那么就只剩下 E, F;先访问 E 的邻接点 D,再访问 F 的邻接点 G。

因此访问顺序是:A -> B -> C -> E -> D -> G

代码实现

此代码实现基于之前的:无向图的邻接矩阵与邻接表详细实现有向图的邻接矩阵与邻接表详细实现。

无向图(邻接矩阵)的DFS与BFS实现

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

#define MAX 100

class MatrixUDG
{
private:
    char mVexs[MAX];       //顶点集合,如:{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
    int mVexNum;           //顶点数,如:7
    int mEdgNum;           //边数
    int mMatrix[MAX][MAX]; //邻接矩阵
public:
    //创建图(手动输入)
    MatrixUDG();
    //创建图(用提供的顶点和边)
    MatrixUDG(char *vexs, int vNum, char (*edges)[2], int eNum);
    ~MatrixUDG();
    //打印邻接表
    void print();
    //深度优先遍历
    void DFS();
    //广度优先遍历
    void BFS();

private:
    //输入一个合法字母
    char readChar();
    //获得一个字母在顶点数组的下标
    int getPosition(char ch);
    //返回顶点v的第一个邻接顶点的索引,失败返回-1
    int firstVertex(int v);
    //范围顶点v相对于w的下一个邻接顶点的索引,失败返回-1
    //比如: A和C, D, E有连接,则A相对于C的下一个结点为D,即返回D的索引
    int nextVertex(int v, int w);
    //深度优先搜索遍历图的递归实现
    void DFS(int i, int *visited);
};

MatrixUDG::MatrixUDG()
{
    char c1, c2;
    int p1, p2;
    cout << "输入顶点数:";
    cin >> mVexNum;
    cout << "输入边数:";
    cin >> mEdgNum;
    if (mVexNum < 1 || mEdgNum < 1 || (mEdgNum > (mVexNum * (mVexNum - 1))))
    {
        cout << "输入有误!" << endl;
        return;
    }
    for (int i = 0; i < mVexNum; ++i)
    {
        cout << "vertex(" << i << "):";
        mVexs[i] = readChar();
    }
    for (int j = 0; j < mEdgNum; ++j)
    {
        cout << "edge(" << j << "):";
        c1 = readChar();
        c2 = readChar();
        p1 = getPosition(c1);
        p2 = getPosition(c2);
        if (p1 == -1 || p2 == -1)
        {
            cout << "输入的边错误!" << endl;
            return;
        }
        mMatrix[p1][p2] = 1;
        mMatrix[p2][p1] = 1;
    }
}

MatrixUDG::MatrixUDG(char *vexs, int vNum, char (*edges)[2], int eNum)
{
    if (vNum > MAX)
        return;
    mVexNum = vNum;
    mEdgNum = eNum;
    //初始化顶点
    for (int i = 0; i < mVexNum; ++i)
        mVexs[i] = vexs[i];
    int p1, p2;
    for (int j = 0; j < mEdgNum; ++j)
    {
        //获得边的起始点和结束点
        p1 = getPosition(edges[j][0]);
        p2 = getPosition(edges[j][1]);
        //将连线的两个点都置为1
        if (p1 == -1 || p2 == -1)
        {
            cout << "输入的边错误!" << endl;
            return;
        }
        mMatrix[p1][p2] = 1;
        mMatrix[p2][p1] = 1;
    }
}

MatrixUDG::~MatrixUDG()
{
}

int MatrixUDG::getPosition(char ch)
{
    for (int i = 0; i < mVexNum; ++i)
    {
        if (mVexs[i] == ch)
            return i;
    }
    return -1;
}

char MatrixUDG::readChar()
{
    char ch;
    do
    {
        cin >> ch;
    } while (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')));
    return ch;
}

void MatrixUDG::print()
{
    for (int i = 0; i < mVexNum; ++i)
    {
        for (int j = 0; j < mVexNum; ++j)
        {
            cout << mMatrix[i][j] << " ";
        }
        cout << endl;
    }
}

/*
返回顶点v的第一个邻接顶点的索引,失败返回-1
*/
int MatrixUDG::firstVertex(int v)
{
    if (v < 0 || v > (mVexNum - 1))
        return -1;
    for (int i = 0; i < mVexNum; ++i)
    {
        if (mMatrix[v][i] == 1)
            return i;
    }
    return -1;
}
/*
返回顶点v相对于w的下一个邻接顶点的索引,失败则返回-1
比如:A和C,D,E有连接,则A相对于C的下一个结点为D,即返回D的索引
*/
int MatrixUDG::nextVertex(int v, int w)
{
    if (v < 0 || v > (mVexNum - 1) || w < 0 || w > (mVexNum - 1))
        return -1;
    for (int i = w + 1; i < mVexNum; ++i)
    {
        if (mMatrix[v][i] == 1)
            return i;
    }
    return -1;
}

void MatrixUDG::DFS()
{
    int visited[MAX] = {0}; //顶点是否访问标记
    cout << "DFS:";
    for (int i = 0; i < mVexNum; ++i)
    {
        if (!visited[i])
        {
            DFS(i, visited);
        }
    }
    cout << endl;
}

void MatrixUDG::DFS(int i, int *visited)
{
    visited[i] = 1; //标记为访问过
    cout << mVexs[i] << " ";
    //返回-1终止循环
    for (int w = firstVertex(i); w >= 0; w = nextVertex(i, w))
    {
        if (!visited[w])
            DFS(w, visited);
    }
}

void MatrixUDG::BFS()
{
    int i = 0, j, k;
    int visited[MAX] = {0};
    std::queue<int> queue;
    cout << "BFS:";
    for (i = 0; i < mVexNum; ++i)
    {
        if (!visited[i])
        {
            visited[i] = 1;
            cout << mVexs[i] << " ";
            queue.push(i); //入队列
        }
        while (!queue.empty())
        {
            j = queue.front();
            queue.pop(); //出队列
            for (k = firstVertex(j); k >= 0; k = nextVertex(j, k)) //依次找到j的各个邻接点,然后访问再加到队列中
            {
                if (!visited[k])
                {
                    visited[k] = 1;
                    cout << mVexs[k] << " ";
                    queue.push(k); //入队列
                }
            }
        }
    }
    cout << endl;
}

int main()
{
    char vexs[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
    char edges[][2] = {
        {'A', 'C'},
        {'A', 'D'},
        {'A', 'F'},
        {'B', 'C'},
        {'C', 'D'},
        {'E', 'G'},
        {'F', 'G'}};
    int vNum = sizeof(vexs) / sizeof(vexs[0]);
    int eNum = sizeof(edges) / sizeof(edges[0]);
    //1. 根据提供的数据生成
    MatrixUDG mudg(vexs, vNum, edges, eNum);
    mudg.print(); //打印图
    mudg.DFS(); //深度优先遍历
    mudg.BFS(); //广度优先遍历
    //2. 手动生成
    // MatrixUDG mudg1;
    // mudg1.print();
    // mudg1.DFS();
}

运行结果如下:

0 0 1 1 0 1 0 
0 0 1 0 0 0 0 
1 1 0 1 0 0 0 
1 0 1 0 0 0 0 
0 0 0 0 0 0 1 
1 0 0 0 0 0 1 
0 0 0 0 1 1 0 
DFS:A C B D F G E 
BFS:A C D F B G E 

无向图(邻接表)的DFS与BFS实现

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

#define MAX 100

class ListUDG
{
private:
    //每一条边
    struct ENode
    {
        int iVex;               //指向的顶点的位置
        ENode *nextEdge = NULL; //指向顶点的下一条边的指针
    };
    //数组中存储的顶点
    struct VNode
    {
        char data;
        ENode *firstEdge = NULL; //指向第一条该顶点的边
    };

private:
    int mVexNum;      //图的顶点数目
    int mEdgeNum;     //图的边的数目
    VNode mVexs[MAX]; //存储顶点 如B的邻接点:C->E->F
public:
    ListUDG();
    ListUDG(char vexs[], int vNum, char edges[][2], int eNum);
    ~ListUDG();
    //打印邻接表
    void print();
    //深度优先搜索遍历
    void DFS();
    //广度优先搜索遍历
    void BFS();

private:
    //读取一个合法的输入字符
    char readChar();
    //返回字符ch在的位置
    int getPosition(char ch);
    //将node结点链接到list的最后
    void linkLast(ENode *list, ENode *node);
    //深度优先搜索遍历图的递归实现
    void DFS(int i, int *visited);
};

ListUDG::ListUDG()
{
    char c1, c2;
    int p1, p2;

    ENode *node1, *node2;
    cout << "输入顶点数:";
    cin >> mVexNum;
    cout << "输入边数:";
    cin >> mEdgeNum;
    if (mVexNum > MAX || mEdgeNum > MAX || mVexNum < 1 || mEdgeNum < 1 || (mEdgeNum > (mVexNum * (mVexNum - 1))))
    {
        cout << "输入有误!" << endl;
        return;
    }
    for (int i = 0; i < mVexNum; ++i)
    {
        cout << "vertex(" << i << "):";
        mVexs[i].data = readChar();
    }
    //初始化邻接表的边
    for (int j = 0; j < mEdgeNum; ++j)
    {
        cout << "edge(" << j << "):";
        c1 = readChar();
        c2 = readChar();
        p1 = getPosition(c1);
        p2 = getPosition(c2);
        if (p1 == -1 || p2 == -1)
        {
            cout << "输入的边有错误!" << endl;
            return;
        }

        node1 = new ENode();
        node1->iVex = p2;
        if (mVexs[p1].firstEdge == NULL)
            mVexs[p1].firstEdge = node1;
        else
            linkLast(mVexs[p1].firstEdge, node1);
        node2 = new ENode();
        node2->iVex = p1;
        if (mVexs[p2].firstEdge == NULL)
            mVexs[p2].firstEdge = node2;
        else
            linkLast(mVexs[p2].firstEdge, node2);
    }
}

ListUDG::ListUDG(char *vexs, int vNum, char (*edges)[2], int eNum)
{
    if (vNum > MAX || eNum > MAX)
        return;
    char c1, c2;
    int p1, p2;
    ENode *node1, *node2;
    mVexNum = vNum;
    mEdgeNum = eNum;
    for (int i = 0; i < mVexNum; ++i)
    {
        mVexs[i].data = vexs[i];
    }
    for (int j = 0; j < mEdgeNum; ++j)
    {
        c1 = edges[j][0];
        c2 = edges[j][1];
        p1 = getPosition(c1);
        p2 = getPosition(c2);
        if (p1 == -1 || p2 == -1)
        {
            cout << "输入的边有错误!" << endl;
            return;
        }
        node1 = new ENode();
        node1->iVex = p2;
        if (mVexs[p1].firstEdge == NULL)
            mVexs[p1].firstEdge = node1;
        else
            linkLast(mVexs[p1].firstEdge, node1);
        node2 = new ENode();
        node2->iVex = p1;
        if (mVexs[p2].firstEdge == NULL)
            mVexs[p2].firstEdge = node2;
        else
            linkLast(mVexs[p2].firstEdge, node2);
    }
}

ListUDG::~ListUDG() {}

void ListUDG::linkLast(ENode *list, ENode *node)
{
    ENode *p = list;
    while (p->nextEdge)
        p = p->nextEdge;
    p->nextEdge = node;
}

int ListUDG::getPosition(char ch)
{
    for (int i = 0; i < mVexNum; ++i)
    {
        if (mVexs[i].data == ch)
            return i;
    }
    return -1;
}

char ListUDG::readChar()
{
    char ch;
    do
    {
        cin >> ch;
    } while (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')));
    return ch;
}

//深度优先搜索遍历图的递归实现
void ListUDG::DFS(int i, int *visited)
{
    visited[i] = 1;
    cout << mVexs[i].data << " ";
    ENode *node = mVexs[i].firstEdge;
    while (node != NULL)
    {
        if (!visited[node->iVex])
        {
            DFS(node->iVex, visited);
        }
        node = node->nextEdge;
    }
}

//深度优先搜索遍历图
void ListUDG::DFS()
{
    int i = 0;
    int visited[MAX] = {0};
    cout << "DFS:";
    for (i = 0; i < mVexNum; ++i)
    {
        if (!visited[i])
        {
            DFS(i, visited);
        }
    }
    cout << endl;
}

void ListUDG::BFS()
{
    int visited[MAX] = {0};
    int i, j, k;
    std::queue<int> queue;
    ENode *node;
    cout << "BFS:";
    for (i = 0; i < mVexNum; ++i)
    {
        if (!visited[i])
        {
            visited[i] = 1;
            cout << mVexs[i].data << " ";
            queue.push(i);
        }
        while (!queue.empty())
        {
            j = queue.front(); //出队列
            queue.pop();
            node = mVexs[j].firstEdge;
            while (node != NULL)
            {
                k = node->iVex;
                if (!visited[k])
                {
                    visited[k] = 1;
                    cout << mVexs[k].data << " ";
                    queue.push(k);
                }
                node = node->nextEdge;
            }
        }
    }
    cout << endl;
}

void ListUDG::print()
{
    ENode *node;
    for (int i = 0; i < mVexNum; ++i)
    {
        cout << i << "(" << mVexs[i].data << "):";
        node = mVexs[i].firstEdge;
        while (node != NULL)
        {
            cout << node->iVex << "(" << mVexs[node->iVex].data << ")";
            node = node->nextEdge;
        }
        cout << endl;
    }
}

int main(int argc, char **argv)
{
    char vexs[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
    char edges[][2] = {
        {'A', 'C'},
        {'A', 'D'},
        {'A', 'F'},
        {'B', 'C'},
        {'C', 'D'},
        {'E', 'G'},
        {'F', 'G'}};
    int vNum = sizeof(vexs) / sizeof(vexs[0]);
    int eNum = sizeof(edges) / sizeof(edges[0]);
    //1. 根据提供的数据生成
    ListUDG ludg(vexs, vNum, edges, eNum);
    ludg.print();
    ludg.DFS();
    ludg.BFS();
    //2. 手动输入
    // ListUDG ludg;
    // ludg.print();
    return 0;
}

运行结果如下:

0(A):2(C)3(D)5(F)
1(B):2(C)
2(C):0(A)1(B)3(D)
3(D):0(A)2(C)
4(E):6(G)
5(F):0(A)6(G)
6(G):4(E)5(F)
DFS:A C B D F G E 
BFS:A C D F B G E 

注:关于有向图的邻接矩阵和邻接表的 DFS 和 BFS 算法与无向图的完全相同,因为有向图的存储方式与无向图是完全相同的,只不过有向图存储的是只是单向而已。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值