基本概念
图(Graph)是一种非线性数据结构,由顶点(Vertex)和边(Edge)组成。顶点表示实体,边表示实体之间的关系。
图的分类
按方向性分类
- 无向图(Undirected Graph):边没有方向,表示双向关系。例如社交网络中的好友关系。
- 有向图(Directed Graph/Digraph):边有方向,表示单向关系。例如网页链接的指向关系。
按权重分类
- 无权图(Unweighted Graph):边没有权重,仅表示连接关系。
- 加权图(Weighted Graph):边带有权重,表示关系的强度或成本。例如交通网络中的距离或时间。
图的实现
1.邻接矩阵(稠密图)
邻接矩阵适合稠密图(边数接近顶点数的平方)或需要频繁查询边是否存在的情况。
是一个 n×n 的方阵(n 为顶点数),记为 A,其中:
- 行和列分别对应图中的顶点;
- 元素
A[i][j]表示「顶点 i 到顶点 j」的连接关系:- 无向图 / 有向图(无权):
A[i][j] = 1表示有边,A[i][j] = 0表示无边; - 带权图:
A[i][j] = 权重值(如距离、成本),无边时记为0或∞(无穷大,表示不可达);
- 无向图 / 有向图(无权):
int graph[MAXN][MAXN];
//以无向图为例
int main() {
int n, m;
cin >> n >> m;
//初始化邻接矩阵
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
//自己到自己为0,其它初始化为无穷大
if (i == j) grpah[i][j] = 0;
else graph[i][j] = INT_MAX;
}
}
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
//无向图
graph[u][v] = min(graph[u][v], w);
graph[v][u] = min(graph[v][u], w);
}
}
2.邻接表(稀疏图)
邻接表适合稀疏图(边数远小于顶点数的平方)或需要高效遍历邻接节点的场景。
是一个数组链表(数组+链表/动态数组),其中:
- 用一个「数组」存储所有顶点(数组下标对应顶点编号,数组元素是链表头);
- 每个顶点对应的「链表」,存储该顶点的「相邻顶点」(无向图是直接相邻,有向图是出边指向的顶点,带权图还需存储权重)。
//graph[u]储存所有结点 u 可以到达的结点
vector<vector<int>> graph(MAXN);
//以无向图为例
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
graph[v].push_back(u);
}
}
//也可以储存有权图,但不太建议,空间复杂度太高
struct node {
int v, w;
};
vector<vector<node>> graph(MAXN);
3.链式前向星(对有权图非常好用)
模拟链表,采用头插法,以边为单位,记录每一条边的目标点,其中:
- edge数组存放所有边的数据
- head数组中,索引表示结点,索引上的数据表示以索引结点为起点的边(在edge数组中的下标)
struct Edge {
int to; //第i条边的终点
int w; //第i条边的权值
int next; //与第i条边同起点的下一条边的位置
} edge[MAXM]; //边数组
//头数组(结点数组), 最近一次输入的以i为起点的边在edge数组中的下标
vector<int> head(MAXN, -1);
void addEdge(int u, int v, int w) {
edge[cnt].to = v; //cnt为edge索引
edge[cnt].w = w;
//头插法
edge[cnt].next = head[u];
head[u] = cnt++; //更新头节点
}
void print(vector<int>& head, int n) {
//n个结点
for (int i = 1; i <= n; i++) {
cout << i << endl;
for (int j = head[i]; j != -1; j = edge[j].next) {
cout << i << " " << edge[j].to << " " << edge[j].w << endl;
}
cout << endl;
}
}
图的算法
图的遍历
1.广度优先遍历(BFS)
广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,并一层层向外扩张。
基本模版
//用二维数组储存图结构
vector<vector<int>> graph(MAXN);
//以 u 为起始结点的广度优先遍历
void BFS(int u) {
queue<int> q;
//标记结点是否被访问
vector<int> visited(MAXN, false);
q.emplace(u);
visited[u] = true;
cout << u << " ";
while (!q.empty()) {
int cur = q.front();
q.pop();
//添加 cur 相连的结点
for (int v : graph[cur]) {
if (!visited[v]) {
q.emplace(v);
visited[v] = true;
cout << v << " ";
}
}
}
}
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
//graph[v].push_back(u); 双向图
}
BFS(1);
}
例题P11046 [蓝桥杯 2024 省 Java B] 星际旅行 - 洛谷
https://www.luogu.com.cn/problem/P11046该题需要注意数据的范围,结点数 < 查询的次数,而且结点个数的数量级在1e3,那么就可以使用离线方式处理,算出所有结点,建立查询表(counter),最后只需要查询就可以得出答案。
#include <bits/stdc++.h>
#define MAXN 1005
#define MAXM 5 * MAXN
using namespace std;
//邻接表储存图结构
vector<vector<int>> graph(MAXN);
//记录每个结点 i 传送 j 次可以到达的星球个数 counter[i][j]
vector<vector<int>> counter(MAXN, vector<int>(MAXM, 0));
//以 u 为起始点的广度优先搜索
void bfs(int u) {
int cnt = 0;
//记录 u 到结点 i 需要的次数 dist[i]
vector<int> dist(MAXN, INT_MAX);
dist[u] = 0;
queue<int> q;
q.emplace(u);
counter[u][0] = 1;
while (!q.empty()) {
int cur = q.front();
q.pop();
for (int v : graph[cur]) {
//如果 dist[v] 次数没有改变(即 INT_MAX),代表没有被访问
if (dist[v] == INT_MAX) {
//cur 到 v 进行一次传送
dist[v] = dist[cur] + 1;
//以 u 为起始点传送 dist[v] 次才能到达的星球个数
counter[u][dist[v]]++;
q.emplace(v);
}
}
}
}
int main() {
int n, m, Q;
cin >> n >> m >> Q;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
graph[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
//对每个结点进行广度优先搜索
bfs(i);
for (int j = 1; j <= n; j++) {
//传送 j 次包括比 j 更小的传送次数
//原来的 counter[i][j] 表达的是恰好传送 j 次才能到达的星球个数
//更新后的 counter[i][j] 表达的是传送 j 次共能到达的星球个数
counter[i][j] += counter[i][j-1];
}
}
double ans = 0.0;
for (int i = 0; i < Q; i++) {
int x, y;
cin >> x >> y;
//直接查询 counter
ans += counter[x][y];
}
printf("%.2lf", ans / Q);
}
2.深度优先遍历(DFS)
深度优先遍历是一种优先走到底、无路可走再回头的遍历方式。
//用二维数组储存图结构
vector<vector<int>> graph(MAXN);
vector<bool> visited(MAXN, false);
//以 u 结点为起点的深度优先遍历
void DFS(int u) {
if (visited[u]) return;
visited[u] = true;
cout << u << " ";
for (int v : graph[u]) {
DFS(v);
}
}
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
//graph[v].push_back(u); 双向图
}
DFS(1);
}
例题:
200. 岛屿数量
https://leetcode.cn/problems/number-of-islands/description/
class Solution {
public:
//岛屿链接的方向
vector<vector<int>> directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
int numIslands(vector<vector<char>>& grid) {
int m = grid.size(), n = grid[0].size();
int cnt = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
cnt++;
dfs(grid, i, j);
}
}
}
return cnt;
}
//以 i, j 为起点用 dfs 遍历岛屿
void dfs(vector<vector<char>>& grid, int i, int j) {
int m = grid.size(), n = grid[0].size();
//如果是岛屿,赋值为 '0' (或其他的值也行),表示已访问过。
if (grid[i][j] == '1') {
grid[i][j] = '0';
}
else {
return ;
}
for (vector<int> d : directions) {
int x = i + d[0], y = j + d[1];
if (x < 0 || y < 0 || x >= m || y >= n) {
continue;
}
dfs(grid, x, y);
}
}
};
拓补排序
拓补排序是一个有向无环图的所有顶点得线性序列。
且序列必须满足下面两个条件:
1.每个顶点出现且只出现一次。
2.若存在一条从顶点A到顶点B的路径,那么在序列中顶点A出现在顶点B的前面。
简单来说就是每次选取入度最小的顶点加入线性序列中,再从图中删除该顶点及该顶点的边。
例题及模版
B3644 【模板】拓扑排序 / 家谱树
https://www.luogu.com.cn/problem/B3644
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
//记录 i 的入度
vector<int> head(n+1);
//记录 i 的出边
vector<vector<int>> graph(n+1);
for (int i = 1; i <= n; i++) {
int v;
cin >> v;
while (v != 0) {
//i -> v
graph[i].push_back(v);
//v 的入度+1
head[v]++;
cin >> v;
}
}
//每次找到入度最小的结点,再更新入度数组
for (int i = 1; i <= n; i++) {
int idx = min_element(head.begin()+1, head.end()) - head.begin();
cout << idx << " ";
for (int v : graph[idx]) {
head[v]--;
}
//删除已选择的结点
head[idx] = INT_MAX;
}
}
最小生成树
最小生成树详细解释可以看图-最小生成树-Prim(普里姆)算法和Kruskal(克鲁斯卡尔)算法_哔哩哔哩_bilibili
最小生成树模版题
洛谷-P3366 【模板】最小生成树
https://www.luogu.com.cn/problem/P3366
1.prim最小生成树(加点法)
不断选择距离已选择路径最小的节点,并将其加入到该路径中,最终得到的路径就是最小生成树。
(下面的代码为使用prim算法的解答,可以先自己写一遍)
#include <bits/stdc++.h>
#define MAXM 200005
using namespace std;
//基于链式前向星实现
struct Edge {
int to, w, next;
} edge[MAXM<<1]; //无向图, 开两倍数组
void addEdge(int cnt, int u, int v, int w, vector<int>& head) {
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
//最小生成树
int prim(vector<int>& head, int n) {
int ans = 0;
int s = 1;
vector<int> dist(n+1, INT_MAX); //点到生成树的距离
vector<bool> visitedFlag(n+1, false);
dist[s] = 0;
visitedFlag[s] = true;
for (int i = head[s]; i != -1; i = edge[i].next) {
int v = edge[i].to;
int w = edge[i].w;
//更新剩余点到生成树的距离
//同时可以处理重复边的输入, 选择最小的权值
dist[v] = min(dist[v], w);
}
for (int i = 1; i < n; i++) {
int w = INT_MAX, vet = 0;
for (int j = 1; j < n+1; j++) {
//到最小生成树权值最小且未被访问
if (dist[j] < w && !visitedFlag[j]) {
w = dist[j];
vet = j;
}
}
//没有找到可以连接的节点, 表示未连通
if (vet == 0) return INT_MAX;
ans += w;
dist[vet] = 0;
visitedFlag[vet] = true;
for (int j = head[vet]; j != -1; j = edge[j].next) {
int v = edge[j].to;
int w = edge[j].w;
dist[v] = min(dist[v], w);
}
}
return ans;
}
int main() {
int n, m;
cin >> n >> m;
vector<int> head(n+1, -1);
for (int i = 0; i < 2*m; i+=2) {
int u, v, w;
cin >> u >> v >> w;
addEdge(i, u, v, w, head);
addEdge(i+1, v, u, w, head);
}
int ans = prim(head, n);
if (ans == INT_MAX) {
cout << "orz" << endl;
}
else {
cout << ans << endl;
}
}
2.kruskal最小生成树(加边法)
先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量比总点数少一即可。
并查集有关的知识可以去看我写的博客,也可以自己去搜,知道基本模版就行。
(下面的代码为使用kruskal算法的解答,建议自己先写一遍,比prim算法要简单一点)
#include <bits/stdc++.h>
#define MAXM 200005
#define MAXN 5000
using namespace std;
//基于并查集
struct Edge {
int u, v, w;
} edge[MAXM];
int parent[MAXN];
//sort比较函数
bool cmp(Edge a, Edge b) {
return a.w < b.w;
}
//并查集查询
int find(int x) {
if (parent[x] < 0)
return x;
return parent[x] = find(parent[x]); //路径优化
}
//并查集合并
void merge(int x, int y) {
int root1 = find(x);
int root2 = find(y);
if (root1 == root2)
return ;
//按秩优化(按结点数)
if (abs(parent[root1]) < abs(parent[root2])) {
parent[root2] += parent[root1];
parent[root1] = root2;
}
else {
parent[root1] += parent[root2];
parent[root2] = root1;
}
}
//kruskal最小生成树
int kruskal(int n, int m) {
int ans = 0;
int cnt = 1;
for (int i = 0; i < m; i++) {
Edge e = edge[i];
int u = e.u, v = e.v, w = e.w;
//判断是否为同一集合
if (find(u) == find(v)) {
continue;
}
//添加新边
ans += w;
cnt ++;
merge(u, v);
if (cnt == n) break;
}
//存在独立结点,无法形成通路
if (cnt != n) {
ans = INT_MAX;
}
return ans;
}
int main() {
int n, m;
cin >> n >> m;
//初始化并查集
for (int i = 1; i <= n; i++) {
parent[i] = -1;
}
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> edge[i].u >> edge[i].v >> edge[i].w;
}
//对edge数组按权值从小到大排序
sort(edge, edge+m, cmp);
int res = kruskal(n, m);
if (res == INT_MAX) {
cout << "orz" << endl;
}
else {
cout << res << endl;
}
}
最短路径
1.无权图最短路径(迷宫类型)
无权图路径题目适合用图的遍历,DFS或BFS,两者有所侧重,
DFS适合用于寻找一条路径,但不一定是最短路径。实现相对简单,但在复杂迷宫中可能会走很多冤枉路,时间复杂度较高。
DFS例题:
P2196 [NOIP 1996 提高组] 挖地雷
https://www.luogu.com.cn/problem/P2196需要找到所有可能的路径,并实时更新最大的地雷数,更适合使用dfs去找每一个路径。
#include <bits/stdc++.h>
using namespace std;
//储存图结构
vector<vector<int>> graph(25);
//地雷数
vector<int> weight(25);
//判断是否访问
vector<bool> visited(25, false);
//最终的路径方案
vector<int> solution;
//最大地雷数
int ans = 0;
//基本上就是dfs模版
void dfs(int i, int cnt, vector<int> cur) {
cnt += weight[i];
visited[i] = true;
cur.push_back(i);
//更新地雷数以及路径
if (cnt > ans) {
ans = cnt;
solution = cur;
}
for (int to : graph[i]) {
if (visited[to]) continue;
dfs(to, cnt, cur);
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> weight[i];
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= n-i; j++) {
int tmp;
cin >> tmp;
//存在路径就添加到数组中
if (tmp == 1) {
graph[i].push_back(j+i);
}
}
}
for (int i = 1; i <= n; i++) {
dfs(i, 0, {});
//还原visited,重新设置为未访问
visited.assign(visited.size(), false);
}
//输出答案
for (int d : solution) {
cout << d << " ";
}
cout << endl;
cout << ans << endl;
}
BFS适合用于寻找最短路径,保证第一次找到的路径就是最短路径。实现相对复杂,需要更多的内存来储存结点。
BFS例题及模版:
P1135 奇怪的电梯
https://www.luogu.com.cn/problem/P1135
#include <bits/stdc++.h>
using namespace std;
//bfs模版 s -> e
void bfs(vector<vector<int>>& graph, int s, int e) {
int n = graph.size();
//ans[i] 为 s 到 i 结点按按钮的次数
vector<int> ans(n, -1);
queue<int> q;
ans[s] = 0;
q.emplace(s);
while (!q.empty()) {
int cur = q.front();
q.pop();
if (cur == e) {
break;
}
for (int v : graph[cur]) {
//如果ans[v]为初始值时表示未访问
if (ans[v] == -1) {
ans[v] = ans[cur] + 1;
q.emplace(v);
}
}
}
cout << ans[e] << endl;
}
int main() {
int n, a, b;
cin >> n >> a >> b;
vector<vector<int>> graph(n+1);
for (int i = 1; i <= n; i++) {
int k;
cin >> k;
//第 i 层楼可以到达的楼层
int up = i+k, down = i-k;
if (up <= n) {
graph[i].push_back(up);
}
if (down >= 1) {
graph[i].push_back(down);
}
}
bfs(graph, a, b);
}
二维BFS
基本思想是一样的,主要难点在于需要记录的数据较多,包括要激励前驱结点。
P10234 [yLCPC2024] B. 找机厅
https://www.luogu.com.cn/problem/P10234
#include <bits/stdc++.h>
using namespace std;
//每次移动的方向
//D R U L
vector<vector<int>> directions = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
string methods = "DRUL";
//输出路径
void printRoute(vector<vector<int>>& route, int i, int j) {
if (i == 0 && j == 0) return;
//倒推找上一步所在的位置
int d = route[i][j];
if (d == 0) printRoute(route, i-1, j);
else if (d == 1) printRoute(route, i, j-1);
else if (d == 2) printRoute(route, i+1, j);
else printRoute(route, i, j+1);
//再从最开始依次输出
cout << methods[d];
}
//广度优先遍历求最短路径
void bfs(vector<string>& graph) {
int n = graph.size(), m = graph[0].size();
//记录路径(route[i][j] 表示上一步的方向)
vector<vector<int>> route(n, vector<int>(m, -1));
//记录到达 i, j 所花的时间
vector<vector<int>> ans(n, vector<int>(m, -1));
//4 表示没有上一步,即为起点
route[0][0] = 4;
ans[0][0] = 0;
queue<pair<int, int>> q;
q.push({0, 0});
while (!q.empty()) {
pair<int, int> cur = q.front();
int i = cur.first, j = cur.second;
q.pop();
if (i == n-1 && j == m-1) {
break;
}
//顺序依次为D R U L
for (int d = 0; d < 4; d++) {
int x = i + directions[d][0], y = j + directions[d][1];
//不能走的格子
if (x < 0 || y < 0 || x >= n || y >= m || graph[x][y] == graph[i][j]) {
continue;
}
//如果所花的时间为-1(初始值),则表示没有访问过
if (ans[x][y] == -1) {
ans[x][y] = ans[i][j] + 1;
route[x][y] = d;
q.push({x, y});
}
}
}
cout << ans[n-1][m-1] << endl;
if (ans[n-1][m-1] != -1) {
printRoute(route, n-1, m-1);
cout << endl;
}
}
int main() {
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
vector<string> graph(n);
for (int i = 0; i < n; i++) {
cin >> graph[i];
}
bfs(graph);
}
}
2.有权图最路径
Dijkstra(迪杰斯特拉)
简单来说,Dijkstra就是每次选择距离起点最短的结点添加到已选择的结点集合中,并利用新增节点的边去更新剩余未访问结点的距离(只能计算边权值大于0的情况)。
与prim最小生成树相似,都是逐点添加,并更新路径,
但不一样的是,prim是选择距离生成树集合最短的结点添加,而Dijkstra是选择距离起点最短的结点添加。
Dijkstra模版题
B3602 [图论与代数结构 202] 最短路问题_2
https://www.luogu.com.cn/problem/B3602
下面先给出模版代码
看题目的数据量,由于边数较大,而且是有权图,使用链式前向星储存图结构。
#include <bits/stdc++.h>
#define MAXM (int)3e6
#define MAXN (int)3e6
using namespace std;
using ll = long long int;
struct Edge {
int to, next;
ll w;
} edge[MAXM];
//结点 i 到 起点的距离
ll dist[MAXN];
//头结点数组
int head[MAXN];
//标记是否访问过
bool visited[MAXN];
//添加边
void addEdge(int i, int u, int v, ll w) {
edge[i].w = w;
edge[i].to = v;
edge[i].next = head[u];
head[u] = i;
}
void Dijsktra(int begin, int n) {
//将起点加入到点集中
visited[begin] = true;
dist[begin] = 0;
//更新剩余的结点
for (int i = head[begin]; i != -1; i = edge[i].next) {
int v = edge[i].to;
ll w = edge[i].w;
dist[v] = min(dist[v], w);
}
for (int i = 2; i <= n; i++) {
ll min_dist = LLONG_MAX;
int min_u = -1;
//在未被访问的结点中找距离起点最小的结点
for (int u = 1; u <= n; u++) {
if (!visited[u] && dist[u] < min_dist) {
min_dist = dist[u];
min_u = u;
}
}
//没有找到,表示图中没有通路了,直接退出
if (min_u == -1) break;
//设置为被访问表示加入到点集中
visited[min_u] = true;
//更新剩余未被访问的结点
for (int j = head[min_u]; j != -1; j = edge[j].next) {
int v = edge[j].to;
ll w = edge[j].w;
if (!visited[v]) dist[v] = min(dist[v], w+dist[min_u]);
}
}
}
int main() {
int n, m;
cin >> n >> m;
//初始化
for (int i = 1; i <= n; i++) {
//最开始都是无穷大,表示还没有路径
dist[i] = LLONG_MAX;
head[i] = -1;
visited[i] = false;
}
//读取输入数据
for (int i = 0; i < m; i++) {
int u, v;
ll w;
cin >> u >> v >> w;
addEdge(i, u, v, w);
}
//起点为结点 1
int begin = 1;
//Dijsktra(begin, n);
DijsktraPlus(begin, n);
for (int i = 1; i <= n; i++) {
//表示没有找到路径
if (dist[i] == LLONG_MAX) {
dist[i] = -1;
}
cout << dist[i] << " ";
}
}
但题目数据量大,这种基本写法只能部分AC。
所以我们需要稍微优化一下时间复杂度,可以发现我们寻找最短的结点用了O(n^2),有没有时间复杂度更小的办法,那就是——priority_queue(优先队列),优化时间复杂度到O(nlogn)。
AC代码——优化版Dijsktra:
#include <bits/stdc++.h>
#define MAXM (int)3e6
#define MAXN (int)3e6
using namespace std;
using ll = long long int;
struct Edge {
int to, next;
ll w;
} edge[MAXM];
//结点 i 到 起点的距离
ll dist[MAXN];
//头结点数组
int head[MAXN];
//标记是否访问过
bool visited[MAXN];
//添加边
void addEdge(int i, int u, int v, ll w) {
edge[i].w = w;
edge[i].to = v;
edge[i].next = head[u];
head[u] = i;
}
//自定义比较函数
struct compare {
bool operator()(pair<int, ll> a, pair<int, ll> b) {
return a.second > b.second; //以距离为依据的小顶堆
}
};
//优化版 Dijsktra
void DijsktraPlus(int begin, int n) {
dist[begin] = 0;
priority_queue<pair<int, ll>, vector<pair<int, ll>>, compare> pq;
//初始化优先队列
pq.push({begin, dist[begin]});
while (!pq.empty()) {
//取出距离起点最短的结点
pair<int, ll> cur = pq.top();
pq.pop();
int min_u = cur.first;
ll min_dist = cur.second;
//如果访问过了,则继续选取
if (visited[min_u]) {
continue;
}
//设置为被访问
visited[min_u] = true;
//更新剩余未被访问的结点
for (int i = head[min_u]; i != -1; i = edge[i].next) {
int v = edge[i].to;
ll w = edge[i].w;
if (!visited[v]) {
//更新结点距离
dist[v] = min(dist[v], w + min_dist);
//加入到队列中
pq.push({v, dist[v]});
}
}
}
}
int main() {
int n, m;
cin >> n >> m;
//初始化
for (int i = 1; i <= n; i++) {
//最开始都是无穷大,表示还没有路径
dist[i] = LLONG_MAX;
head[i] = -1;
visited[i] = false;
}
//读取输入数据
for (int i = 0; i < m; i++) {
int u, v;
ll w;
cin >> u >> v >> w;
addEdge(i, u, v, w);
}
//起点为结点 1
int begin = 1;
//Dijsktra(begin, n);
DijsktraPlus(begin, n);
for (int i = 1; i <= n; i++) {
//表示没有找到路径
if (dist[i] == LLONG_MAX) {
dist[i] = -1;
}
cout << dist[i] << " ";
}
}
Floyd(弗洛伊德)
简单来说就是遍历所有的结点,每次使用遍历到的结点(via)去更新所有结点(可以用于负权值)。
- 每轮第via行和第via列与主对角线是不变的。
- 对应行列值相加与现有比较,取最小(graph[i][j] = min(graph[i][j], graph[i][via] + graph[via][j]);)。
Floyd模版题
B3647 【模板】Floyd
https://www.luogu.com.cn/problem/B3647
#include <bits/stdc++.h>
#define MAXN 105
using namespace std;
//用邻接矩阵储存图结构
int graph[MAXN][MAXN];
void Floyd(int n) {
//遍历每个结点
for (int via = 1; via <= n; via++) {
//遍历图
for (int i = 1; i <= n; i++) {
//第 i 行不变
if (via == i) continue;
for (int j = 1; j <= n; j++) {
//第 j 列及主对角线不变
if (via == j || i == j) continue;
//取 i 通过 via 到 j 与现在 i 到 j 的最小值
graph[i][j] = min(graph[i][via] + graph[via][j], graph[i][j]);
}
}
}
}
int main() {
int n, m;
cin >> n >> m;
//初始化图
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) {
graph[i][j] = 0;
}
else {
graph[i][j] = INT_MAX/2;
}
}
}
//输入图数据
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
graph[u][v] = min(graph[u][v], w);
graph[v][u] = min(graph[v][u], w);
}
Floyd(n);
//i 表示起点,graph[i][j] 表示 i 到 j 的最小路径
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (graph[i][j] == INT_MAX/2) {
cout << -1 << " ";
}
else {
cout << graph[i][j] << " ";
}
}
cout << endl;
}
}
最短路径与最小生成树的区别
最小生成树关注的是连接整个图的所有顶点,使得边的总权值最小,但它不保证任意两点之间的路径是最短的。常用算法有 Prim 和 Kruskal,适用于如网络布线、管道铺设等整体成本最小化的场景。
最短路径则是从一个顶点到另一个顶点,寻找路径权值之和最小的路线。它不要求覆盖所有顶点,常用算法有 Dijkstra(单源最短路径)、Floyd(多源最短路径),典型应用是导航、交通规划等局部最优路径问题。
ps:创作不易,给博主点点赞点点关注,感谢大家。
25万+

被折叠的 条评论
为什么被折叠?



