普里姆算法-寻找最小生成树

前言:普里姆算法寻找最小生成树的学习笔记和实现

什么是普里姆算法

MST性质:设图 G = (V,E)所有顶点的集合为V,MST中顶点的集合为T。

  • 从G中选取任意顶点作为MST的根,将其添加至T。

  • 循环执行下述处理直至T=V

  • 在连接T内顶点与V-T内顶点的边中选取权值最小的边 (V,E),将其作为MST的边,并将 u 添至T。

基于MST性质的最小生成树怎么理解呢?偷了张动图如下所示

就比如这里我们挑选从顶点1开始,可以看到的就是跟1顶点有相关边的有4个顶点,分别是2,3,6,5号顶点

那么根据MST性质来寻找最小生成树,顶点3跟顶点1的之间的权值是最小的,所以我们这里选择的就是顶点3

  • 这里需要注意了,这里如何通过代码找到顶点3呢?我们通过遍历顶点1相关的邻接矩阵就可以找到最小权值对应的相关顶点

  • 那么这里还需要注意的一点,在邻接矩阵中我们定义的顶点自身和顶点自身之间的权值为0,所以在代码中判断的时候还需要加上一个条件就是权值!=0的情况,这样就不会把自身顶点返回回去了

这里继续看,当找到了顶点3,此时记录每个顶点之间的权值就会发生改变

  • 改变的规律是什么?在实现记录每个顶点之间的权值的时候,我们这里是通过创建单独的一个结构体来记录每个顶点之间的权值的

  • 当每次找到了一个最小的权值之后,我们都要重新遍历一次这个结构体中当前顶点对其他顶点中的权值

  • 如何改变?如果当前顶点对某一个顶点的权值更小,那么就将该结构体中记录的某一个顶点的权值更新为更小的那一个

  • 如此重复上面的步骤即可

实现普利姆寻找最小生成树

代码实现如下

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 32767
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef char VexType;
typedef struct _Graph
{
	int vexNum;
	int arcNum;
	int** arcs;
	char* vexs;
}Graph, *PGraph;

Graph* initGraph(int vexNum)
{
	Graph* pGraph = NULL;
	if (pGraph == NULL)
	{
		pGraph = (Graph*)malloc(sizeof(Graph));
		memset(pGraph, 0, sizeof(Graph));
		if (pGraph == NULL)
			return NULL;
		pGraph->vexNum = vexNum;
		pGraph->vexs = (char*)malloc(sizeof(pGraph->vexNum));
		memset(pGraph->vexs, 0, sizeof(pGraph->vexNum));
		pGraph->arcNum = 0;
		pGraph->arcs = (int**)malloc(sizeof(int*)* pGraph->vexNum);
		memset(pGraph->arcs, sizeof(int*)* pGraph->vexNum, 0);
		for (int i = 0; i < pGraph->vexNum; i++)
		{
			pGraph->arcs[i] = (int*)malloc(sizeof(int)* pGraph->vexNum);
			memset(pGraph->arcs[i], 0, sizeof(int)*pGraph->vexNum);
		}
	}
	return pGraph;
}

Status createGraph(Graph** pGraph, char* vexs, int* arcs)
{
	if (*pGraph == NULL)
		return ERROR;
	for (int i = 0; i<(*pGraph)->vexNum; i++)
	{
		*((*pGraph)->vexs + i) = *(vexs + i);
		for (int j = 0; j<(*pGraph)->vexNum; j++)
		{
			(*pGraph)->arcs[i][j] = *(arcs + i*((*pGraph)->vexNum) + j);
			printf("%d ", (*pGraph)->arcs[i][j]);
			if ((*pGraph)->arcs[i][j] != 0 && (*pGraph)->arcs[i][j] != MAX)
				(*pGraph)->arcNum++;
		}
		printf("\n");
	}
	(*pGraph)->arcNum /= 2;
	return OK;
}

// 深度优先遍历DFS
Status DFS(Graph* pGraph, int* iVisitedArray, int visitedIndex)
{
	if (pGraph == NULL)
		return ERROR;
	iVisitedArray[visitedIndex] = 1;
	printf("%c ", pGraph->vexs[visitedIndex]);
	for (int i = 0; i<pGraph->vexNum; i++)
	{
		if (pGraph->arcs[visitedIndex][i] > 0 && !iVisitedArray[i] && pGraph->arcs[visitedIndex][i] != MAX)
			DFS(pGraph, iVisitedArray, i);
	}
	return OK;
}

// 用于存储最小权值所使用的结构体
struct _Record
{
	int iWegiht;
	char vex1;
	char vex2;
}vexRecord[5] = { 0 };

