数据结构-难点代码突破(C++实现图广度优先遍历,深度优先遍历,广度/深度优先遍历生成树和森林,广度优先遍历求单源非带权图两点最短路径)

news2024/9/25 9:29:09

注意:为了简便起见,这里采用邻接矩阵的方式储存图的边信息

文章目录

  • 1.邻接矩阵图的广度优先遍历
    • 单源非带权图的两点最短路径
  • 2. 邻接矩阵图的深度优先遍历
  • 3. 广度/深度优先遍历生成树和森林
    • 深度优先生成树与森林
    • 广度优先遍历生成树与森林

图的遍历是指从图中的某一顶点出发,按照某种搜索方法沿着图中的边对图中的所有顶点访问一次且仅访问一次。

注意到树是一种特殊的图,所以树的遍历实际上也可视为一种特殊的图的遍历。

图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。

图的遍历比树的遍历要复杂得多,因为图的任一顶点都可能和其余的顶点相邻接,所以在访问某个顶点后,可能沿着某条路径搜索又回到该顶点上。为避免同一顶点被访问多次,在遍历图的过程中,必须记下每个已访问过的顶点,为此可以设一个辅助数组visited[]来标记顶点是否被访问过。

图的遍历算法主要有两种:广度优先搜索和深度优先搜索

1.邻接矩阵图的广度优先遍历

广度优先搜索类似于二叉树的层序遍历算法。

基本思想是:

  1. 首先访问起始顶点V
  2. 接着由V出发,依次访问V的各个未访问过的邻接顶点W1, W2,…,Wi
  3. 然后依次访问W1, W2,…,的所有未被访问过的邻接顶点
  4. 再从这些访问过的顶点出发,访问它们所有未被访问过的邻接顶点直至图中所有顶点都被访问过为止。
  5. 若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作为始点,重复上述过程,直至图中所有顶点都被访问到为止。

Dijkstra单源最短路径算法和Prim最小生成树算法也应用了类似的思想。

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

广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有往回退的情况,因此它不是一个递归的算法。为了实现逐层的访问,算法必须借助一个辅助队列,以记忆正在访问的顶点的下一层顶点

这里使用C++实现从图的某个点开始进行BFS遍历

// 邻接矩阵法存储图结构

#include <iostream>
#include <assert.h>
#include <map>
#include <vector>
#include <stdio.h>

// v:图顶点保存的值。w:边的权值 max:最大权值,代表无穷。flag=true代表有向图。否则就是无向图
template <class v, class w, w max = INT_MAX, bool flag = false>
class graph
{
public:
    std::vector<v> _verPoint;            // 顶点集合
    std::map<v, int> _indexMap;          // 顶点与下标的映射
    std::vector<std::vector<w>> _matrix; // 邻接矩阵

    int _getPosPoint(const v &point)
    {
        if (_indexMap.find(point) != _indexMap.end())
        {
            return _indexMap[point];
        }
        else
        {
            std::cout << point << " not found" << std::endl;
            return -1;
        }
    }

public:
    // 根据数组来开辟邻接矩阵
    graph(const std::vector<v> &src)
    {
        _verPoint.resize(src.size());
        for (int i = 0; i < src.size(); i++)
        {
            _verPoint[i] = src[i];
            _indexMap[src[i]] = i;
        }

        // 初始化邻接矩阵
        _matrix.resize(src.size());
        for (int i = 0; i < src.size(); i++)
        {
            _matrix[i].resize(src.size(), max);
        }
    }

    // 添加边的关系,输入两个点,以及这两个点连线边的权值。
    void AddEdge(const v &pointA, const v &pointB, const w &weight)
    {
        // 获取这个顶点在邻接矩阵中的下标
        int posA = _getPosPoint(pointA);
        int posB = _getPosPoint(pointB);
        _matrix[posA][posB] = weight;
        if (!flag)
        {
            // 无向图,邻接矩阵对称
            _matrix[posB][posA] = weight;
        }
    }

    // 打印邻接矩阵
    void PrintGraph()
    {
        // 打印顶点对应的坐标
        typename std::map<v, int>::iterator pos = _indexMap.begin();
        while (pos != _indexMap.end())
        {
            std::cout << pos->first << ":" << pos->second << std::endl;
            pos++;
        }
        std::cout << std::endl;

        // 打印边
        printf("  ");
        for (int i = 0; i < _verPoint.size(); i++)
        {
            std::cout << _verPoint[i] << " ";
        }
        printf("\n");

        for (int i = 0; i < _matrix.size(); i++)
        {
            std::cout << _verPoint[i] << " ";
            for (int j = 0; j < _matrix[i].size(); j++)
            {
                if (_matrix[i][j] == max)
                {
                    // 这条边不通
                    printf("∞ ");
                }
                else
                {
                    std::cout << _matrix[i][j] << " ";
                }
            }
            printf("\n");
        }
        printf("\n");
    }
};
#include "matrix.h"
#include <queue>
#include <vector>

using namespace std;

template <class v, class w, w max = INT_MAX, bool flag = false>
void BFS(graph<v, w> &graph, const v begin)
{
    int beginPos = graph._getPosPoint(begin);
    queue<int> q;
    q.push(beginPos);
    // 标记数组
    vector<bool> visit(graph._matrix.size(), false);
    visit[beginPos] = true;
    int levelSize = 1;
    while (!q.empty())
    {
        for (int i = 0; i < levelSize; i++)
        {
            int font = q.front();
            cout << font << ":" << graph._verPoint[font];
            q.pop();

            // 这个节点的周围节点入队列
            for (int j = 0; j < graph._verPoint.size(); j++)
            {
                if (graph._matrix[font][j] != max)
                {
                    if (visit[j] == false)
                    {
                        q.push(j);
                        visit[j] = true;
                    }
                }
            }
        }
        levelSize = q.size();
        cout << "\n";
    }
    cout << "\n";
}

int main(int argc, char const *argv[])
{
    vector<char> vet = {'a', 'b', 'c', 'd'};
    graph<char, int> graph(vet);
    graph.AddEdge('a', 'd', 1);
    graph.AddEdge('c', 'b', 1);
    graph.AddEdge('c', 'd', 1);
    graph.PrintGraph();
    BFS(graph, 'b');
    return 0;
}

根据代码可知:

如果图是连通图,则上述的遍历方式可以将图全部遍历完毕。

如果不是连通图,最后看标记数组是否全部已经被标记。如果还有点没有标记,则说明这个图不是连通图。此时更改点继续遍历。

在这里插入图片描述

单源非带权图的两点最短路径

利用广度优先遍历也可以求单源非带权图的两点最短路径。

若图G = (V, E)为非带权图,定义从顶点u到顶点v的最短路径d(u, v)为从u到v的任何路径中最少的边数;若从u到v没有通路,则d(u,v)=∞。

使用BFS,我们可以求解一个满足上述定义的非带权图的单源最短路径问题,这是由广度优先搜索总是按照距离由近到远来遍历图中每个顶点的性质决定的
下面是测试代码:

#include "matrix.h"
#include <queue>
#include <vector>
#include <algorithm>

using namespace std;

template <class v, class w, w max = INT_MAX, bool flag = false>
int BFS_FindShortest(graph<v, w> &graph, const v begin, const v end)
{
    int beginPos = graph._getPosPoint(begin);
    int endPos = graph._getPosPoint(end);
    vector<int> ret(graph._verPoint.size(), max); // ret[i]为begin到i的最短路径,最后函数返回值应为ret[endPos],ret[i]=max代表begin到i不通,不是连通图
    queue<int> q;
    vector<bool> visit(graph._verPoint.size(), false);
    ret[beginPos] = 0;
    q.push(beginPos);
    int levelSize = 1;
    visit[beginPos] = true;
    int level = 1;
    while (!q.empty())
    {
        for (int i = 0; i < levelSize; i++)
        {
            int font = q.front();
            q.pop();
            for (int j = 0; j < graph._verPoint.size(); j++)
            {
                if (visit[j] == false && graph._matrix[font][j] != max)
                {
                    ret[j] = level;
                    q.push(j);
                    visit[j] = true;
                }
            }
        }
        levelSize = q.size();
        level += 1;
    }
    // 打印最短路径
    for (int i = 0; i < ret.size(); i++)
    {
        cout << graph._verPoint[beginPos] << "到" << graph._verPoint[i] << "的最短路径为" << ret[i] << std::endl;
    }
    return ret[endPos];
}

