普里姆(Prim)算法

Prim算法是用来干什么的? 用于生成最小生成树

那么最小生成树又是什么? 最小生成树的定义:连通图G上的一棵各边权值之和最小的带权生成树

算法思想:

假设G=(V, E)是一个具有n个顶点的连通图,其中,V是T的顶点集,E是G中边的集合。T=(U,TE)是一个图,其中,U是T的顶点集,TE是T的边集。刚开始时,U和TE的初值均为空集。

(1)首先从V中任取一个顶点,把它并入顶点集U中。
(2)然后从不属于U中的顶点,但与U中顶点直接相连的所有边中,找一条权值最小的边,将这条边并入TE中,并将相应的顶点并入U中。
(3)不断重复(2),直到U=V,TE含有n-1条边,此时的T就是G的最小生成树。

算法的详细过程

举例:假设图G如下图所示
在这里插入图片描述

第1步:首先,我们可以从V中取0这个顶点,将它并入顶点集U中

第2步:符合条件的边有(0, 1) (0, 2) (0, 5),而在这些边中,边的最小权值是3,于是将(0, 5)并入边集TE中,将顶点5并入顶点集U中

第3步:符合条件的边有(0, 1) (0, 2) (5, 1) (5, 2) (5, 3) (5, 4),而在这些边中,边的最小权值是1,于是将(5, 4)并入边集TE中,将顶点4并入顶点集U中
在这里插入图片描述

第4步:符合条件的边有(0, 1) (0, 2) (5, 1) (5, 2) (5, 3) (4, 2) (4, 3),而在这些边中,边的最小权值是2,于是将(4, 3)并入边集TE中,将顶点3并入顶点集U中

第5步:符合条件的边有(0, 1) (0, 2) (5, 1) (5, 2) (4, 2) (3, 1),而在这些边中,边的最小权值是4,于是将(0, 2)并入边集TE中,将顶点2并入顶点集U中

第6步:符合条件的边有(0, 1) (5, 1) (3, 1),而在这些边中,边的最小权值是5,于是将(5, 1)并入边集TE中,将顶点1并入顶点集U中
在这里插入图片描述

当操作到第6步时,U=V,TE含有n-1条边,此时红线所画的图就是图G的最小生成树

核心代码

void Prim (int n, int cost[LEN][LEN])
{
    
    
	int i, j, k;
	int min;
	int lowcost[LEN], closest[LEN];
	
	for (j = 1; j < n; j++)
	{
    
    
		lowcost[j] = cost[0][j];
//将lowcost数组中各个元素设为从顶点0到各顶点的权值		
		closest[j] = 0;
//依附于该边的顶点为0		
	}
//将顶点0作为最小生成树的第一个结点	
	closest[0] = -1;
//表示顶点0已经并入集合U中	
	
//下面的整个大的for循环刚好可以输出n-1条边,以及所对应边的权值	
	for (i = 1; i < n; i++)
	{
    
    
		min = M, k = i;
		for (j = 0; j < n; j++)
		{
    
    
			if (closest[j] != -1 && lowcost[j] < min)
			{
    
    
				min = lowcost[j];
				k = j;
			}
		}
//上面的for循环是为了求出符合条件,且权值最小的边所对应的数组坐标k		
		
		printf ("(%d, %d) 权值为:%d\n", closest[k], k, lowcost[k]);
		closest[k] = -1;
//将顶点k并入集合U中		
				
		for (j = 0; j < n; j++)
		{
    
    
			if (closest[j] != -1 && cost[k][j] < lowcost[j])
			{
    
    
				lowcost[j] = cost[k][j];
				closest[j] = k;
			} 
		}
//上面的for循环是为了更新两个数组中的数据		
	} 
} 

辅助操作

从上面的核心代码,我们可以知道,为了实现Prim算法,需要设置两个辅助的一维数组lowcost和closest。其中数组lowcost用来保存符合条件的边的最小权值;数组closest用来保存依附在这条边且在集合U中的顶点

