【算法导论学习】最小生成树(Kruskal&Prim)

什么是最小生成树

在一个无向图中,连接每两个不同顶点的每一条边都有一个长度,叫做权,最小生成树就是在这个图中找到一棵树,这棵树可以把每一个点连接起来,同时保证权和是所有生成树中最小的。
讲的专业点就是这样:
在这里插入图片描述

求法

计算最小生成树的办法最经典的有两个,Kruskal和Prim。Kruskal算法更好理解,但是实际上的的代码编写可能比Prim还要更复杂。

有空建议看看视频(点我),这个视频的动画演示非常清晰的展现了这两个算法的运算思路。

在这里插入图片描述

Kruskal算法
简述

对与一个图而言,每条边都有权,那么我们可以把所有的权列出来,然后从小到大排序。
同时把你纸上的图的所有边擦掉,然后依次选择最小的边,填回这张被擦掉边的图,也就是那一堆点中。
依次选择最小的边代回,如果在代回的时候发现这条边所属的两个点都分别有另外的边与之相连了,那么这个时候就不要强人所难,就不将这条边再放回去了,如果放回去的话就会在图中形成一个回路,无法构成树的条件。

这次感觉书上讲的比我的简洁
这次感觉树上讲的比我的简洁

图例

下面深色边则表示我所讲的所选择的边
在这里插入图片描述

伪代码

这个伪代码相当抽象,但是主旨也就是这样
在这里插入图片描述

C语言代码
typedef struct//邻接矩阵
{
	int edges[MAXV][MAXV];
	int n,e;
	VertexType vexs[MAXV];
}MatGraph;
typedef struct//边信息
{
	int u;
	int v;
	int w;
}Edge;
bool cmp(Edge a,Edge b)//排序条件
{
    return a.w<b.w;
}
void Kruskal(MatGraph g)
{
	int i,j,ul,vl,snl,sn2,k;
	int vset[MAXV];	//记录点所属的树 
	Edge E[MAXV];	//将所有边的信息保存 
	k=0;
	
	for(i=0;i<g.n;i++)	//保存每条边所属的两个端点已经权 
	{
		for(j=0;j<=i;j++)
		{
			if(g.edges[i][j]!=0&&g.edges[i][j])
			{
				E[k].u=i;
				E[k].v=j;
				E[k].w=g.edges[i][j];
				k++;
			}
		}
	}
	
	sort(E,E+k,cmp);	//从小到大排序 
	for(i=0;i<g.n;i++)	//初始化记录数组,初始状态为自己本身 
	{
		vset[i]= i;
	} 
	k=1;j=0;
	while(k<g.n)
	{
		ul=E[j].u;vl=E[j].v;	//两个端点 
		snl=vset[ul];			 
		sn2=vset[vl];
		if(snl!=sn2)			//判断是否已经被连接了,没有则连接 
		{
			printf("边(%d,%d)权为:%d\n",ul,vl,E[j].w );
			k++;
			for(i=0;i<g.n;i++)
			{
				if(vset[i]==sn2)	//将所有属于第二个端点的点的记录值改到第一个端点 
					vset[i]=snl;
			}
		}
		j++;
	}	
}
时间复杂度分析

我作为小垃圾表示我已经看不懂,只记得这个结论好了
在这里插入图片描述

Prim算法
简述

Prim算法将图的点分为了两个部分,一个部分是在生成树中的点,一部分是不在生成树中的点。
在初始的时候,只选择一个点为生成树的初始元点,其他都不在生成树中,然后通过元点找到两个点集中的最短连接桥梁,将桥梁的非树中点加入到树中点,这样不断扩大生成树直到完全覆盖图,就形成了最小生成树。在这里插入图片描述

图例

在这里插入图片描述

伪代码

在这里插入图片描述

C语言代码

传入邻接矩阵和初始点

void Prim(MatGraph g,int v )
{
	int lowcost[MAXV];	//距离数组 
	int MIN;			//记录最小距离 
	int closest[MAXV];	//连接点地址 
	int i,j,k;			//k记录最小距离对应的连接点 
	for(i=0;i<g.n;i++)	//初始化 
	{
		lowcost[i]=g.edges[v][i];
		closest[i]=v;
	}
	for(i=1;i<g.n;i++)
	{
		MIN=INF;
		for(j=0;j<g.n;j++)
		{
			if(lowcost[j]!=0&&lowcost[j]<MIN)//取最小的权 
			{
				MIN=lowcost[j];
				k=j;
			}
		}
		printf("边(%d,%d)权为:%d\n",closest[k],k,MIN);
		lowcost[k]=0;	//在输出之后将距离置0,判断为加入已选择的点集 
		for(j=0;j<g.n;j++)	//通过新加入的点扫描,判断是否用更短距离的更新 
		{
			if(g.edges[k][j]!=0&&g.edges[k][j]<lowcost[j])//更新数组 
			{
				lowcost[j]=g.edges[k][j];
				closest[j]=k;
			}
		}
	}
}
时间复杂度分析

