图论
Nevermore 2024-02-17 algorithm
# 图的存储结构
- 邻阶矩阵:利于表示边的权值,O(1)判断两边是否相连,O(N)判断与顶点相连的所有边
- 邻阶表:vector保存顶点,并将相邻的点用链表串联。利于查找一个顶点的所有边,但不利于确定顶点是否相连以及边的权值
# 最小生成树
无向图中,任意一对顶点都是连通的,则称为连通图。
n个顶点的连通图的生成树有n-1条边(最小连通子图即为生成树)
构成生成树的所有边权值和最小——最小生成树
# Kruskal算法
- 恰好选用n-1条边,权值最小且不能构成回路
- 最小生成树不一定唯一
思路:从边的权值最小处开始选择,并将相连的两个顶点加入并查集。使用并查集判断是否有环,在同一个集合的边不能选择。
::: 示例代码
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <functional>
using namespace std;
struct Edge {
size_t srcpoint;
size_t despoint;
int weight;
Edge(size_t srcp, size_t desp, int w) : srcpoint(srcp), despoint(desp), weight(w)
{}
bool operator> (const Edge& eg) const {
return weight > eg.weight;
}
};
int Kruskal(const vector<vector<int>>& matrix, vector<vector<int>>& minTree) {
priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;
int n = matrix.size();
for(size_t i = 0; i < n; i++) {
for(size_t j = 0; j < n; j++) {
if(i < j && matrix[i][j] != INT_MAX) { // 无向图遍历一半就可以了——对称的;i==j无效
minHeap.push({i, j, matrix[i][j]}); //入队列进行排序
}
}
}
vector<int> unionfindSet(n, - 1); //并查集
function<int(int)> findroot = [&unionfindSet](int a)->int{
while(unionfindSet[a] >= 0) {
a = unionfindSet[a];
}
return a;
};
function<void(Edge)> addTree = [&minTree](const Edge & eg) { //添加点到最小生成树
minTree[eg.srcpoint][eg.despoint] = eg.weight;
};
int Nedge = 0;
int SumWeight = 0;
while(!minHeap.empty()) {
Edge Min = minHeap.top();
minHeap.pop();
int root1 = findroot(Min.srcpoint);
int root2 = findroot(Min.despoint);
if(root1 != root2) {
unionfindSet[root1] += unionfindSet[root2];
unionfindSet[root2] = root1; //添加到并查集
addTree(Min);
Nedge++;
SumWeight += Min.weight;
}
}
if(Nedge == n-1) {
return SumWeight;
}
return 0;
}
int main()
{
vector<vector<int>> matrix {{1, 1, 2}, {1, 0,3}, {2, 3, 0}};
int n = matrix.size();
vector<vector<int>> minTree(n, vector<int>(n, 0)); //记录最小生成树
cout << Kruskal(matrix, minTree) << endl;
for(auto x : minTree) {
for(auto y : x) {
cout << y << " ";
}
cout << endl;
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
:::
# Prim算法
- 算法思路:开始选择一个顶点,选择与这个顶点相连的最小边,将顶点通过这个最小边相连的对应点添加入集合。
- 使用优先级队列获得权值最小的边,将此边对应的顶点插入set(插入的顶点不能是set中曾经出现过的,否则会成环)
::: 示例代码
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <functional>
#include <unordered_set>
using namespace std;
struct Edge {
size_t srcpoint;
size_t despoint;
int weight;
Edge(size_t srcp, size_t desp, int w) : srcpoint(srcp), despoint(desp), weight(w)
{}
bool operator> (const Edge& eg) const {
return weight > eg.weight;
}
};
int Prim(const vector<vector<int>>& matrix, vector<vector<int>>& minTree, size_t startPoint) {
int n = matrix.size();
unordered_set<size_t> inX;
unordered_set<size_t> outY;
function<void(Edge)> addTree = [&minTree](const Edge & eg) { //添加点到最小生成树
minTree[eg.srcpoint][eg.despoint] = eg.weight;
};
inX.insert(startPoint);
for(size_t i = 0; i < n; i++) {
if(startPoint != i) {
outY.insert(i); //插入不是开始点的所有点
}
}
//选出startPointchu出发的权值最小边
priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;
// 插入队列时先判断这个边的两个顶点是否同时出现在一个队列中(inX或outY)
// 不在则插入,在则舍弃
for(size_t i = 0; i < n; i++) { // i == startpoint ?
if(matrix[startPoint][i] != INT_MAX) {
minHeap.push({startPoint, i, matrix[startPoint][i]}); //将不是本体外的所有结点入队列
}
}
int Nedge = 0;
int SumWeight = 0;
while(!minHeap.empty()) {
Edge Min = minHeap.top();
minHeap.pop();
if(inX.count(Min.despoint) == 0) // 插入的点若不在集合中,才可插入,不构成环
{
addTree(Min);
inX.insert(Min.despoint);
outY.erase(Min.despoint);
for(size_t i = 0; i < n; i++) {
if(matrix[Min.despoint][i] != INT_MAX && inX.count(i) == 0) {
minHeap.push({Min.despoint, i, matrix[Min.despoint][i]});
}
}
++Nedge;
SumWeight+=Min.weight;
}
}
if(Nedge == n-1) {
return SumWeight;
}
return 0;
}
int main()
{
vector<vector<int>> matrix {{INT_MAX, 1, 2}, {1, INT_MAX,3}, {2, 3, INT_MAX}};
int n = matrix.size();
vector<vector<int>> minTree(n, vector<int>(n, 0)); //记录最小生成树
cout << Prim(matrix, minTree, 2) << endl;
for(auto x : minTree) {
for(auto y : x) {
cout << y << " ";
}
cout << endl;
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
:::
# 最短路径
带权值的有向图,找到权值和最小的路径
- Dijkstra:针对单源路径,且权值不能为负数
算法思路:选择一个顶点,将顶点连出的所有边对应的节点进行更新,取每次路径权值的和最小值,将权值和最小的点作为下一次更新的起点。




::: 示例代码
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <functional>
#include <unordered_set>
#include <algorithm>
using namespace std;
struct Edge {
size_t srcpoint;
size_t despoint;
int weight;
Edge(size_t srcp, size_t desp, int w) : srcpoint(srcp), despoint(desp), weight(w)
{}
bool operator> (const Edge& eg) const {
return weight > eg.weight;
}
};
void PrintShortPath(const size_t& src, const vector<int>& dist, const vector<int>& pPath, int n)
{
size_t srci = src;
vector<char> vertexs {'0','1','2','3','4','5','6','7'};
for (size_t i = 0; i < n; ++i)
{
if (i != srci)
{
// 找出i顶点的路径
vector<int> path;
size_t parenti = i;
while (parenti != srci)
{
path.push_back(parenti);
parenti = pPath[parenti];
}
path.push_back(srci);
reverse(path.begin(), path.end());
cout << vertexs[path[0]] << "到" << vertexs[path[path.size() - 1]] << "的最短路径为:";
for (auto index : path)
{
cout << vertexs[index] << "->";
}
cout <<"权值和为:" << dist[i] << endl;
}
}
}
void Dijkstra(const vector<vector<int>>& matrix, size_t startPoint) {
int n = matrix.size();
vector<int> dict(n, INT_MAX); // 记录每个顶点权值和的当前最小值
vector<int> father(n, -1); //记录当前顶点权值对应的父节点
vector<bool> Set(n, false); //记录已经遍历过的节点
dict[0] = 0;
father[0] = startPoint;
for(size_t i = 0; i < n; i++) {
size_t Min = LLONG_MAX;
size_t Point = 0;
for(size_t j = 0; j < n; ++j) {
if(Set[j] == false && dict[j] < Min) { //遍历所有节点,找到节点值比Min小的,作为下次更新的起点
Point = j;
Min = dict[j];
}
}
Set[Point] = true; //记录遍历的节点
for(size_t k = 0; k < n; ++k) {
//若最小值的点发散出的边,与各个边权值的和小于对应点(K)的值,更新K
if(Set[k] == false &&
matrix[Point][k] != INT_MAX && dict[Point] + matrix[Point][k] < dict[k]) {
dict[k] = dict[Point] + matrix[Point][k];
father[k] = Point;
}
}
}
PrintShortPath(startPoint, dict, father, n);
}
int main()
{
vector<vector<int>> matrix {{INT_MAX, 3,INT_MAX,2,3, INT_MAX, INT_MAX,INT_MAX},
{INT_MAX, INT_MAX, 4, INT_MAX, INT_MAX, 8, 1, INT_MAX},
{INT_MAX, 2, INT_MAX, INT_MAX, INT_MAX, 7, 9, INT_MAX},
{INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX,INT_MAX},
{INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX,INT_MAX},
{INT_MAX, INT_MAX, INT_MAX, 9, INT_MAX, INT_MAX, INT_MAX,5},
{INT_MAX, INT_MAX, INT_MAX, INT_MAX, 7, INT_MAX, INT_MAX,INT_MAX},
{INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, 8, INT_MAX,INT_MAX},
};
int n = matrix.size();
vector<vector<int>> minTree(n, vector<int>(n, 0)); //记录最小生成树
Dijkstra(matrix, 0);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
- 运行结果:
[z@r test]$ ./test
0到1的最短路径为:0->1->权值和为:3
0到2的最短路径为:0->1->2->权值和为:7
0到3的最短路径为:0->3->权值和为:2
0到4的最短路径为:0->4->权值和为:3
0到5的最短路径为:0->1->5->权值和为:11
0到6的最短路径为:0->1->6->权值和为:4
0到7的最短路径为:0->1->5->7->权值和为:16
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 使用优先级队列实现的结果保持一致:好处是只需要遍历更新了权值的结果,不需要从0到n重新遍历。
void Dijkstra(const vector<vector<int>>& matrix, size_t startPoint) {
int n = matrix.size();
vector<int> dict(n, INT_MAX); // 记录每个顶点权值和的当前最小值
vector<int> father(n, -1);
vector<int> visited(n);
vector<Edge> edge;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(matrix[i][j] != INT_MAX) {
edge.push_back({i, j, matrix[i][j]});
}
}
}
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> minHeap;
//默认排序规则,first由小(top)到大,若first相等按second排序
//first最小路径值,sencond节点
dict[startPoint] = 0;
minHeap.push({0, startPoint});
while(!minHeap.empty()) {
int value = minHeap.top().first;
int p = minHeap.top().second;
minHeap.pop();
if(visited[p]) continue; //访问过的点跳过
visited[p] = 1;
// if(value > dict[p]) { //结果可以等价以上两条
// continue; //若值大于则不记录
// }
for(auto eg : edge) {
if(eg.srcpoint == p ){ //找到与p相等的源点,计算到其对应点的权值和,
int des = eg.despoint;
int w = eg.weight;
if(dict[des] > dict[p] + w) {
dict[des] = dict[p] + w;
father[des] = p;
minHeap.push({dict[des], des});
}
}
}
}
PrintShortPath(startPoint, dict, father, n);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
:::
- Bellman-Ford算法:单源路径,权值可以为负数