// 最小生成树算法-个人理解实现-后面发现类似Kruskal算法最小生成树
void personalUnderstanding(Graph* pGraph)
{
	ElemType iMin;
	int i;
	// m -> 总共循环的次数等于顶点的个数, 这层循环能够找出一条边对应权值最小的两个顶点
	for (int m = 0; m<pGraph->vexNum - 1; m++)
	{
		// 第m次中找到对应index节点中相关边权值最小的值
		iMin = MAX - 1;
		for (i = 0; i<pGraph->vexNum; i++)
		{
			if (pGraph->arcs[m][i] != MAX && pGraph->arcs[m][i] > 0 && pGraph->arcs[m][i] < iMin)
				iMin = pGraph->arcs[m][i];
		}

		// 通过遍历对应index节点最小的边权值对比,来获得另外一个顶点的索引j
		for (i = 0; i<pGraph->vexNum; i++)
		if (pGraph->arcs[m][i] != MAX && pGraph->arcs[m][i] > 0 && pGraph->arcs[m][i] == iMin)
			break;

		// 两个顶点的最小权值已经拿到了,为了下次不影响,这里的话将其设置为-1,无向图是对称的,所以这里A-B B-A的权值都设置为-1
		// pGraph->arcs[m][i] = -1;
		pGraph->arcs[i][m] = -1;

		// 每次遍历完都要将结果进行保存,之后要打印使用
		vexRecord[m].iWegiht = iMin;
		vexRecord[m].vex1 = pGraph->vexs[m];
		vexRecord[m].vex2 = pGraph->vexs[i];
	}
}

typedef struct _Edge{
	char vex;
	int weight;
}Edge;

// Edge的结构体用来存储每次遍历节点的相关节点的最小权值
Edge* initEdge(Graph* pGraph, int index)
{
	Edge* pEdge = (Edge*)malloc(sizeof(Edge)*pGraph->vexNum);
	for (int i = 0; i<pGraph->vexNum; i++)
	{
		pEdge[i].vex = pGraph->vexs[index];
		pEdge[i].weight = pGraph->arcs[index][i];
	}
	return pEdge;
}

// 返回的是下一个要取得最小权值的顶点的索引
int getMinIndex(Graph* pGraph, Edge* pEdge)
{
	int index = 0;
	int iMin = MAX;
	for (int i = 0; i<pGraph->vexNum; i++)
	{
		if (pEdge[i].weight !=0 && pEdge[i].weight < iMin)
		{
			/*
			iMin = pGraph->arcs[index][i];
			index = i;
			*/
			iMin = pEdge[i].weight;
			index = i;
		}
	}
	return index; // 每次返回的都是edge中最小权值对应的索引
}


/*
将图中的所有的顶点分为两类:树顶点(已经被选入生成树的顶点)和非树顶点(还未被选入生成树的顶点)。首先选择任意一个顶点加入生成树,接下来要找出一条边添加到生成树,
这需要枚举每一个树顶点到每一个非树顶点所有的边,然后找到最短边加入到生成树。依次,重复操作n-1次,直到将所有顶点都加入生成树中。
*/

// prim
void getPrim(Graph* pGraph)
{
	Edge* pEdge = initEdge(pGraph, 0);
	int iMinIndex;
	for (int i = 0;i<pGraph->vexNum-1;i++)
	{
		// 获取顶点相关最小权值的顶点的索引
		iMinIndex = getMinIndex(pGraph, pEdge);
		// 这边输出对应顶点的最小权值
		printf("%c -- %c -- weight: %d \n", pEdge[iMinIndex].vex, pGraph->vexs[iMinIndex], pEdge[iMinIndex].weight);
		// 这个操作意味着将当前顶点加入到U集合中
		pEdge[iMinIndex].weight = 0;
		// 找到了对应顶点的最小权值后,这里又需要去修正一次U集合中的权值
		// 就比如此时U集合原本有A顶点,然后加入B顶点,那么现在有A,B两个顶点,那么就需要将B顶点和edge中原本存储的权值进行对比,要如果B顶点对应的顶点的权值中
		// 有比edge对应的相同顶点的权值更小,那么这里也就需要修改
		for (int j = 0;j<pGraph->vexNum;j++)
		{
			if (pGraph->arcs[iMinIndex][j] < pEdge[j].weight)
			{
				pEdge[j].weight = pGraph->arcs[iMinIndex][j];
				pEdge[j].vex = pGraph->vexs[iMinIndex];
			}
		}
	}
}

int main()
{
	int initArcs[6][6] =
	{
		0, 6, 1, 5, MAX, MAX,
		6, 0, 5, MAX, 3, MAX,
		1, 5, 0, 5, 6, 4,
		5, MAX, 5, 0, MAX, 2,
		MAX, 3, 6, MAX, 0, 6,
		MAX, MAX, 4, 2, 6, 0
	};
	char initVexs[7] = "123456";
	Graph* pGraph = initGraph(6);
	createGraph(&pGraph, initVexs, (int*)initArcs);
	getPrim(pGraph);
	return 0;
}

posted @ 2022-04-09 14:44  zpchcbd  阅读(213)  评论(0)    收藏  举报