困惑+n
智商-n
害怕.jpg
在这里插入图片描述

完整代码

#include<stdio.h>
#include<malloc.h>
#include<algorithm>
using namespace std;

#define INF 32767
#define MAXV 100
typedef char InfoType;

typedef struct
{
	int no;
	InfoType info;
}VertexType;

typedef struct
{
	int edges[MAXV][MAXV];
	int n,e;
	VertexType vexs[MAXV];
}MatGraph;

//创建图的邻接矩阵 
void CreateMat(MatGraph &g,int A[MAXV][6],int n,int e)
{
	int j,i;
	g.n=n;g.e=e;
	for(i=0;i<g.n;i++)
	{
		for(j=0;j<g.n;j++)
		{
			g.edges[i][j]=A[i][j];
		}
	}
}
//输出图的邻接矩阵 
void DispMat(MatGraph g)
{
	int i,j;
	for(i=0;i<g.n;i++)
	{
		for(j=0;j<g.n;j++)
		{
			if(g.edges [i][j]!=INF)
				printf("%4d",g.edges[i][j] );
			else 
				printf("%4s","∞");
		}
		printf("\n");
	}
}

void Prim(MatGraph g,int v )
{
	int lowcost[MAXV];	//距离数组 
	int MIN;			//记录最小距离 
	int closest[MAXV];	//连接点地址 
	int i,j,k;			//k记录最小距离对应的连接点 
	for(i=0;i<g.n;i++)	//初始化 
	{
		lowcost[i]=g.edges[v][i];
		closest[i]=v;
	}
	for(i=1;i<g.n;i++)
	{
		MIN=INF;
		for(j=0;j<g.n;j++)
		{
			if(lowcost[j]!=0&&lowcost[j]<MIN)//取最小的权 
			{
				MIN=lowcost[j];
				k=j;
			}
		}
		printf("边(%d,%d)权为:%d\n",closest[k],k,MIN);
		lowcost[k]=0;	//在输出之后将距离置0,判断为加入已选择的点集 
		for(j=0;j<g.n;j++)	//通过新加入的点扫描,判断是否用更短距离的更新 
		{
			if(g.edges[k][j]!=0&&g.edges[k][j]<lowcost[j])//更新数组 
			{
				lowcost[j]=g.edges[k][j];
				closest[j]=k;
			}
		}
	}
}

typedef struct
{
	int u;
	int v;
	int w;
}Edge;
bool cmp(Edge a,Edge b)
{
    return a.w<b.w;
}
void Kruskal(MatGraph g)
{
	int i,j,ul,vl,snl,sn2,k;
	int vset[MAXV];	//记录点所属的树 
	Edge E[MAXV];	//将所有边的信息保存 
	k=0;
	
	for(i=0;i<g.n;i++)	//保存每条边所属的两个端点已经权 
	{
		for(j=0;j<=i;j++)
		{
			if(g.edges[i][j]!=0&&g.edges[i][j])
			{
				E[k].u=i;
				E[k].v=j;
				E[k].w=g.edges[i][j];
				k++;
			}
		}
	}
	
	sort(E,E+k,cmp);	//从小到大排序 
	for(i=0;i<g.n;i++)	//初始化记录数组,初始状态为自己本身 
	{
		vset[i]= i;
	} 
	k=1;j=0;
	while(k<g.n)
	{
		ul=E[j].u;vl=E[j].v;	//两个端点 
		snl=vset[ul];			 
		sn2=vset[vl];
		if(snl!=sn2)			//判断是否已经被连接了,没有则连接 
		{
			printf("边(%d,%d)权为:%d\n",ul,vl,E[j].w );
			k++;
			for(i=0;i<g.n;i++)
			{
				if(vset[i]==sn2)	//将所有属于第二个端点的点的记录值改到第一个端点 
					vset[i]=snl;
			}
		}
		j++;
	}	
}

int main()
{
	MatGraph g;
	int A[MAXV][6]={
		{0,5,8,7,INF,3},
		{5,0,4,INF,INF,INF},
		{8,4,0,5,INF,9},
		{7,INF,5,0,5,6},
		{INF,INF,INF,5,0,1},
		{3,INF,9,6,1,0}
	};
	int n=6,e=10;			
	CreateMat(g,A,n,e);
	printf("\n用Prim算法计算最小生成树:\n");
	Prim(g,0);
	printf("\n用Kruskal算法计算最小生成树:\n");
	Kruskal(g);
}
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值