图的最小生成树(prim算法和kruskal算法的实现以及讲解)

目录

 1.题目简介

 先上题目 ,便于更加直观理解和体现该算法

2.介绍一下我对书本上prim算法代码实现的理解

1.lowcost数组的作用

2.adjvex数组的作用

3.kruskal算法

3.要源码的直接看这里


 1.题目简介

做完之后头发又掉了几根估计,写的代码将近两百行,结果提交上去OJ系统又说没有完全做对,这么长的代码我实在是看不出哪里出了问题了

来几个人帮我看看到底那里出了点问题吧呜呜呜(题目给的样例是可以无误输出的,不知道哪里还有问题。。。。)

 先上题目 ,便于更加直观理解和体现该算法

题目描述

根据输入创建无向网。分别用Prim算法和Kruskal算法构建最小生成树。(假设:输入数据的最小生成树唯一。)

输入:

顶点数n

n个顶点

边数m

m条边信息,格式为:顶点1顶点2权值

Prim算法的起点v

输出:

输出最小生成树的权值之和

对两种算法,按树的生长顺序,输出边信息(Kruskal中边顶点按数组序号升序输出)

输入样例

6
v1 v2 v3 v4 v5 v6 
10
v1 v2 6
v1 v3 1
v1 v4 5
v2 v3 5
v2 v5 3
v3 v4 5
v3 v5 6 

输出样例

15
prim:
v1 v3 1
v3 v6 4
v6 v4 2
v3 v2 5
v2 v5 3
kruskal:
v1 v3 1
v4 v6 2
v2 v5 3
v3 v6 4
v2 v3 5

一道题目就涵盖了两种算法而且还要完成 邻接矩阵的构建

主要是prim算法,书本上的着实不好理解,我真的是一步步照着自己的思路然后再结合书本上的做

法才搞懂书上(大话数据结构) 的代码意思

------------------------------------------------------------------------------------------------

2.介绍一下我对书本上prim算法代码实现的理解

先讲prim算法,放prim部分的函数

void prim(int** Map, int vnum, int s, int Enum,  string* vertax) {
	edgeifo* primEdge = new edgeifo[vnum - 1]; //用于存储输出时prim算法的边界信息
	int locate = 0;//标记存储上面primedge
	int* lowcost = new int[vnum];//保存相关顶点间的所有路径权值
	int* adjvex = new int[vnum];//保存相关路径的权值对应的顶点
	int sum = 0, cal = 0;
	int row = s;
	for (int i = 0; i < vnum; i++)
	{
		lowcost[i] = Map[row][i]; // 将起点的所有路径权值赋给lowcost
		adjvex[i] = s;
	}
	while (cal++ < vnum - 1) {
		int min = infinity;
		for (int i = 0; i < vnum; i++)  //找出最小值所在的列
		{
			if (lowcost[i] != 0 && lowcost[i] < min)
			{
				min = lowcost[i];
				row = i;
			}
		}
		sum += min;
		//将当前生成的信息存储起来(其实在这里就可以输出了,不过是受题目要求影响)
		primEdge[locate].head = vertax[adjvex[row]];
		primEdge[locate].tail = vertax[row];
		primEdge[locate].power = min;
		locate++;
		//cout <<  vertax[adjvex[row]] << ' '<<vertax[row] <<" " <<min<< endl;
		lowcost[row] = 0;//表示该点已经完成任务
		for (int i = 0; i < vnum; i++)  // 将最小值的顶点行数据赋给lowcost
		{
			if (lowcost[i] != 0 && Map[row][i] < lowcost[i]) {
				lowcost[i] = Map[row][i];
				adjvex[i] = row;  //用来表明该位置的权值来自哪一个点
			}
		}
	}
}

1.lowcost数组的作用

关于prim算法的大概原理其实很好理解,就是从当前的点出发,把当前点的所有路径权值都给列出来(这在邻接矩阵中相当于把这一行的所有元素给列出来,也就是放到lowcost数组里面了),

列出来之后找到最小值min,然后往那一条路去走到下一个点(也就是获得新的row),再把下一个点的所有路径权值也获取了,注意,这个时候还要把上一个点未被探索的路径给包括进去(我觉得这里代码实现得挺巧妙的)

如何用代码实现上一步呢,

每次去到新的点时,也就是到了邻接矩阵新的一行,遍历该行所有元素,对应lowcost里面的数组

里的每一个元素,若新的点对应的新的一行的元素比当前lowcost的元素要大,则替换掉。这样就

可以将原来的点中的路径给保留了下来,当然如果新的一行每一个元素都比对应lowcost元素要

小,那原来的点的路径就会全部被替换掉了,这样非常的灵活。

