最小生成树的生成有两种方法,一种是prim算法,另一种是kruskal算法。数据结构和算法导论的学习中了解到一点,整理出来方便以后瞅瞅。
无向图的生成方式
采用链式前向星的方法,用一个数组来表示边的所有起点,结构体来表示点所能到达的边,迭代构成一个无向图。
#include <iostream>
#include <vector>
using namespace std;
#define MAX 100000
#define MAX_VAL 0x00ffffff
//储存无向图的每个点可以到达的点
struct node
{
int to;//顶点的一个终点
int val;//边的权值
int next;//同一顶点的下一个终点
}dir[MAX];
vector<int> path;//依次生成最小树的节点的顺序
int head[MAX] = {0};//头结点的
//链式存储无向图
void add(int from,int to,int val,int len)
{
dir[len].to = to;
dir[len].val = val;
dir[len].next = head[from];
head[from] = len;
}
int main()
{
int m;//点的范围
cin>>m;
int n;//边的数目
cin>>n;
int len = 1;
for(int i = 1; i <= n; i++)
{
int u,v,val;
cin>>u>>v>>val;
add(u,v,val,len);//u -> v
len++;
add(v,u,val,len);//v -> u
len++;
}
Prim(n,m);
// cout<<endl;
// for(int i = 1; i <= n; i++)
// {
// for(int j = head[i]; j != 0; j = dir[j].next)
// cout<<dir[j].to<<' '<<dir[j].val<<' '<<i<<endl;
// }
//依次打印生成树节点的顺序
for(int i = 0; i < path.size(); i++)
cout<<path[i]<<endl;
return 0;
}
prim算法
算法思想
选择一个节点作为树的根节点,然后把这个节点放入一个已经找到的集合里,然后开始迭代,每一次都从这个集合中的点,依次找到一条集合中点可以到达的最小的边,然后把这条边的终点记录在集合中,继续重复迭代,直到所有的点都选择完毕。
时间复杂度:因为是两层循环,所以说时间复杂度还是O(n2)。
算法实现
//选择以1号点为树的跟节点
void Prim(int n,int m)
{
int visit[MAX] = {0};//判断找到树节点
path.push_back(1);
visit[1] = 1;
//依次找到其他的最小节点
for(int i = 1; i < m; i++)
{
int min_val = MAX_VAL;
int min_to = 0;//本次循环可以找到的最小边
//从所有已经选择的点开始遍历
for(int j = 0; j < path.size(); j++)
{
int from = path[j];
//查找该点可以到达的点,找到一条最小的边
for(int k = head[from]; k != 0; k = dir[k].next)
{
int to = dir[k].to;
//如果该点没有被访问,并且边的权值小于当前找到的,进行更新
if(!visit[to] && min_val > dir[k].val)
{
min_val = dir[k].val;
min_to = to;
}
}
}
if(min_val == MAX_VAL)
{
cout<<"无法生成最小树"<<endl;
return ;
}
visit[min_to] = 1;
path.push_back(min_to);
}
}
kruskal算法
算法思想
首先先对所有的边从小到大排序,然后在保证没有回路的前提下选出权重娇小的n-1条边,如果(i,j)是所有集合中没有选择的边中权重最小的,并且(i,j)不会和已选的边构成回路。如果(i,j)的两个点 i 和 j 同属于一个连通分支,那么选择 i 和 j 会构成回路,反之则不会。
连通分支
对于一个无向图而言,它的一个极大连通子图即为一连通支。比如说,一个图由三部分构成,其中每一部分都是连通的,但三个部分之间互相不连通,那么每一部分即为无向图的一个连通分支。此图的连通分支数为3。
时间复杂度:给每个边排序的时间复杂度为O(n * logn),选择边的时候时间复杂度是 O(logn),所以说总的时间复杂度是O(n * logn)。
算法实现
int GetRoot(int k, vector<int>& V)
{
while (k != V[k])
k = V[k];//只有根节点才满足 k = V[k]
return k;
}
//Kruskal 算法
double Kruskal(vector<side>& sides, vector<point>& points)
{
double sum = 0;//定义最后生成树的边的长度
//从小到大给边排序
sort(sides.begin(), sides.end(), cmp);
int pointNum = points.size();
vector<int> V(pointNum, 0);//定义并查集数组
//初始化每一个结点都是根节点,并查集中
for (int i = 0; i < pointNum; i++)
V[i] = i;
//筛选
int len = sides.size();//边的数目
for (int i = 0; i < len; i++)
{
int start = GetRoot(sides[i]._start, V);
int end = GetRoot(sides[i]._end, V);
if (start != end)
{
V[start] = end;
sum += sides[i]._distance;
}
}
return sum;
}