c++图论————图的基本与遍历

c++图论————图的基本与遍历

目录

一,图的基础概念

二,图的存储方式

1)领接表

2)领接矩阵

三,图的遍历

1.dfs(深度优先搜索)遍历图

2,bfs(广(宽)度优先搜索)遍历图

四,例题训练

例题一:蓝桥杯官网——帮派弟位

问题描述

输入格式

输出格式

样例输入

样例输出

代码详解:

例题二:蓝桥杯官网——可行路径的方案数

问题描述

输入格式

输出格式

输入样例

输出样例

代码详解:

注:本文所有题目均来自蓝桥杯官网公开真题,仅做算法学习,代码皆由本人做出来并附上解析!

一,图的基础概念

1.结点:图中的点,结点间由边连接。

2.出边:从 A 指向 B 的边 E,是 A 的出边;出边数量叫出度

3.入边:从 A 指向 B 的边 E,是 B 的入边;入边数量叫入度

4.度数:某个点的出边 + 入边总数(无向图中对应边数)。

5.点权:结点上的权重(可表示价值、重要程度等)。

6.边权:边上的权重。

7.有向图:图中的边是有向边(有方向)。

8.无向图:图中的边是无向边(无方向)。

9.完全图:任意两个点之间都有一条边相连。

10.连通图:任意两个点之间都可达,一般用于无向图。

11.图的存储方式

(1)主要有邻接矩阵(如用d[i][j]表示)和邻接表(常用vector或 “前向星” 实现)两种。

(2)多数情况用邻接表;邻接矩阵常用于 Floyd 算法等场景。

如图(来自蓝桥杯官网):

二,图的存储方式

1)领接表

1.存储结构:用vector<pair<int, int>> g[N]表示,其中:
        g[x]存储结点x的所有出点信息;
        g[i][j] = {first, second}:first是从i出发的某个出点的编号,second是这条边的边权。
2.示例(对应上图,未标注边权默认为 0):
g[1] = {{2, 0}, {3, 0}}(结点 1 的出点是 2、3,边权为 0);
g[6] = {{3, 7}}(结点 6 的出点是 3,边权为 7);
g[4] = {{5, 0}, {6, 0}}(结点 4 的出点是 5、6,边权为 0)。

2)领接矩阵

1.定义:用d[i][j]表示从结点i到j的边的距离(通常代表最短距离);若i到j无边,则d[i][j] = inf(无穷大)。
2.示例(对应上图):
        d[1,2] = 0(结点 1 到 2 的边权为 0);
        d[6,3] = 7(结点 6 到 3 的边权为 7);
        d[4,3] = inf(结点 4 到 3 无边)。
3.遍历出点的方式:若要枚举某个点的所有出点,需遍历所有点,再判断d[当前点][遍历点]是否不为inf。

三,图的遍历

1.dfs(深度优先搜索)遍历图

通俗的讲,就是“一条路走到黑”。

代码:

const int N = 1000l; bitset<N>vis;//vis[i]=true表示该点已走过 vector<int>g[N]; void dfs(int x) { vis[x] = true;//给该点打上标记 for (const auto y : g[x]) { if (vis[y]) continue; dfs(y); } }

2,bfs(广(宽)度优先搜索)遍历图

就是“一层层往外走,每个点只走一次”。

核心思想是:从起点出发,先访问起点的所有直接邻接节点(第一层),再依次访问每个邻接节点的邻接节点(第二层),以此类推,直到遍历完所有可达节点。

形象理解:像 “水波扩散” 一样,从起点向外逐层覆盖,能保证第一次到达某个节点时的路径是最短路径(适用于无权图 / 边权为 1 的图)。

需要用队列,代码:

​bitset<int>vis;//vis[x]=true说明已经走过 queue<int>q;//表示待拓展的点队列 while (q.size()) { // 步骤0:判断队列非空,进入循环 // 步骤1:取出当前队头节点x,并从队列中移除 int x = q.front(); q.pop(); // 步骤2:如果x已访问过,直接跳过后续逻辑,回到循环顶部 if (vis[x]) continue; // 步骤3:标记x为已访问 vis[x] = true; // 步骤4:遍历x的所有邻接节点y for (const auto& y : g[x]) { // 步骤4.1:将y入队(核心操作) q.push(y); // 步骤4.2:y入队后,代码做什么? // -->继续执行for循环的下一次迭代,遍历x的下一个邻接节点 } // 步骤5:x的所有邻接节点遍历完成后,代码回到while循环顶部 // -->重新判断q.size()是否为真,若为真则重复步骤1-5 } ​