2.adjvex数组的作用

我感觉这个数组是最为抽象并且难以理解的,一开始照着代码思路敲的时候就一直没有理解这个

adjvex到底代表着什么(有用是肯定有用的就是想不明白什么作用)

于是我按自己的理解去写了一次prim算法后,发现怎么根本用不到adjvex这个数组,一开始还想着

这么简单呢嘻嘻,后来把样例输进去之后我就人傻了,也想不明白为什么报错。

在下面的while循环代码中可以看到 row是每一次循环都会更改数值的图的最小生成树(prim算法和kruskal算法的实现以及讲解)

 若能理解这里的代码就会知道,row在中间才会因为最小值min的更改而改变,也就是说每一次循环开始row是指原来的那一个顶点,而经过了for循环后,找到了最小值,则row就更改了,也就是即将要去的下一个新的顶点,那么我想着要输出的不就是这个刚开始的row和更新之后的row吗

于是我就按着这样的方法输出,结果是有些顶点出错了,最初的row往往不是我想要的,如果按我的方式输出,那么下面输出样例的第四行第一个点应当是v4,但是实际输出是v3。我百思不得其解

v1 v3 1
v3 v6 4
v6 v4 2
v4 v2 5
v2 v5 3

后来我将自己的代码思路再次推敲了一遍后才终于理解了adjvex数组的含义,这个数组就是用来解决我上述的问题,按我上面的做法,就只能一条路走到黑,不断输出,例如某一行的输出顶点肯定是下一行的输入顶点,但是实际当中并不一定会这样,因为前面说过了

找到了下一个点之后并不代表就可以把原来的那些未被探索的路径给去掉,而我就是犯了这个错误

而不自知,因此这个时候adjvex就派上了用场hhhh

		for (int i = 0; i < vnum; i++)  // 将最小值的顶点行数据赋给lowcost
		{
			if (lowcost[i] != 0 && Map[row][i] < lowcost[i]) {
				lowcost[i] = Map[row][i];
				adjvex[i] = row;  //用来表明该位置的权值来自哪一个点
			}
		}

整个prim算法当中其实只有这里需要对adjvex进行赋值,这里其实就是为了告诉你当前这个路径是

属于哪一个点的,做了一个标记,如果新的路径并没有覆盖,那么当前的adjvex会让路径还是保留

着原来那个点的信息

所以在输出数据的时候只需要稍作改动,原本我是在整个大循环一开始的row当作输入顶点,现在

改成了adjvex对应的顶点,而后面的输出顶点也还是那个被更新之后的row,这样就完成啦

虽然上面bb了这么多,但是我还是觉得不会有人看得懂我在说什么,等我以后有空再尽量讲清楚一

点,但是prim算法其实步骤并不算多,只是代码比较抽象,难以理解,照着自己的思路推导一遍我

觉得是对理解prim算法有很大的帮助的

-----------------------------------------------------------------------------------------------

-------------------------------------------------------

3.kruskal算法

kruskal算法相对来说也是非常的直观好理解,代码实现上也比prim要简单,我的代码里面也用函数封装完成。

kruskal从一个上帝视角将所有的边的权值从小到大排列出来,然后再从小到大选择路径就好,这

样可以很好的构建图的最小生成树,需要注意的是需要写一个Find函数来判断是否会形成回路,导

致构建的并非最小生成树

3.要源码的直接看这里

#include<iostream>
#include<cstring>
using namespace std;
#define infinity 65535;


typedef struct edgeifo
{
	string head;
	string tail;
	int power;
};

int** MakeGraph(int num, int Enum, string* vertax, edgeifo* edge, char Gtype)
{
	int** matrix = new int* [num]; //开辟空间 
	for (int i = 0; i < num; i++)
	{
		matrix[i] = new int[num];
		for (int j = 0; j < num; j++)
		{
			if (i == j)
				matrix[i][j] = 0;
			else
				matrix[i][j] = infinity; //初始化数组 
		}
	}
	for (int j = 0; j < Enum; j++)
	{
		int row, column;
		for (int i = 0; i < num; i++)
		{
			if (vertax[i] == edge[j].head)
			{
				row = i;
			}
		}
		for (int i = 0; i < num; i++)
		{
			if (vertax[i] == edge[j].tail)
			{
				column = i;
			}
		}

		matrix[row][column] = edge[j].power;
		if (Gtype == 'U')
			matrix[column][row] = edge[j].power;
	}
	//输出该数组 

	return matrix;
}



