Kruskal求解无向图的最小生成树
1.最小生成树是什么
图的最小生成树可以理解为用最少的边让图连通,即就是让图的任意两点之间可以互相到达,并且将多余的边去掉。
所以我们的目标就是在图连通的条件下使边的总长度最短。
2.算法思想
基于上述描述,大神Kruskal就提出了一种以自己名字命名的Krusal算法,用来解决图的最小生成树问题。
该算法的核心思想是:
①首先按照边的权值进行从小到大的排序。
②循环地从剩余边中选择权值较小且不会产生回路的边,加入生成树中,直到加入了n-1条边为止。
2.1算法具体实现步骤
以下图为例:
- 先将最小生成树初始化为孤立的节点
2.循环地从剩余边中选择权值较小且不会产生回路的边,加入生成树中,直到加入了n-1条边为止。
3.具体实现
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
typedef struct Info
{
int u;//起点
int v;//终点
int w;//权重
}Info;
//并查集,判断图是否连通
class UnionFind {
private:
// 记录每一个节点的父节点father<当前节点下标,父节点下标>
unordered_map<int, int> father;
public:
//找节点x的父节点
int find(int x)
{
int root = x;
while (father[root] != -1)
{
root = father[root];
}
//优化的点:如果我们树很深,那么每次查询的效率都会非常低。这一步把树的深度固定为二。
while (x != root)
{
int original_father = father[x];
father[x] = root;
x = original_father;
}
return root;
}
bool is_connected(int x, int y)
{
return find(x) == find(y);
}
//将连通的两个节点合并为同一个祖先
bool merge(int x, int y)
{
int root_x = find(x);
int root_y = find(y);
if (root_x != root_y)
{
father[root_y] = root_x;
return true;
}
return false;
}
//将新节点添加到并查集中
void add(int x)
{
if (!father.count(x))
{
father[x] = -1;
}
}
};
/*
测试用例:
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
*/
class Kruskal:protected UnionFind
{
private:
vector<Info> graph;
int vertice = 0;//顶点数
int edge = 0;//边数
int sum = 0;//最小生成树的权重总和
int count = 0;//控制遍历次数
//将图的边进行排序(从小到大)
//快速排序的一次划分(Partion)
int Partion(int start, int end)//O(n)
{
Info tmp = graph[start];//基准
int left = start, right = end;
while (left < right) //i==j要退出循环
{
while (left < right && graph[right].w >= tmp.w)//从后向前找比基准值小的数据
{
right--;
}
if (graph[right].w < tmp.w)
{
graph[left] = graph[right];
}
while (left < right && graph[left].w <= tmp.w)//从前往后找比基准值大的数据
{
left++;
}
if (graph[left].w > tmp.w)
{
graph[right] = graph[left];
}
}
graph[left] = tmp;//基准应该放的位置
return left;
}
void Quick_recur(int start, int end)//O(log(n))
{
int mid = Partion(start, end);//一次划分
//递归:O(log(n))
if ((mid - start) > 1)//mid的左部分数据,左边的数据超过一个就需要排序
{
Quick_recur(start, mid - 1);
}
if ((end - mid) > 1)//mid的右部分数据
{
Quick_recur(mid + 1, end);
}
}
public:
//构造函数初始化(顶点数和边数)
Kruskal(int x,int y):vertice(x),edge(y)
{
graph.resize(edge + 1);//0号下标不用
}
void Init_Graph()
{
for (int i = 1; i <= edge; i++)
{
int u = 0, v = 0, w = 0;
cin >> u >> v >> w;
graph[i].u = u;
graph[i].v = v;
graph[i].w = w;
}
}
int Kruskal_Alg()
{
Quick_recur(1, edge);
//并查集初始化
UnionFind fff;
//从小到大遍历每一条边
for (int i = 1; i <= edge; i++)
{
fff.add(graph[i].u);
fff.add(graph[i].v);//将两个节点加入并查集
//判断加入边之图是否连通
if (fff.merge(graph[i].u, graph[i].v))
{
count++;
sum += graph[i].w;
}
if (count == edge - 1)
{
break;
}
}
return sum;
}
};
int main()
{
Kruskal kk(6,9);//6个顶点,9条边
int u = 0, v = 0, w = 0;
//输入边的信息
cout << "请输入边的信息:" << endl;
kk.Init_Graph();
int num = kk.Kruskal_Alg();
cout << num;
return 0;
}