int main(int argc, char const *argv[])
{
    vector<char> vet = {'a', 'b', 'c', 'd'};
    graph<char, int> graph(vet);
    graph.AddEdge('a', 'd', 1);
    graph.AddEdge('c', 'b', 1);
    graph.AddEdge('c', 'd', 1);
    graph.AddEdge('a', 'c', 1);
    graph.PrintGraph();
    BFS_FindShortest(graph, 'a', 'd');
    return 0;
}

在这里插入图片描述

2. 邻接矩阵图的深度优先遍历

与广度优先搜索不同,深度优先搜索类似于树的先序遍历。

如其名称中所暗含的意思一样,这种搜索算法所遵循的搜索策略是尽可能“深”地搜索一个图。

它的基本思想如下:

  1. 首先访问图中某一起始顶点v
  2. 然后由v岀发,访问与v邻接且未被访问的任一顶点W
  3. 再访问与Wi邻接且未被访问的任一顶点
  4. 重复上述过程。当不能再继续向下访问时,依次退回到最近被访问的顶点
  5. 若它还有邻接顶点未被访问过,则从该点开始继续上述搜索过程,直至图中所有顶点均被访问过为止。
#include "../广度优先遍历/matrix.h"

using namespace std;

template <class v, class w, w max = INT_MAX, bool flag = false>
void _DFS(graph<v, w> &graph, int beginPos, vector<bool> &visit)
{
    cout << beginPos << ":" << graph._verPoint[beginPos] << " ";
    visit[beginPos] = true;

    // 这个点周围的点进行遍历
    for (int i = 0; i < graph._verPoint.size(); i++)
    {
        if (visit[i] == false && graph._matrix[beginPos][i] != max)
        {
            _DFS(graph, i, visit);
        }
    }
}

template <class v, class w, w max = INT_MAX, bool flag = false>
void DFS(graph<v, w> &graph, const v begin)
{
    vector<bool> visit(graph._verPoint.size(), false);
    int beginPos = graph._getPosPoint(begin);
    _DFS(graph, beginPos, visit);
    cout << "\n";
}

int main(int argc, char const *argv[])
{
    vector<char> vet = {'a', 'b', 'c', 'd'};
    graph<char, int> graph(vet);
    graph.AddEdge('a', 'd', 1);
    graph.AddEdge('c', 'b', 1);
    graph.AddEdge('c', 'd', 1);
    graph.PrintGraph();
    DFS(graph, 'a');
    return 0;
}

在这里插入图片描述

3. 广度/深度优先遍历生成树和森林

深度优先生成树与森林

深度优先搜索遍历图会产生一棵深度优先生成树。(对连通图调用DFS才能产生深度优先生成树)

否则产生的将是深度优先生成森林
eg:
在这里插入图片描述

因为图的邻接矩阵是唯一的,所以邻接矩阵的深度优先生成树是唯一的。

而图的邻接表是不唯一的,所以邻接表的深度生成树是不唯一的。

下列图片来源

深度优先生成森林步骤:

  1. 首先找到图的连通分量
  2. 每一个连通分量使用深度优先遍历生成树。
  3. 多个连通分量的深度优先生成树构成森林

在这里插入图片描述
对每个连通分量进行深度优先搜索,最终得到 3 棵生成树
在这里插入图片描述

使用深度优先搜索算法遍历非连通图,得到的生成森林就称为深度优先生成森林

根据之前学过的知识
借助孩子兄弟表示法,可以将森林中的每一棵生成树都转换为二叉树,然后再将多棵二叉树合并为一整颗二叉树

在这里插入图片描述

广度优先遍历生成树与森林

在广度遍历的过程中,我们可以得到一棵遍历树,称为广度优先生成树(连通图)

在这里插入图片描述

需要注意的是:
一给定图的邻接矩阵存储表示是唯一的,故其广度优先生成树也是唯一的
但由于邻接表存储表示不唯一,故其广度优先生成树也是不唯一的