void prim(int** Map, int vnum, int s, int Enum,  string* vertax) {
	edgeifo* primEdge = new edgeifo[vnum - 1]; //用于存储输出时prim算法的边界信息
	int locate = 0;//标记存储上面primedge
	int* lowcost = new int[vnum];//保存相关顶点间的所有路径权值
	int* adjvex = new int[vnum];//保存相关路径的权值对应的顶点
	int sum = 0, cal = 0;
	int row = s;
	for (int i = 0; i < vnum; i++)
	{
		lowcost[i] = Map[row][i]; // 将起点的所有路径权值赋给lowcost
		adjvex[i] = s;
	}
	while (cal++ < vnum - 1) {
		int min = infinity;
		for (int i = 0; i < vnum; i++)  //找出最小值所在的列
		{
			if (lowcost[i] != 0 && lowcost[i] < min)
			{
				min = lowcost[i];
				row = i;
			}
		}
		sum += min;
		//将当前生成的信息存储起来(其实在这里就可以输出了,不过是受题目要求影响)
		primEdge[locate].head = vertax[adjvex[row]];
		primEdge[locate].tail = vertax[row];
		primEdge[locate].power = min;
		locate++;
		//cout <<  vertax[adjvex[row]] << ' '<<vertax[row] <<" " <<min<< endl;
		lowcost[row] = 0;//表示该点已经完成任务
		for (int i = 0; i < vnum; i++)  // 将最小值的顶点行数据赋给lowcost
		{
			if (lowcost[i] != 0 && Map[row][i] < lowcost[i]) {
				lowcost[i] = Map[row][i];
				adjvex[i] = row;  //用来表明该位置的权值来自哪一个点
			}
		}
	}
	//输出
	cout << sum << endl << "prim:" << endl;
	for (int i = 0; i < vnum - 1; i++)
	{
		cout << primEdge[i].head << " " << primEdge[i].tail << " " << primEdge[i].power << endl;
	}


}

int Find(int* parent,int f) {
	while (parent[f] > 0) {
		f = parent[f];  //不断去寻找未曾联通的点
	}
	return f;
}

void kruskal(int** Map, int vnum, int Enum ,edgeifo* edge,string* vertax) {
	//先获得边集数组
	cout << "kruskal:" << endl;
	edgeifo* Earr = new edgeifo[Enum];
	memcpy(Earr, edge, sizeof(struct edgeifo)*Enum);//拷贝结构体数组
	for (int i = 0; i < Enum - 1; i++) //按权重由小到大排序
	{
		for (int j = 0; j < Enum - i -1; j++)
		{
			if (Earr[j].power > Earr[j + 1].power) {
				edgeifo tp;
				tp = Earr[j];
				Earr[j] = Earr[j + 1];
				Earr[j + 1] = tp;
			}
		}

	}

	int* parent = new int[Enum]; //用来存放边界信息,判断是否形成回路
	for (int i = 0; i < Enum; i++)
	{
		parent[i] = 0; //初始化
	}
	for (int i = 0; i < Enum; i++) //遍历每一条边
	{
		int begin, end;
		for (int j = 0; j < vnum; j++)
		{
			if (Earr[i].head == vertax[j])
				begin = j;
			if (Earr[i].tail == vertax[j])
				end = j;
		}
		int m, n;
		n = Find(parent,begin);
		m = Find(parent,end);
		if (n != m)
		{
			parent[n] = m; //表示第n个顶点与m顶点相联通
			cout << Earr[i].head << ' ' << Earr[i].tail << ' ' << Earr[i].power << endl;
		}
	}

}




void MatrixIfo(int num, char Gtype) {
	int Enum; string* vertax = new string[num];
	//cout << "please enter each vertax :" << endl
	//输入顶点 
	for (int i = 0; i < num; i++)
	{
		cin >> vertax[i];
	}
	//cout << "enter the edge number:" << endl;//输入边的个数和信息 
	cin >> Enum;
	edgeifo* edge = new edgeifo[Enum];
	for (int i = 0; i < Enum; i++) {
		cin >> edge[i].head;
		cin >> edge[i].tail;
		cin >> edge[i].power;
	}
	string start; int startNum;
	cin >> start;
	for (int i = 0; i < num; i++)
	{
		if (start == vertax[i])
			startNum = i;
	}

	//边界信息输入完毕 
	 //返回邻接矩阵
	int** Map = MakeGraph(num, Enum, vertax, edge, Gtype);

	prim(Map, num, startNum, Enum, vertax); //prim算法
	kruskal(Map, num, Enum,edge,vertax); //kruskal算法
}

int main() {

	char Gtype = 'U';//直接标明是无向图
	int num;
	cin >> num;
	MatrixIfo(num, Gtype);
}

菜鸟新手写题,不喜欢迎喷

上一篇:prim算法(普里姆算法)详解


下一篇:Prim算法解决最小生成树 (解决修路问题)