一.测试用图
邻接矩阵表示:
//prim注意是无向图
vector<vector<int>> p(6, vector<int>(6, INF));//类似dijikstra,没有边的点设为INF
p[0][1] = 10;
p[0][3] = 30;
p[0][4] = 45;
p[1][0] = 10;
p[3][0] = 30;
p[4][0] = 45;
p[1][2] = 50;
p[1][4] = 40;
p[1][5] = 25;
p[2][1] = 50;
p[4][1] = 40;
p[5][1] = 25;
p[2][4] = 35;
p[2][5] = 15;
p[4][2] = 35;
p[5][2] = 15;
p[3][5] = 20;
p[5][3] = 20;
p[4][5] = 55;
p[5][4] = 55;
注意:prim算法的图为无向图,和dijikstra一样,没有边的地方要用INF填充
其中INF表示无穷:
const int INF = 0x3f3f3f;//不能取得太大
二.prim算法:
//prim算法,返回最小生成树的权值
int Prim(vector<vector<int>> cost)//下标从0开始
{
int n = cost.size();
vector<bool> visited(n, false);
vector<int> lowc(n, 0);
int ans = 0;
visited[0] = true;//第一个点标记为已访问
for (int i = 1;i < n;i++)lowc[i] = cost[0][i];
for (int i = 1;i < n;i++)
{
int minc = INF;
int p = -1;
for (int j = 0;j < n;j++)
if (!visited[j] && minc > lowc[j])
{
minc = lowc[j];
p = j;
}
if (minc == INF)return -1;//原图不连通
ans += minc;
visited[p] = true;
for (int j = 0;j < n;j++)
if (!visited[j] && lowc[j] > cost[p][j])
lowc[j] = cost[p][j];
}
return ans;
}
测试:
int minWeight =Prim(p);
cout << "minWeight= " << minWeight << endl;
测试结果:
三.kruskal算法:
图改为边数组表示,利用并查集很容易理解。此处直接用一个数组来表示并查集,数组的下标表示节点的编号,对应的值为其父指针的值,初始时,将并查集中的所有元素的父指针指向-1.
3.1图的边数组表示法:
//kruskal:使用并查集
struct Edge {
int from;//起点
int to;//终点
int weight;
bool operator<(const Edge&rhs){//重载operator <之后可以直接调用sort
return weight < rhs.weight;//此处也可以单独写一个仿函数,传给sort作比较器
}
};
此时上面的测试用图表示为:
vector<Edge> kru =
{ { 0,1,10 },{ 1,0,10 },{ 0,3,30 },{ 3,0,30 },{ 0,4,45 },{ 4,0,45 },{ 1,2,50 },{ 2,1,50 },
{ 1,4,40 },{ 4,1,40 },{ 1,5,25 },{ 5,1,25 },{ 2,4,35 },{ 4,2,35 },
{ 3,5,20 },{ 5,3,20 },{ 5,4,55 },{ 4,5,55 },{ 2,5,15 },{ 5,2,15 } };
3.2并查集的find函数
int Find(vector<int>& UnionFindSet,int x){//索引表示节点标号,对应的值为其父节点的标号
if (UnionFindSet[x] == -1)//代表元素是-1,说明只有一个元素
return x;
return UnionFindSet[x] = Find(UnionFindSet,UnionFindSet[x]);//递归修改,扁平化
}
3.3 kruskal算法
int kruskal(vector<Edge> graph,int vertexNum) {
size_t N = graph.size();
int ans = 0;
int cnt = 0;//已经选取的边数
vector<int> UnionFindSet(N, -1);//初始化并查集,每个集合的代表元素置为-1;
sort(graph.begin(), graph.end());//给边排序,拍好序后边的权值由小到大排列
for (size_t i = 0;i < N;++i) {
int from = graph[i].from;
int to = graph[i].to;
int weight = graph[i].weight;
//找出这条边的两个顶点所在的集合
int t1 = Find(UnionFindSet, from);
int t2 = Find(UnionFindSet, to);
if (t1 != t2) {//t1和t2不在一个集合时,即没有形成回路时,选取该边
ans += weight;
UnionFindSet[t1] = t2;//合并两个集合
++cnt;
}
if(cnt==vertexNum-1)//选够顶点数减1条边时,退出,最小生成树的边数为顶点数减一
break;
}
if (cnt < vertexNum - 1) return -1;//不连通
else return ans;
}
3.4 kruskal测试
cout << "kruskal: " << endl;
vector<Edge> kru =
{ { 0,1,10 },{ 1,0,10 },{ 0,3,30 },{ 3,0,30 },{ 0,4,45 },{ 4,0,45 },{ 1,2,50 },{ 2,1,50 },
{ 1,4,40 },{ 4,1,40 },{ 1,5,25 },{ 5,1,25 },{ 2,4,35 },{ 4,2,35 },
{ 3,5,20 },{ 5,3,20 },{ 5,4,55 },{ 4,5,55 },{ 2,5,15 },{ 5,2,15 } };
int ret = kruskal(kru, 6);
cout << "kruskal= " << ret << endl;
3.5 kruskal测试结果