详细图解:

//bfs详细理解: // 1 // / \ // 2 3 // \ / // 4

起点 1 入队,vis[1]=true
队列变为[1]
弹出队头 1,遍历其邻接节点 2、3:
2未访问-->vis[2] = true + 入队;
3未访问-->vis[3] = true + 入队;
队列变为[2,3]
处理 2    弹出队头 2,遍历其邻接节点 4:
4 未访问-->vis[4] = true + 入队;
队列变为[3,4]
处理 3    弹出队头 3,遍历其邻接节点 4:
4 已访问(vis[4] = true)-->跳过;
队列变为[4]

四,例题训练

例题一:蓝桥杯官网——帮派弟位

问题描述

有一个帮派,小明不学无术,混入其中。

给定一个正整数 n ,代表该帮派的总人数,并且小明的序号是 m ,给出这 n 个人中每个人的附属关系,确保给出的关系网为一棵树。帮派地位的定义是按照自己手下有多少帮众决定的,注意手下的手下也算是自己的手下。如果手下的帮众相同则按序号较小的在前面。你能帮助小明找到自己的帮派地位吗?

输入格式

第一行,两个正整数 n (1≤n≤10^5) 和 m (1≤m≤n) ,代表该帮派的总人数以及小明的序号。

接下来 n−1 行,每行两个正整数,格式如下:

  • l r (1≤l,r≤n) , 代表序号为 l 的人附属于序号为 r 的人。

输出格式

一行,包含 1 个正整数,输出按手下人数多少排序后小明的排名。

样例输入

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

样例输出

5

代码详解:

#include <iostream> #include<vector> #include<bitset> #include<algorithm> using namespace std; const int N=1e5+9; int sz[N]; vector<int>g[N]; //例题:bpdw // 题目逻辑: // 1 // / \ // 2 3 // / \ // 4 5 // / // 6 // 其实就是dfs求size子树大小-1 // 最终可得: // 1 5(1有5个小弟,1为第一关键字,5为第二关键字) // 2 3 // 3 0 // 4 0 // 5 1 // 6 0 // 然后按第二关键字降序,第一关键字升序排序: // 5 3 1 0 0 0 // 1 2 5 3 4 6 --->小明是编号4,他排行5,所以答案输出5 void dfs(int x,int p) { sz[x]=0; for(const auto &y:g[x]) { if(y==p) continue; dfs(y,x); sz[x]+=sz[y]+1;//要加一,不然全是0 } } //改写sort规则 struct Node { int id,siz; bool operator<(const Node&u)const { return siz==u.siz?id<u.id:siz>u.siz; } };//不要忘了结构体的分号! int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n,m;cin>>n>>m; for(int i=1;i<n;i++) { int l,r;cin>>l>>r; g[r].push_back(l);//表示r是l的小弟 } dfs(1,0);//计算小弟数量 vector<Node>v; for(int i=1;i<=n;i++) { v.push_back({i,sz[i]}); } sort(v.begin(),v.end()); for(int i=0;i<n;i++) { if(v[i].id==m) { cout<<i+1<<'\n'; } } return 0; }

这小明就是弟中之弟O(∩_∩)O哈哈~。

例题二:蓝桥杯官网——可行路径的方案数

问题描述

有 n 个城市,这些城市间有 m 条双向路径,第 i 条路径连接城市 ai 和城市 bi​,通行时间为 1 小时。请问1--->n有多少种可行路径?由于答案可能非常大,因此将答案对 10^9+7 取模。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 ai​ 和 bi​,表示存在一条连接城市 ai​ 和城市 bi​ 的双向路径。

数据范围保证:2≤n≤2×10^5,0≤m≤2×10^5,1≤ai​<bi​≤n。

输出格式

输出一个整数,表示小蓝最快到达小桥的城市的方案数,答案对 10^9+7 取模。

输入样例

4 4 1 2 1 3 3 4 2 4 

输出样例

2

代码详解:

