快速解决克鲁斯卡尔和普里姆算法--C++实现
关于此算法的思想. 具体可以参照
最小生成树视频演示(bilibili上的)
此视频演示是我的头号参照, 大量文字叙述算法只会帮倒忙, 必要部分进行画图陈述
克鲁斯卡尔算法(有向图形式):
先将其封装为一个个的方法, 方便代码重用
/*
从上至下的方法作用:
*/
int getIndexOfRepresentative(const int index);
// 递归获取该编号节点的根节点索引
void merge(const int index1, const int index2);
// index1编号的集合与index2的集合合并, index2的根节点为合并后的树的根节点
bool isLoop(const int index1, const int index2);
// 判断要连接的两点是否会构成回路
比如对下图进行getIndexOfRepresentative(1)的调用, 返回的将是4
对getIndexOfRepresentative(3), getIndexOfRepresentative(7)也是如此
首先, 我们先得有存储右侧edge的物品, 这个时候
先起一个定义 边的结构体!
struct edge {
int begin;
int end;
int len;
};
至于输入, 排序代码放在最后. 现在, 我们假定已经做好了排序, 完成了图中所示的结构, 那么接下来继续下一步
对
N
个结点,
M
条给定的无向边
我们先定义好
totalLength, 用于存放路径的总长度
addedEdges用于存放已经加入的边数
我们依据规则, 便能写出如下代码:
int N, M;
int totalLength = 0;
int addedEdges = 0;
cin >> N >> M;
edge *edges = new edge[M];
...
...
...
int i = 0;
while (i != M)
{
if (!(isLoop(edges[i].begin, edges[i].end)))
{
merge(edges[i].begin, edges[i].end);
addedEdges++;
totalLength += edges[i].len;
cout << edges[i].begin << " -> " << edges[i].end
<< " len:" << edges[i].len << endl;
if (addedEdges == (N - 1)) { cout << totalLength << endl; return 0; }
}
i++;
}
从edges数组中
取一条最短的边, 判断加入后是否会构成回路, 否, 则将第一个集合与第二个连接起来, addedEdges自增一次, totalLength增加一次==
如图. 将索引为
0 4
的结点连接起来时
我们先判断是否构成回路, 没有构成回路, 进行下一步.
先将0 指向 4, 而 4 指向 6这个根节点
为了让所有结点最终都指向6
需要对索引0往回找, 直到找到根节点1
那么修改顺序如下:
0 —> 5 修改为 0 —> 4
5 —> 1 修改为 5 —> 0
1 —> 1 修改为 1 —> 5
至此, 完成了连接.
(3是指向根节点1的, 所以1指向改变, 3随之改变)
#include<iostream>
#include<algorithm>
struct edge {
int begin;
int end;
int len;
};
static int nodes[5000];
/*
从上至下的方法作用:
递归获取该编号节点的根节点索引
index1编号的集合与index2的集合合并, index2的根节点为合并后的树的根节点
判断要连接的两点是否会构成回路
*/
int getIndexOfRepresentative(const int index);
void merge(const int index1, const int index2);
bool isLoop(const int index1, const int index2);
bool compare(edge e1, edge e2) {
return e1.len < e2.len;
}
int main(void) {
using namespace std;
int N, M;
int totalLength = 0;
int addedEdges = 0;
cin >> N >> M;
edge *edges = new edge[M];
for (int i = 0; i < M; i++) {
cin >> edges[i].begin >> edges[i].end;
cin >> edges[i].len;
}
sort(edges, edges + M, compare);
for (int i = 0; i < N; i++) {
nodes[i] = i;
}
cout << endl;
/*
至此: 已经对边的长度进行了升序排序
完成了边数据的输入, 以及各顶点的指向初始化, 各个顶点看成各棵树上的根结点
*/
int i = 0;
while (i != M)
{
if (!(isLoop(edges[i].begin, edges[i].end)))
{
merge(edges[i].begin, edges[i].end);
addedEdges++;
totalLength += edges[i].len;
cout << "第" << i + 1 << "次, 选择了边: "
<< edges[i].begin << " -> " << edges[i].end
<< " 权值:" << edges[i].len << endl;
/* 选择了N-1条边, 退出程序并且输出结果 */
if (addedEdges == (N - 1))
{
cout << "路径总长度: " << totalLength << endl;
for (int i = 0; i < N; i++)
cout << i << "号节点的父节点是" << nodes[i] << "节点\n";
return 0;
}
}
i++;
}
cout << "orz\n";
return 0;
}
int getIndexOfRepresentative(const int x)
{
if (nodes[x] == x) { return x; }
else { return getIndexOfRepresentative(nodes[x]); }
}
/*
将num1的代表结点指向num2的代表结点
*/
void merge(const int num1, const int num2)
{
int next = num1;
int front = num2;
int temp = 0;
while (nodes[next] != next)
{
temp = nodes[next];
nodes[next] = front;
front = next;
next = temp;
}
nodes[next] = front;
}
/*
传入将要连接的两个结点编号
判断是否是形成了一个环
*/
bool isLoop(const int num1, const int num2)
{
return getIndexOfRepresentative(num1) == getIndexOfRepresentative(num2);
}
对视频中数据进行测试
9 14
6 7 1
2 8 2
5 6 2
0 1 4
2 5 1
6 8 6
7 8 7
2 3 7
0 7 8
1 2 8
3 4 9
4 5 10
1 7 11
3 5 14
得到绘制如图
弊端:不能回退, 考虑进行双向链表进行实现.
struct node{
int index;
node* next;
node* front;
};
普里姆算法(无向图形式)
离散思想:
按照套路, 这次我在代码中增加了提示输入的文字(可以删除), 我们和上面一样, 先进行数据的输入, 然后所有的边全部被升序排序
与克鲁斯卡尔算法依照边的一次while循环不同, 我们不能查看一条边(不管使用还是不使用)都跳过这条边, 因为一条边权值很小但是可能当前用不到----不代表之后用不到
我们在edge的结构体上进行改动:
/* isUsed初始化为false, 已经连接的边, 会被设置为true, 避免某条边重复使用 */
struct edge {
int node1;
int node2;
int length;
bool isUsed;
};
我们使用一条边的依据是:
边的两个结点序号有且至多有一个存在于已经连接的结点中, 说明这条边可以使用
全部代码:
/* 获取下一个要连接的结点的序号(0 ~ N-1)
* 判断方法: e的两个结点序号有且至多有一个存在于nodes数组中
* 这样正好保证了不会形成回路
*/
int nextNode(int* nodes, int currentNodes, edge e){
int index1, index2;
index1 = index2 = -1;
for (int i = 0; i < currentNodes; i++) {
if (e.node1 == nodes[i]){ index1 = i; }
else if (e.node2 == nodes[i]){ index2 = i; }
}
if ((-1 == index1 && index2 == -1) || (-1 != index1 && index2 != -1)) { return -1; }
else { return index1 == -1 ? e.node1 : e.node2; }
}
#include<iostream>
#include<algorithm>
/* isUsed初始化为false, 已经连接的边, 会被设置为true, 避免某条边重复使用 */
struct edge {
int node1;
int node2;
int length;
bool isUsed;
};
/* 用于sort()函数按照权值大小升序排序edge边数组
* 当e1 和 e2 的位置反过来时, 会变成降序排序
*/
bool compare(edge e1, edge e2) {
return e1.length < e2.length;
}
/* 获取下一个要连接的结点的序号(0 ~ N-1)
* 判断方法: e的两个结点序号有且至多有一个存在于nodes数组中, 这样正好保证了不会形成回路
*/
int nextNode(int* nodes, int currentNodes, edge e){
int index1, index2;
index1 = index2 = -1;
for (int i = 0; i < currentNodes; i++) {
if (e.node1 == nodes[i]){ index1 = i; }
else if (e.node2 == nodes[i]){ index2 = i; }
}
if ((-1 == index1 && index2 == -1) || (-1 != index1 && index2 != -1)) { return -1; }
else { return index1 == -1 ? e.node1 : e.node2; }
}
int main(void)
{
using namespace std;
/* 准备工作 */
int N, M;
cout << "结点个数: ";
cin >> N;
cout << "给定的边数: ";
cin >> M;
int* nodes = new int[N] {};
edge* edges = new edge[M];
cout << "请依次输入边: 结点序号1 结点序号2 权值\n";
for (int i = 0; i < M; i++) {
int node1, node2, len;
cin >> node1 >> node2 >> len;
edges[i].node1 = node1;
edges[i].node2 = node2;
edges[i].length = len;
edges[i].isUsed = false;
}
sort(edges, edges + M, compare);
/* nodes数组存储已经连接好的结点, 在无向图中, 只需要将已经连接的结点看成整体
然后每次选择最小权值的边, 当currentNodes等于结点总数, 说明最小生成树完成
nodes[0]意味着先将第一个结点看成起始点 */
nodes[0] = 0;
int totalLength = 0;
int i = 0;
int currentNodes = 1;
while (currentNodes != N)
{
while(i < M){
if (edges[i].isUsed == false)
{
int temp = nextNode(nodes, currentNodes, edges[i]);
if (-1 != temp) {
nodes[currentNodes++] = temp;
edges[i].isUsed = true;
totalLength += edges[i].length;
cout << edges[i].node1 << " -- " << edges[i].node2 << endl;
break;
}
}
i++;
}
}
cout << "总权值: "<< totalLength << endl;
return 0;
}
运行结果:
分析:
以0号结点为整体, 循环从edges数组中找到包含0节点的边, 且权值最小------------是 0 1 4, 连接, 然后将isUsed标记为true,然后将新节点1加入到nodes数组中
现在以0 , 1 为整体, 从edges数组中寻找包含0, 1其中之一结点的边, 且权值最小-------是0 7 8, 连接, 然后将isUsed标记为true, 将新结点加入到nodes数组中,
现在以0, 1, 7结点为整体…
至此完成~~;