同样的道理,用广度优先搜索算法遍历非连通图得到的生成森林又称广度优先生成森林。

对于广度优先生成树,其一般是普通的树。
通常先借助孩子兄弟表示法将普通树转换为二叉树,然后再操作二叉树实现自己想要的功能。

孩子兄弟法转化普通二叉树的方法C++代码

这两部分的C++代码比较复杂,貌似考的也不多,先空开。
后序如果在复习备考的时候遇到算法题,应该会补上。(咕咕咕)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/97815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Compose回忆童年 - 手拉灯绳-开灯/关灯

一、前言 偶然间想到小时候顺着那白色开关垂下来的灯绳&#xff0c;拉一下“咔哒”一声&#xff0c;再拉一下又是“咔哒”一声。当时年龄小感觉新奇总是把灯开了关又关了开的拉着玩&#xff0c;以至于好几次拉坏了开关灯绳。 今天我们在手机上做一个拉不坏的灯绳&#x1f604…

毕业四年,我当初是如何走上编程这条路的!

感概万千&#xff0c;毕业已达4年之久。 想起在大学时期学习编程的事情&#xff0c;感觉很有意义&#xff0c;在此记录回顾一下。 希望自己初心未变&#xff0c;勇往向前 现状与过去 20210706 目前的我是在天津一家公司做前端开发&#xff0c;主要用Python。 从毕业实习到…

入门:容器工作机制概述

我们先来看看Docker的整体架构&#xff1a; 实际上分为三个部分&#xff1a; Docker 客户端&#xff1a;也就是我们之前使用的docker命令&#xff0c;都是在客户端上执行的&#xff0c;操作会发送到服务端上处理。 Docker 服务端&#xff1a;服务端就是启动容器的主体了&#x…

Java项目:SSM网上超市购物商城管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为前后台&#xff0c;分为普通用户、管理员两种角色。前台普通用户登录&#xff0c;后台管理员登录&#xff1b; 管理员角色包含以下功…

毕业设计 单片机温湿度环境检测仪 - stm32 物联网 嵌入式

文章目录0 前言1 简介2 主要器件3 实现效果4 设计原理5 部分核心代码6 最后0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉学长…

Dynamic RSA System 设计与实现

一、背景 在实现了静态的加解密工具后&#xff0c;感觉不够灵活&#xff0c;想设计一个动态生成 RSA KeyPair 的中间系统&#xff0c;暂且称为 Dynamic RSA System&#xff0c;以达到自动化维护信安高墙的效果。 加解密和签名校验工具_余衫马的博客-CSDN博客_校验和工具RSA加…

Zero-sho原先的升级版:hourglass网络:U-Net

ZMFF: Zero-shot multi-focus image fusion &#xff08;ZMFF: Zero-shot 多聚焦图像融合&#xff09; &#xff08;本论文的先导片&#xff1a;ZERO-SHOT MULTI-FOCUS IMAGE FUSION&#xff09; 这是我们之前的扩展工作。在ZMFF,我们做一些改动和改进相比原来的框架。首先&…

数学英语不好,新手学编程难吗?适合学Python吗?

英语不好&#xff0c;上学时考试从来没有超越40分。 数学也不可&#xff0c;很多的东西都还给老师了。 我还能学习编程吗&#xff1f;&#xff1f;&#xff1f; 刚开始学习的时分&#xff0c;这个问题深深的困扰着我。以致于我其时报培训班之前犹疑了很长很长时刻。 由于在我…

[C语言数据结构]万字长文带你学习八大排序

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;数据结构 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客讲解八大排序&#xff0c;及其优化 文章目录排序Ⅰ插入排序&…

配置SSH Keys到github,实现本地操作远程仓库的权限

文章目录第1步&#xff1a;创建SSH Key第2步&#xff1a;检查是否纯正.ssh目录第3步&#xff1a;复制id_rsa.pub的内容第4步&#xff1a;在你的github页面上配置SSH key第5步&#xff1a;验证是否可连接第1步&#xff1a;创建SSH Key 在用户根目录下&#xff0c;打开命令&…