#include <iostream> #include<set> #include<algorithm> #include<vector> #include<queue> #include<bitset> #include<cstring>//memset头文件 using namespace std; using ll=long long; /*先建图,bfs第一次走到终点时的距离一定是最近距离,设dp[i]表示 从1到i的最近距离的路径方案数,在bfs过程中A->B更新d[B](即B到1点的最近距离)*/ //d[B]=min(d[B],d[A]+1),如果距离变小了。就重新计算dp[B]=dp[A],否则就dp[B]+=dp[A]。 //d[i]指的是索引城市i到1的最近距离!dp[i]指的是i到1的最短路径数(方案数)! //一定要注意是两个最近! const ll N=2e5+9; const ll P=1e9+7; vector<int>g[N]; ll dp[N],d[N]; void bfs(int x) { bitset<N>vis; queue<int>q; q.push(1); memset(d,0x3f,sizeof d);//初始化d无穷大! d[1]=0;//初始化1到1的距离是0 //在此题目城市间的距离默认为1 dp[1]=1;//初始化1到1的路径方案数为1 while(q.size())//只要q不为空 { int x=q.front();q.pop(); if(vis[x])continue; vis[x]=true; for(const auto &y:g[x]) { // 情况1:从1到x再到y的路径长度(d[x]+1)比已知最短距离d[y]长 --> 不是最短路径,跳过 if(d[x]+1>d[y])continue; // 情况2:从1到x再到y的路径长度等于已知最短距离d[y] --> 找到新的最短路径,累加路径数 else if(d[x]+1==d[y]) dp[y]=(dp[x]+dp[y])%P; // 情况3:从1到x再到y的路径长度比已知最短距离d[y]短 --> 更新最短距离和路径数 else { d[y] = d[x] + 1; // 更新最短距离 dp[y] = dp[x]; // 路径数继承自x(此时x是第一条最短路径的前驱) q.push(y);//最后两个条件满足其一就入栈 } } } } int main() { ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); int n,m;cin>>n>>m; while(m--) { int x,y;cin>>x>>y; g[x].push_back(y); g[y].push_back(x);//双向图 } bfs(1); cout<<dp[n]<<'\n'; return 0; }

注意:

memset(d, 0x3f, sizeof d) 的作用
1. 为什么需要这行代码?
        d[i] 表示「城市 i 到起点 1 的最短距离」,初始化时需要给所有节点的最短距离赋一个极大值(无穷大),原因是:
        初始状态下,我们只知道起点 1 到自己的距离是 0(d[1]=0),其他节点到 1 的距离是未知的,默认设为 “无穷大” 表示「尚未找到路径」;
        BFS 过程中,当第一次通过节点 x 到达节点 y 时,d[x]+1 一定小于 d[y](因为 d[y] 是无穷大),此时才能正确更新 d[y] = d[x]+1(标记为第一条最短路径)。
2. 0x3f 是什么?
        memset 是按字节赋值的函数,0x3f 是十六进制数,对应的十进制是 63,二进制是 00111111;
        d 数组的类型是 ll(long long,8 字节),memset(d, 0x3f, sizeof d) 会给 d 中每个字节都赋值为 0x3f,因此每个 ll 类型的 d[i] 的值为:
        0x3f3f3f3f3f3f3f3f(8 个 0x3f 拼接),对应的十进制约是 1e18,远大于题目中 n≤2e5 的最大可能距离(最长路径最多 2e5 步),是一个安全的 “无穷大”。
3. 为什么不用 memset(d, 0, ...) 或其他值?
        若初始化为 0:d[y] 初始为 0,而 d[x]+1 ≥1,会导致 d[x]+1 > d[y] 永远成立,无法更新任何节点的距离,逻辑完全错误;
        若初始化为其他小值(比如 1e9):虽然也能作为 “无穷大”,但 0x3f3f3f3f3f3f3f3f 是 memset 能赋出的、适合 long long 的最优极大值(不会溢出,且足够大)。

Read more

43-dify案例分享-MCP-Server让工作流秒变第三方可调用服务

43-dify案例分享-MCP-Server让工作流秒变第三方可调用服务