步骤:
(1)假设初始状态时,我们让0作为生成树根结点的顶点,接着令closest[0]=-1,它表示顶点0已经并入集合U中;令数组lowcost中的值是顶点0和其余各顶点所构成的边的权值(注意:不与顶点0直接相连的边的权值记为M,即初始为7,4,M,M,3)
(2)输出符合条件且权值最小的边,令lowcost[k]=1,(表示顶点k已经并入集合U中)然后更新数组lowcost和closest中的数据(由于顶点k并入集合U后,符合条件的边发生了变化,所以需要根据具体情况更新数组lowcost和closest中的)
(3)重复操作(2),直到数组closest中的值都为-1时结束

生成最小生成树中两个数组的变化如下表所示,读者可自己试一遍数,可以更好地理解这个算法哦

[0] [1] [2] [3] [4] [5] V-U 集合 U集合 生成树的边 边的权值
(1) closest -1 0 0 0 0 0
lowcost 7 4 M M 3 {1, 2, 3, 4, 5} {0}
(2) closest -1 5 0 5 5 -1
lowcost 5 4 6 1 3 {1, 2, 3, 4} {0,5} (0, 5) 3
(3) closest -1 5 0 4 -1 -1
lowcost 5 4 2 1 3 {1, 2, 3} {0,5,4} (5, 4) 1
(4) closest -1 5 0 -1 -1 -1
lowcost 5 4 2 1 3 {1, 2} {0,5,4,3} (4, 3) 2
(5) closest -1 5 -1 -1 -1 -1
lowcost 5 4 2 1 3 {1} {0,5,4,3,2} (0, 2) 4
(5) closest -1 -1 -1 -1 -1 -1
lowcost 5 4 2 1 3 { } {0,5,4,3,2,1} (1, 5) 5

源代码

# include <stdio.h>
# define M 999999
# define LEN 100


int CreateTu (int cost[LEN][LEN])
{
    
    
	int n, e;
//n表示图的顶点,e表示图的边	 
	int i, j;
	int v1, v2, w; 
	printf ("请输入该图的顶点数:"); 
	scanf ("%d", &n);
	printf ("请输入该图的边数:");
	scanf ("%d", &e);
	
	for (i = 0; i < n; i++)
	{
    
    
		for ( j = 0; j < n; j++)
		{
    
    
			cost[i][j] = M; 
		}
	} 
//初始化数组元素的值

	printf ("\n请依次输入顶点,顶点,两顶点所连成边的权值!!!");
	printf ("(格式:顶点 顶点 权值)\n"); 
	for (i = 0; i < e; i++)
	{
    
    
		printf ("v1, v2, w = ");
		scanf ("%d %d %d", &v1, &v2, &w);
		cost[v1][v2] = w;
		cost[v2][v1] = w; 
	}

	return n;
//返回顶点数	 
}


void Prim (int n, int cost[LEN][LEN])
{
    
    
	int i, j, k;
	int min;
	int lowcost[LEN], closest[LEN];
	
	for (j = 1; j < n; j++)
	{
    
    
		lowcost[j] = cost[0][j];
//将lowcost数组中各个元素设为从顶点0到各顶点的权值		
		closest[j] = 0;
//依附于该边的顶点为0		
	}
//将顶点0作为最小生成树的第一个结点	
	closest[0] = -1;
//表示顶点0已经并入集合U中	
	
//下面的整个大的for循环刚好可以输出n-1条边,以及所对应边的权值	
	for (i = 1; i < n; i++)
	{
    
    
		min = M, k = i;
		for (j = 0; j < n; j++)
		{
    
    
			if (closest[j] != -1 && lowcost[j] < min)
			{
    
    
				min = lowcost[j];
				k = j;
			}
		}
//上面的for循环是为了求出符合条件,且权值最小的边所对应的数组坐标k		
		
		printf ("(%d, %d) 权值为:%d\n", closest[k], k, lowcost[k]);
		closest[k] = -1;
//将顶点k并入集合U中		
				
		for (j = 0; j < n; j++)
		{
    
    
			if (closest[j] != -1 && cost[k][j] < lowcost[j])
			{
    
    
				lowcost[j] = cost[k][j];
				closest[j] = k;
			} 
		}
//上面的for循环是为了更新两个数组中的数据		
	} 
} 


int main (void)
{
    
    
	int n;
	int cost[LEN][LEN];
	n = CreateTu (cost);
	printf ("\n最小生成树为:\n");
	Prim (n, cost);
	return 0;
} 

运行结果

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_52607834/article/details/117467666