最小生成树算法
用于解决图中连接所有节点并且总权重最小的问题。在一个带权重的无向图中,最小生成树是一棵包含图中所有节点的树,并且其边的权重之和最小。
前提:补充一下图论基础知识点
图的分类
- 无向图(Undirected Graph):图中的边没有方向,即边连接的两个顶点是无序的。
- 有向图(Directed Graph):图中的边有方向,通常表示为箭头,指向从一个顶点到另一个顶点。
- 加权图(Weighted Graph):图中的边具有与其相关联的权重或成本。
图的表示方法
- 邻接矩阵(Adjacency Matrix):对于具有n个顶点的图,邻接矩阵是一个n×n的矩阵,其中如果顶点i和顶点j之间存在边,则矩阵的第i行第j列的元素为1(或边的权重),否则为0。
- 邻接表(Adjacency List):对于图中的每个顶点,邻接表记录与该顶点直接相连的所有顶点。这通常通过链表或其他数据结构实现。
图的特性
- 度数(Degree):无向图中一个顶点的度数是与该顶点相连的边数。有向图中,一个顶点的入度是指向该顶点的边的数量,出度是从该顶点出发的边的数量。
- 路径(Path):图中一系列连续的边和顶点,其中每条边的两个端点都是该序列中的顶点,并且序列中的每个顶点只出现一次(除了可能的首尾顶点)。
- 连通性(Connectivity):在无向图中,如果从任何顶点都存在到达任何其他顶点的路径,则称该图是连通的。
一 并查集
举个例子:一个有向图,有13个结点,现在想要找到结点13的源父亲节点。
查找源父亲结点(结点没有父亲结点),前提dp[14] = {0,3,1,5,3,5,4,6,1,5,3,4,2,1};意思为i个结点,dp[i]里面的数值是它的直接父亲结点位置。位置1的父亲结点是位置3的结点,位置2的结点的父亲结点是位置1的结点......,现在求位置13的结点的源父亲结点(不会有两个源父亲)
int dp[14] = {0,3,1,5,3,5,4,6,1,5,3,4,2,1};
int find(in x){
while(dp[x]!=x){
x=dp[x];
}
return x;
}
这个就是单纯的查找结点的源结点,首先找到dp[13],发现dp[13]!=13;x=dp[dp[13]],即x=dp[1],dp[1]!=1;x=dp[dp[1]],即x=dp[3],dp[3]!=3;x=dp[dp[3]],即x=dp[5],dp[5]=5,返回源节点信息为5;
输出打印信息:
3 1 5 3 5 4 6 1 5 3 4 2 1
5
3 1 5 3 5 4 6 1 5 3 4 2 1
int dp[14] = {0,3,1,5,3,5,4,6,1,5,3,4,2,1};
int find(int x){
if(dp[x]!=x){
dp[x]=find(dp[x]);
}
return dp[x];
}
这个在查找的时候会更新每个结点的源结点的信息,首先找到dp[13],发现dp[13]!=13;然后层层递归dp[13]=find(dp[13]),即dp[13]=find(1);dp[1]=find(dp[1]),即dp[1]=find(3);dp[3]=find(dp[3]),即dp[3]=find(5); 然后发现dp[5]=5,层层将5传上去,dp[3]=find(dp[3])=5,dp[1]=find(dp[1])=5,dp[13]=find(dp[13])=5。因此在查找的时候,查找结点的源节点信息都改变了。
原来查找13的源节点信息需要13->1->3->5,下次再查找13的源节点就是13->5
3 1 5 3 5 4 6 1 5 3 4 2 1
5
5 1 5 3 5 4 6 1 5 3 4 2 5
int main() {
for (int i = 1; i <= 13; i++) {
cout << dp[i] << " ";
}
cout << endl;
cout << find(13) << endl;
for (int i = 1; i <= 13; i++) {
cout << dp[i] << " ";
}
cout << endl;
return 0;
}
P2307 迷宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
二 kruskal (克鲁斯卡尔算法)
思路:
- 构建一个结构体,包含一条边的两个结点,以及两个结点之间的路径的权值
- 构建一个dp[N]数组,初始化为dp[i]=i,用来记录位置i的源节点位置
- 将所有数据插入到一个vector数组中,并且按照权值的大小进行排序
- 然后遍历vector数组,查找每个值的左端点的源节点是否等于右端点的源结点(),有,则跳过,没有就answer加1,并且将左端点的源结点更新为右端点的源节点。
#include<iostream> #include<vector> #include<algorithm> using namespace std; int dp[14] = {0,3,1,5,3,5,4,6,1,5,3,4,2,1}; //构建一个结构体,包含左端点x,右端点y,以及权值value struct node{ int x, y, value; node() {}; node(int x1, int y1, int value1) :x(x1), y(y1), value(value1) {} bool operator<(const node p) { return value<p.value; } }; //这里是题目需要求的权值 int temp(int x, int y) { int result = 0; while (x || y) { if (x % 10 != y % 10) { result += (x % 10 + y % 10); } x /= 10; y /= 10; } return result; } //查找源结点 int find(int x) { if (x != dp[x]) dp[x] = find(dp[x]); return dp[x]; } int main() { vector<node> s; //构建数组vector for (int i = 1; i <= 2020; i++) { for (int j = i + 1; j <= 2021; j++) { node p; p.x = i; p.y = j; p.value = temp(i, j); s.push_back(p); } } //让数组按照权值最小的顺序排序(在结构体中写了比较函数) sort(s.begin(), s.end()); int answer = 0; int t = dp[s[0].x]; //初始化dp for (int i = 0; i < 2022; i++) dp[i] = i; int num = 0; //遍历dp for (int i = 0; i < s.size(); i++) { if (find(dp[s[i].y]) != find(dp[s[i].x])) { num++; answer += s[i].value; dp[find(s[i].x)] = find(s[i].y); } if (num == 2020) { break; } } cout << answer << endl; return 0; }
三 prim算法
举个例子:(可以从结点1~n开始)
前提:设立一个bool数组vis[n],,初始化为false,int 数组 dis[n],初始化为0x3f,n为结点的数量
比如选择结点1开始,将dis[1]的值设为0,意思为与1结点连接的权值为0
- 选择结点1(因为dis[1]=0,为dis[n]中权值最小的),将vis[1]标记为true,意思是结点1标记过
- 然后更新结点1到它直接相连结点m的权值,更新dis[m];
- 遍历dis,选择权值最小的结点t,将vis[t]标记为true;
- 然后更新结点t到它直接相连的结点m的权值
- 重复选择权值最小的结点,更新dis数组,知道所有的点vis都被标记过。
思路:
- 直接从结点1开始
- 更新与结点1连接的结点的权重系数
- 从所有未标记的权重系数中挑选一个权重系数最小的结点加入到生成树中
#include<iostream>
using namespace std;
int g[2022][2022];//连接矩阵
bool vis[2022];//一个用来确实该节点是否被标记的数组
int dis[2022];//一个用来存储每次更新后的权值,用来记录结点t到周围连接的结点权值更新
int temp(int x, int y) {//用来计算value值
int result = 0;
while (x || y) {
if (x % 10 != y % 10) {
result += x % 10 + y % 10;
}
x /= 10;
y /= 10;
}
return result;
}
int prime() {
int result = 0;
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
for (int i = 1; i <= 2021; i++) {//执行2021次
int t = -1;
for (int j = 1; j <= 2021; j++) {
if (!vis[j] && (t == -1|| dis[j] < dis[t])) {
t = j;
}
}
vis[t] = true;
result += dis[t];
if (dis[t] == 0x3ff) return 0;
for (int j = 1; j <= 2021; j++) {
dis[j] = min(dis[j],g[t][j]);
}
}
return result;
}
int main() {
memset(g, 0x3f, sizeof g);//初始化为最大值
for (int i = 1; i <= 2021; i++) {
for (int j = 1; j <= 2021; j++) {
if (i != j) {
g[i][j] = g[j][i] = temp(i,j);
}
}
}
cout << prime() << endl;
return 0;
}
可以选择从结点5开局
int prime(int n) {
memset(dis, 0x3f, sizeof(dis));
int result = 0;
dis[n] = 0;
for (int i = 0; i < 8; i++) {
int t = -1;
for (int j = 1; j <= 8; j++) {
if (!vis[j] && (dis[j] < dis[t] || t == -1)) {
t = j;
}
}
vis[t] = true;
if (dis[t] == 0x3f) return 0x3f;
result += dis[t];
for (int j = 1; j <= 8; j++) {
if (!vis[j]) dis[j] = min(dis[j],dp[t][j]);
}
}
return result;
}
int main()
{
memset(dis, 0x3f, sizeof(dis));
for (int i = 1; i < 9; i++) {
for (int j = 1; j < 9; j++) {
cin >> dp[i][j];
if (dp[i][j] == 0) {
dp[i][j] = 0x3f;
}
}
}
cout << prime(5) << endl;
return 0;
}
参考: