快速解决克鲁斯卡尔和普里姆算法--C++实现(CSP通过)

关于此算法的思想. 具体可以参照
最小生成树视频演示(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) = 4, getIndexOfRepresentative(7) = 4也是如此
在这里插入图片描述在这里插入图片描述首先, 我们先得有存储右侧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

在这里插入图片描述那么修改顺序如下:
1 —> 1 修改为 1 —> 5
5 —> 1 修改为 5 —> 0
0 —> 5 修改为 0 —> 4
(递归)
在这里插入图片描述至此, 完成了连接.
(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 currentNodes = 1;
	while (currentNodes != N)
	{
    
    
	/* 选择边时, 从0索引依次向后寻找边(权值从小到大) 选择一条边后, 重置索引i */
		int i = 0;
		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结点为整体…

至此完成~~;

注意! 上面的代码是假设以输入从1开始, 不包含0, 如果需要提交答案, 请修改代码输入并且删除输入输出提示
CSP验证:
在这里插入图片描述

#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;

	cin >> N;
	cin >> M;

	int* nodes = new int[N] {
    
    };
	edge* edges = new edge[M];
	for (int i = 0; i < M; i++) {
    
    
		int node1, node2, len;
		cin >> node1 >> node2 >> len;
		edges[i].node1 = node1 - 1;
		edges[i].node2 = node2 - 1;
		edges[i].length = len;
		edges[i].isUsed = false;
	}
	sort(edges, edges + M, compare);

	/* nodes数组存储已经连接好的结点, 在无向图中, 只需要将已经连接的结点看成整体
	   然后每次选择最小权值的边, 当currentNodes等于结点总数, 说明最小生成树完成
	   nodes[0]意味着先将第一个结点看成起始点 */

	nodes[0] = 0;
	int totalLength = 0;
	int currentNodes = 1;
	while (currentNodes != N)
	{
    
    
	/* 选择边时, 从0索引依次向后寻找边(权值从小到大) 选择一条边后, 重置索引i */
		int i = 0;
		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;
					break;
				}
			}
			i++;
		}
	}
	cout << totalLength << endl;

	return 0;
}

猜你喜欢

转载自blog.csdn.net/Valishment/article/details/106645192