【数据结构与算法】图的遍历与拓扑排序

news2024/11/23 20:40:27

文章目录

  • 一、用数组模拟邻接表
  • 二、图的深度优先遍历(dfs)
    • 2.1 概念
    • 2.2 例题:树的重心
  • 三、图的广度优先遍历(bfs)
    • 3.1 概念
    • 3.2 例题:图中点的层次
  • 四、拓扑排序
    • 4.1 概念
    • 4.2 例题:有向图的拓扑序列

一、用数组模拟邻接表

再上一篇中我们讲了树的两种存储方式:【数据结构与算法】图——邻接表与邻接矩阵
这一篇我们可以用数组来模拟邻接表。
假设现在我们要进行n次操作,实现无向图。
首先需要一个保存是哪个节点的数组e然后就是类似指针数组的h,每个表都会连一串单链表e,ne

int n;

const int N = 1e5 + 10, M = 2 * N;

int h[N], e[M], ne[M], idx;

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n;
    int a, b;
    for(int i = 0; i < n; i++)
    {
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    return 0;
}

每次插入其实就是一次头插,这里要注意把h数组初始化成-1,以便标识每个链表的结尾。

二、图的深度优先遍历(dfs)

2.1 概念

我们之前接触过树的遍历,其实树就是一个特殊的图,遍历图的时候我们要注意的是有可能会遍历到我们之前遍历过的点,所以需要一个标志位数组记录遍历过的节点。
给定一个节点u,我们就把这个节点的所有直接相连的点(没有被遍历过的)都进行dfs搜索

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

void dfs(int u)
{
    cnt[u] = true;
    cout << u << " ";
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(!cnt[j])
        {
            dfs(j);
        }
    }
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n;
    int a, b;
    for(int i = 0; i < n; i++)
    {
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    dfs(1);
    return 0;
}

在这里插入图片描述
可以看到所有的点都完成了遍历。

2.2 例题:树的重心

题目描述:

给定一颗树,树中包含 n个结点(编号 1∼n)和 n−1条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式

第一行包含整数 n,表示树的结点数。接下来 n−1行,每行包含两个整数 a和 b,表示点 a
和点 b之间存在一条边。

输出格式

输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围

1≤n≤105

输入样例

9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6

输出样例:

4

思路分析:
先解释一下这句话:如果将这个点删除后,剩余各个连通块中点数的最大值
举个例子:
在这里插入图片描述
对于点F,把他删除后,会有四个连通块。
在这里插入图片描述
它的最大值就是6。而这个题是要把每个节点的这个最大值求出来,然后取最小值。

那么怎么求呢?

还是以F点举例子,假设现在我们从上面遍历到了F点,下面的几个点好求,但是上面的已经不能再回去了,但是我们知道了一共有多少个节点,我们只需要把下面的节点个数加起来然后再用总数减去即可得到上面有多少个节点,然后取最大值。

#include <iostream>
#include <cstring>

using namespace std;

int n;

const int N = 1e5 + 10, M = 2 * N;
int ans = N;

int h[N], e[M], ne[M], idx;
bool cnt[N];

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int dfs(int u)
{
    cnt[u] = true;
    int sum = 1;// 包括当前节点
    int ret = 0;// 记录最大的连通块
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(!cnt[j])
        {
            int tmp = dfs(j);
            sum += tmp;
            ret = max(ret, tmp);
        }
    }
    ret = max(ret, n - sum);
    ans = min(ans, ret);
    return sum;
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n;
    int a, b;
    for(int i = 0; i < n; i++)
    {
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    dfs(1);
    cout << ans << endl;
    return 0;
}

三、图的广度优先遍历(bfs)

3.1 概念

我们知道广度优先遍历要借助队列结构,当一个节点入队列后,把所有的相连的点入队列。知道队列为空,还是跟上面一样,防止重复遍历,要加一个标志位。

int n, m;

const int N = 1e5 + 10;

int h[N], e[N], ne[N], idx;
queue<int> q;
bool cnt[N];

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