1.前言 之前我们为大家介绍过MCP SSE插件,它能够支持MCP-server在Dify平台上的调用,从而帮助Dify与第三方平台提供的MCP-server进行无缝对接。有些小伙伴提出了疑问:既然Dify可以通过MCP SSE插件调用其他平台的MCP-server,那么Dify的工作流或Chatflow是否也能发布为MCP-server,供其他支持MCP client的工具使用呢?今天,我们将为大家介绍一款Dify插件——mcp-server,它能够实现这一功能,即将Dify的工作流或Chatflow发布为MCP-server,供其他第三方工具调用。 插件名字叫做MCP-server,我们在dify插件市场可以找到这个工具 Mcp-server 是一个由 Dify 社区贡献的 Extension 类型插件。安装后,你可以把任何 Dify 应用转变成符合 MCP 标准的 Server Endpoint,供外部 MCP 客户端直接访问。它的主要功能包括: * **暴露为 MCP 工具:**将 Dify 应用抽象为单一 MCP 工具,供外部 MCP 客户端(如

By Ne0inhk
【MCP】详细了解MCP协议:和function call的区别何在?如何使用MCP?

【MCP】详细了解MCP协议:和function call的区别何在?如何使用MCP?

本文介绍了MCP大模型上下文协议的的概念,并对比了MCP协议和function call的区别,同时用python sdk为例介绍了mcp的使用方式。 1. 什么是MCP? 官网:https://modelcontextprotocol.io/introduction 2025年,Anthropic提出了MCP协议。MCP全称为Model Context Protocol,翻译过来是大模型上下文协议。这个协议的主要为AI大模型和外部工具(比如让AI去查询信息,或者让AI操作本地文件)之间的交互提供了一个统一的处理协议。我们常用的USB TypeC接口(USB-C)统一了USB接口的样式,MCP协议就好比AI大模型中的USB-C,统一了大模型与工具的对接方式。 MCP协议采用了C/S架构,也就是服务端、客户端架构,能支持在客户端设备上调用远程Server提供的服务,同时也支持stdio流式传输模式,也就是在客户端本地启动mcp服务端。只需要在配置文件中新增MCP服务端,就能用上这个MCP服务器提供的各种工具,大大提高了大模型使用外部工具的便捷性。 MCP是开源协议,能让所有A

By Ne0inhk
【大模型系列篇】大模型基建工程:基于 FastAPI 自动构建 SSE MCP 服务器

【大模型系列篇】大模型基建工程:基于 FastAPI 自动构建 SSE MCP 服务器

今天我们将使用FastAPI来构建 MCP 服务器,Anthropic 推出的这个MCP 协议,目的是让 AI 代理和你的应用程序之间的对话变得更顺畅、更清晰。FastAPI 基于 Starlette 和 Uvicorn,采用异步编程模型,可轻松处理高并发请求,尤其适合 MCP 场景下大模型与外部系统的实时交互需求,其性能接近 Node.js 和 Go,在数据库查询、文件操作等 I/O 密集型任务中表现卓越。 开始今天的正题前,我们来回顾下相关的知识内容: 《高性能Python Web服务部署架构解析》、《使用Python开发MCP Server及Inspector工具调试》、《构建智能体MCP客户端:完成大模型与MCP服务端能力集成与最小闭环验证》   FastAPI基础知识 安装依赖 pip install uvicorn, fastapi FastAPI服务代码示例  from fastapi import FastAPI app

By Ne0inhk
超详细图文教程:用vscode+copilot(代理模式)便捷使用mcp+一个范例:用自然语言进行3d建模

超详细图文教程:用vscode+copilot(代理模式)便捷使用mcp+一个范例:用自然语言进行3d建模

在vscode使用claude mcp吧! 在vscode更新到最新版本(注意,这是前提)后,内置的copilot可以使用mcp了!!! 关于mcp(Model Context Protocol 模型上下文协议),可以参考我的上一篇文章: MCP个人理解+示例+集成管理+在python中调用示例,给AI大模型装上双手-ZEEKLOG博客 以下是使用教程: 1.点击左下角的齿轮状设置按钮,点击设置 2.在输入面板输入chat.agent.enabled,勾上勾选框 3.点击Ctrl+shift+P,输入reload,点击重新加载窗口,刷新窗口 4.打开copilot后,在右下角将模式改为代理即可。 5.点击工具按钮,开始安装mcp 先去github找到自己想要添加的mcp服务,以blender MCP为例,打开https://github.com/ahujasid/blender-mcp,可以在readme文档里看到详细的安装过程。可以看到,

By Ne0inhk