非零基础自学Golang 第10章 错误处理 10.5 Go语言错误的应用 10.6 小结 10.7 知识拓展

非零基础自学Golang 文章目录非零基础自学Golang第10章 错误处理10.5 Go语言错误的应用10.6 小结10.7 知识拓展10.7.1 error接口的应用第10章 错误处理 10.5 Go语言错误的应用 10.4.2小节中的panic()和recover()虽然能模拟其他语言的异常机制&#xff0c;但是并不建议在Go语言…

广州特耐苏-广州风淋通道构造及特点

风淋通道构造及风淋通道特点-广州特耐苏-广州风淋通道-广州风淋通道规格-广州风淋通道置量-广州风淋通道价格-特耐苏拥有*的数控加工设备&#xff0c;技术力量雄厚&#xff0c;专业生产自动化/风淋室货淋室、 >>采用更适合洁净室原理的圆弧转角; >>系统自动控制运…

密码技术扫盲:非对称加密

个人博客 密码技术扫盲&#xff1a;对称加密&#x1f3af; 密码技术扫盲&#xff1a;非对称加密密码技术扫盲&#xff1a;认证 在上一篇对称加密的介绍中&#xff0c;我们了解到对明文的加密需要使用到密钥&#xff0c;而解密时也必须用到同一把密钥&#xff0c;也就是说发送…

假如编程是魔法之零基础看得懂的Python入门教程

一、前言 几个月前编写了一份python语言入门的博文&#xff0c;近期重新审阅了一遍发现编写的质量太过随意&#xff0c;可能对于一部分人并不是非常友好&#xff0c;故此重新编写Python语言的零基础教程。 本篇教程将会尽量把一些专业术语给读者讲解清楚&#xff0c;并且让读者…

如何成为一个优秀的Python工程师?

众所周知&#xff0c;Python因其优雅而简洁的语言优势而备受程序员的青睐和追捧。随着人工智能、大数据技术的落地&#xff0c;Python工程师也成为了目前薪资待遇高&#xff0c;发展前景好的热门岗位。虽然&#xff0c;Python入门简单&#xff0c;对初学者友好&#xff0c;但是…

52 如何 尽可能的减少 自定义ClassLoader 造成的影响

前言 // 呵呵 很快又该总结 2022 了, 希望这一年也能总结出很多收获 接着 java.lang.Class/java.lang.ClassLoader/InstanceKlass/ClassloaderData 的卸载 可以先看一下 这一篇文章, 明确一下 上下文 这里 主要说的是 如果我们的场景中存在自定义的 classloader 的情况…

Flask + echarts 轻松解决 nginx 日志可视化

最近&#xff0c;线上的业务系统不太稳定&#xff0c;需要分析下访问情况&#xff0c;能拿到的数据只有 nginx 服务器的访问日志&#xff0c;不过难不倒我&#xff0c;用合适的工具&#xff0c;分分钟做出图形化展示&#xff0c;看看怎么做的吧 思路 nginx 访问日志&#xff…

9 CPP结构体注意事项

注意&#xff1a; 1 结构名是标识符 2 结构体的成员可以是任意数据类型 3 定义结构体描述的代码可以放在程序的人和地方&#xff0c;一般放在main函数的上面或头文件中。 4 结构体成员可以用C的类&#xff08;如string&#xff09;&#xff0c;但是不提倡。 5 在C中&#…

java:AES加密和解密

java&#xff1a;AES加密和解密 1 前言 对称加密&#xff0c;即单秘钥加密&#xff0c;指加密和解密的过程中&#xff0c;使用相同的秘钥&#xff0c;相比于非对称加密&#xff0c;因仅有一把钥匙&#xff0c;故而速度更快&#xff0c;更适合解密大文件&#xff08;常见于如视…

ESP32的arduino IDE代码使用flash download tool进行烧录

ESP32的arduino IDE代码使用flash download tool进行烧录前言arduino代码烧录arduino下载了一些什么文件flash download tool工具烧录总结前言 最近遇到用户在使用 arduino IDE开发环境编写了ESP32的代码&#xff0c;希望提供编写好的程序给用户烧录&#xff0c;但是又不希望让…