void bfs(int u)
{
    cnt[u] = true;
    q.push(u);
    while(q.size())
    {
        int t = q.front();
        q.pop();
        cout << t << " ";
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(!cnt[j])
            {
                q.push(j);
                cnt[j] = true;
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m;
    int a, b;
    while(m--)
    {
        cin >> a >> b;
        add(a, b);
    }
    bfs(1);
    return 0;
}

在这里插入图片描述

3.2 例题:图中点的层次

题目描述:

给定一个 n个点 m条边的有向图,图中可能存在重边和自环。所有边的长度都是 1,点的编号为 1∼n。请你求出 1号点到 n号点的最短距离,如果从 1号点无法走到 n号点,输出 −1。

输入格式

第一行包含两个整数 n和 m。
接下来 m行,每行包含两个整数 a和 b,表示存在一条从 a走到 b的长度为 1的边。

输出格式

输出一个整数,表示 1号点到 n号点的最短距离。

数据范围

1≤n,m≤105

输入样例:

4 5
1 2
2 3
3 4
1 3
1 4

输出样例:

1

思路分析:

广度优先遍历的话,第一次遇到n节点的距离一定是最短距离,所以我们这里可以把cnt数组变成d数组,代表每个点离起始位置的距离,先全部初始化成-1,当节点要入队列时,如果距离是-1,说明没有遍历过,那么就入队列,然后改变当前点的距离。

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

int n, m;

const int N = 1e5 + 10;

int h[N], e[N], ne[N], idx;
queue<int> q;
int d[N];

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

void bfs(int u)
{
    d[u] = 0;
    q.push(u);
    while(q.size())
    {
        int t = q.front();
        q.pop();
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(d[j] == -1)
            {
                q.push(j);
                d[j] = d[t] + 1;
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof h);
    memset(d, -1, sizeof d);
    cin >> n >> m;
    int a, b;
    while(m--)
    {
        cin >> a >> b;
        add(a, b);
    }
    bfs(1);
    cout << d[n] << endl;
    return 0;
}

四、拓扑排序

4.1 概念

首先要知道什么是拓扑排序:
在一个有向图中,对所有的节点进行排序,要求没有一个节点指向它前面的节点
先统计所有节点的入度,对于入度为0的节点就可以分离出来,然后把这个节点指向的节点的入度减一。
一直做改操作,直到所有的节点都被分离出来。
如果最后不存在入度为0的节点,那就说明有环,不存在拓扑排序,也就是很多题目的无解的情况。
这里提到了入度的概念,当前点的入度其实就是有多少个节点指向当前节点。
我们以入度为0的节点为突破口,一层一层剥开,如果没有环,则一定能够把所有点的入度减为0。

4.2 例题:有向图的拓扑序列

题目描述:

给定一个 n个点 m条边的有向图,点的编号是 1到 n,图中可能存在重边和自环。
请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。
若一个由图中所有点构成的序列 A满足:对于图中的每条边 (x,y),x在 A中都出现在 y之前,则称 A是该图的一个拓扑序列。

输入格式

第一行包含两个整数 n 和 m。
接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。

输出格式

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。
否则输出 −1。

数据范围

1≤n,m≤105

输入样例:

3 3
1 2
2 3
1 3

输出样例:

1 2 3

思路分析

这道题我们也是用广度优先遍历的思想,先找到所有入度为0的点,全部入队列,然后一层层的遍历,把入度为0的点剥离,把该节点指向的节点入度减去1,只要入度为0,就入队列,如此循环即可。
然后出队列的元素顺序即为拓扑排序。我们只需要记录入队列的数据个数等不等于总节点个数就可以判断是否是拓扑序列。

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int h[N], e[N], ne[N], idx;
int in[N];// 入边
queue<int> q;
int res[N];

int n, m, cnt;

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

void topology()
{
    for(int i = 1; i <= n; i++)
    {
        if(in[i] == 0)
        {
            q.push(i);
        }
    }
    while(q.size())
    {
        int t = q.front();
        q.pop();
        res[cnt++] = t;
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            in[j]--;
            if(in[j] == 0)
            {
                q.push(j);
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    cin >> n >> m;
    int a, b;
    while(m--)
    {
        cin >> a >> b;
        add(a, b);
        in[b]++;
    }
    
    topology();
    
    if(cnt == n)
    {
        for(int i = 0; i < n; i++)
        {
            cout << res[i] << " ";
        }
    }
    else
    {
        cout << -1;
    }
    return 0;
}


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

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

相关文章

2直接连接的网络与VLAN划分-2.2【实验】【计算机网络】

2直接连接的网络与VLAN划分-2.2-2.3【实验】【计算机网络】 前言推荐2直接连接的网络与VLAN划分2.1共享式以太网和交换式以太网2.2交换机MAC地址表建立与帧转发实验目的实验内容及实验环境实验原理交换机通过逆向学习帧的源地址&#xff0c;构建MAC地址表;交换机中MAC地址的构建…

netty基础(五)

1.基本介绍 1>.异步的概念和同步相对.当一个异步过程调用发出后,调用者不能立刻得到结果.实际处理这个调用的组件在完成后,通过状态、通知和回调来通知调用者; 2>.Netty中的I/O操作是异步的,包括Bind、Write、Connect等操作会简单的返回一个 ChannelFuture; 3>.调用…

二、Eureka注册中心

目录 注册中心主要有三种角色&#xff1a; 1、服务提供者&#xff08;RPC Server&#xff09; 2、服务消费者&#xff08;RPC Client&#xff09; 3、服务注册中心&#xff08;Registry&#xff09; 注册中心实现原理 常用的注册中心&#xff1a; 常用注册中心对比&…

【C/C++每日一练】总目录(更新至2023.5.12)

C/C 2023.05 2023.5.11-2023.5.12 20230512 1. 成绩打印 ※ 2. 按要求补齐数组 &#x1f31f;&#x1f31f;&#x1f31f; 3. 水仙花数 ※ 20230511 1. 最长公共前缀 &#x1f31f; 2. 打家劫舍 &#x1f31f;&#x1f31f; 3. 最接近的三数之和 &#x1f31f;&am…

修改通过CDH安装的Kafka和Zookeeper等组件的最大文件句柄数

目录 1.查看当前kafka最大文件句柄数 2.修改supervisord.service 服务配置文件 3.加载配置文件 4.重启supervisord.service服务 5.重启kafka等组件 6.查看修改后最大文件句柄数 7.查看kafka当前使用的文件句柄数 参考&#xff1a; 1.查看当前kafka最大文件句柄数 jpsc…

【rust】| 05——语法基础 | 流程控制

系列文章目录 【rust】| 00——开发环境搭建 【rust】| 01——编译并运行第一个rust程序 【rust】| 02——语法基础 | 变量(不可变?)和常量 【rust】| 03——语法基础 | 数据类型 【rust】| 04——语法基础 | 函数 【rust】| 05——语法基础 | 流程控制 文章目录 流程控制1. 条…

双向链表的功能实现

前言&#xff1a;我们已经学习并知道了单链表的实现&#xff0c;链表的实现方式不只是单链表一种&#xff0c;今天我们就来介绍一种新的结构&#xff0c;就是双链表结构&#xff0c;本质上是将节点间进行双向链接&#xff0c;从而使一些操作更加容易实现。 目录 1.双向链表的简…

OpenCL编程指南-3.3类型转换

隐式类型转换 隐式类型转换是一种自动的类型转换&#xff0c;只要混合使用不同的类型&#xff0c;编译器就会完成这种隐式类型转换。这里支持表4-1中定义的标量类型&#xff08;除void、double和half以外&#xff09;的隐式转换。完成隐式转换时&#xff0c;并不只是重新解释一…

MySQL:数据库的增删查改

我们这一篇主要介绍数据库的增删查改~ 增&#xff1a;insert into 表名 value (); 删&#xff1a;delete from 表名; 查&#xff1a;select from 表名; 改&#xff1a;update 表名; 目录 1.insert&#xff08;增&#xff09; 2.select&#xff08;查询&#xff09; 2.1 全列…

Goby 漏洞更新 |Telesquare TLR-2005Ksh 路由器 ExportSettings.sh 文件下载漏洞(CVE-2021-46423)

漏洞名称&#xff1a;Telesquare TLR-2005Ksh 路由器 ExportSettings.sh 文件下载漏洞&#xff08;CVE-2021-46423&#xff09; English Name&#xff1a;Telesquare TLR-2005Ksh ExportSettings.sh file download (CVE-2021-46423) CVSS core: 7.5 影响资产数&#xff1a;2…

勒索病毒“顽疾”,没有“特效药”吗?

基础设施瘫痪、企业和高校重要文件被加密、毕业论文瞬间秒没……这就是六年前的今天&#xff0c;WannaCry勒索攻击爆发时的真实场景。攻击导致150多个国家数百万台计算机受影响&#xff0c;也让勒索病毒首次被全世界广泛关注。 六年后&#xff0c;勒索攻击仍是全球最严重的网络…

bootp引导程序协议

bootp又称为引导程序协议,我们来简单了解一下这个协议以及他的用法。 1&#xff0c;BOOTP 请求和应答均被封装在 U D P数据报中 &#xff1b; 2&#xff0c;B O O T P使用 U D P&#xff0c;且通常需与 T F T P协同工作&#xff1b; 3&#xff0c;B O O T P有两个熟知端口&a…

选择合适的 MQTT 云服务:一文了解 EMQX Cloud Serverless、Dedicated 与 BYOC 版本

引言 EMQX Cloud 是基于 EMQX Enterprise 构建的一款全托管云原生 MQTT 消息服务。为了满足不同客户的需求&#xff0c;EMQX Cloud 提供了三种版本供客户选择&#xff1a;Serverless 版、专有版和 BYOC 版。 本文将简要介绍这三个版本的核心区别&#xff0c;并通过三个用户故…

【ChatGPT】体验一下ChatGPT

体验一下ChatGPT 可以帮你写代码、写邮件、编故事的神器 最近OpenAI 发布了备受期待的原型通用 ChatGPT&#xff0c;这是一种基于对话的 AI 聊天界面&#xff0c;算是GPT-3(Generative Pre-trained Transformer 3)的继承者&#xff0c;今天记录一下体验的过程&#xff0c;以前…

详解set/map的底层结构——AVL树和红黑树

目录 前文 一&#xff0c;AVL树 1.1 什么是AVL树&#xff1f; 1.2 AVL树节点的定义 1.3 insert—插入(重点) 1.4 旋转(重点) 1.4.1 右单旋 1.4.2 左单旋 1.4.3 左右双旋 1.4.4 右左双旋 1.5 IsBalanc(平衡判断) 1.6 中序遍历 1.7 测试 二&#xff0c;红黑树 2.1 什么…

非常提效的7款原型工具推荐

原型图工具允许在开发前进行测试和迭代过程&#xff0c;可以帮助节省大量的开发时间和成本。在本文中&#xff0c;我们盘点了7个易于使用的原型图工具&#xff0c;以提高您的生产力&#xff01; 1.即时设计 即时设计是一款免费的在线 UI 设计工具&#xff0c;无系统限制&…

自学黑客,一般人我劝你还是算了吧!

我为啥说自学黑客&#xff0c;一般人我还是劝你算了吧&#xff01;因为我就是那个不一般的人。 首先我谈下对黑客&网络安全的认知&#xff0c;其实最重要的是兴趣热爱&#xff0c;不同于网络安全工程师&#xff0c;他们大都是培训机构培训出来的&#xff0c;具备的基本都是…

【Python入门】Python的判断语句(if elif else语句)

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Python零基础入门系列&#xff0c;本专栏主要内容为Python基础语法、判断、循环语句、函…

从0开始学习数据库

一个数据库最重要的部分是什么&#xff1f; 关系型数据库mysql有着四大特性&#xff0c;原子性&#xff0c;隔离性&#xff0c;一致性&#xff0c;持久性。 kv数据库有着原子性&#xff0c;持久性&#xff0c;弱一致性。 可见&#xff0c;不管数据库的存储引擎是什么&#xff0…

【计算机网络】第一章 计算机网络基础(期末急救包)

目录 前言 正文 考点 1.1 计算机网络组成 1.2 计算机网络的分类 ——4种 1.3计算机网络的性能指标 1.4 计算机网络标准化工作及相关组织 2.计算机网络体系结构与模型 结语 前言 期末将至&#xff0c;相信有的同学们还在为怎么过期末而发愁吧&#xff01;不用担心&#…