数据结构(C语言实现)-图(3)(无向图的最小生成树:普里姆算法和克鲁斯卡尔算法)

生成树:在一个连通图G中,取它全部顶点和一部分边构成该图的一个子图G’,若子图的所有边能够使全部顶点连通而不形成任何环,则称子图G’是图G的一个生成树。一个有n个顶点的生成树只有n-1条边。

最小生成树:在一个连通网中,每条边的权值会有不同,各边权值之和最小的生成树成为最小生成树。

构造最小生成树有多种算法,大多数算法利用了最小生成树的MST性质,即:假设**N=(V,{E})**是一个连通网,U是顶点集V的一个非空子集,若(u,v)是一条具有最小权值的边,u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
Prim算法和Kruskal算法就是两个利用MST性质构造最小生成树的算法

Prim(普里姆)算法

两个集合:顶点集合U,最初只有一个随机取的顶点;边集合TE,最初是个空集。
重复执行下面操作:在所有u∈U,v∈V-U的边(u,v)∈E中,取一条权值最小的边(u0,v0)放入集合TE,同时v0放入集合U。直至U=V。

//图结构使用前面写过的邻接矩阵的代码
//定义一个辅助数组closedge[],数组的每一个元素代表图中一个顶点。每个顶点有两个属性,adjvex代表该顶点与U中哪个元素相连(可以使与之相连的弧的权值最小),lowcost代表与之相连的弧的权值
typedef struct {
	Elemtype adjvex;       //U中元素
	int lowcost;           //权值
}closedge[MAX_VERTEX_NUM];

void MiniSpanTree_Prim(MGraph G,Elemtype u)//从第u个顶点出发构造G的最小生成树
{
	int initVex = LocateVex(&G, u);
	closedge my_closedge;
	for(int i = 0;i<G.vexnum-1;i++)       //辅助数组初始化
		if (i != initVex)
			my_closedge[i] = { u, G.arcs[initVex][i].adj };//与u相连则为权值,不相连则权值为INFINITY
	my_closedge[initVex].lowcost = 0;     //lowcost设为0标志该顶点已经并入U中

	int min = 0;
	for (int i = 1; i < G.vexnum; i++)   //选择其余G.vexnum-1个顶点
	{
		for (int n = 0; n < MAX_VERTEX_NUM - 1; n++)//选出与U中顶点相连的、具有最小权值的顶点
		{
			if (my_closedge[n].lowcost < my_closedge[min].lowcost)
				min = n;                 //序号记在min中
		}
		printf(。。。);//输出生成树的边
		my_closedge[min].lowcost = 0;    //将该顶点并入U集
		for (int n = 0; n < G.vexnum - 1; n++)//新顶点并入U后更新V-U中顶点的权值和顶点信息
			if (G.arcs[min][n].adj < my_closedge[n].lowcost)
				my_closedge[n] = { min,G.arcs[min][n].adj };
	}
}

Kruskal(克鲁斯卡尔)算法

一个非连通图集合T=(V,{}):拥有全部顶点,没有弧,每个顶点自成一个连通分量
重复执行下面操作:在E中选择权值最小的边,若该边依附的两个顶点位于此时T中两个不同的连通分量中,则将此边加入T中,否则舍弃此边而寻找下一条权值最小的边。直至T中只有一个连通分量

//Kruskal算法的图结构借助边集数组实现,边集数组由两个一维数组构成。
//一个一维数组存储顶点信息,另外一个一维结构体数组存储边的信息。
//以下是边集数组的实现
#define MAX_ARCTEX_NUM 20
#define INFINITY 65535
typedef char Elemtype;

typedef struct Edge{
	int head;    //有向图时为弧头指向的顶点序号
	int tail;    //无向图时为弧的两端连接的顶点序号
	int weight;  //权重
}Edge,EdgeVec[MAX_ARCTEX_NUM];

//定义图结构
typedef struct EdgeGraph{
	Elemtype vexs[MAX_VERTEX_NUM];  //顶点数组存储顶点信息
	EdgeVec arcs;                   //弧数组存储弧信息
	int vexnum,arcnum;              //顶点数,弧数
}EdgeGraph;

int LocateVex(EdgeGraph *G,Elemtype info)//确定顶点info在顶点数组中的序号
{
	for(int i = 0; i<G->vexnum; i++)
		if(G->vexs[i] == info)
			return i;
}

void CreateEdgeGraph(EdgeGraph *G)
{
	printf("请输入图的顶点数量和边的数量:");
	scanf_s("%d %d",&(G->vexnum),&(G->arcnum));
	for(int i = 0; i < G->vexnum; i++)
	{
		printf("输入顶点值:");
		scanf_s("%c",&(G->vexs[i]));
	}

	for(int i = 0; i<G->vexnum; i++)  //顶点数组初始化
		G->arcs[i].weight = INFINITY;
	for(int i = 0; i<G->arcnum; i++)  //边集数组赋值
	{
		int weight;
		Elemtype head,tail;
		printf("请输入边的起点终点和权值:");
		scanf_s("%c %c %d",&tail,&head,&weight);
		int v1 = LocateVex(G,tail);
		int v2 = LocateVex(G,head);
		G->arcs[i].weight = weight;
		G->arcs[i].head = v2;
		G->arcs[i].head = v1;
	}
}
//接下来是Kruskal算法的实现
void MiniSpanTree_Kruskal(EdgeGraph *G)
{
	Sort(G);      //对EdgeVec[]即边集数组进行排序,得到权值从小到大的数组
	int *Vexset;  //需要设置一个辅助数组,用于记录各顶点是否在同一个连通分量	
	Vexset = (int*)malloc(sizeof(int)*G.vexnum); //为辅助数组动态分配内存	
	for (i = 0; i < G.vexnum-1; i++)
		Vexset[i] = i;
	int v1,v2,vs1,vs2;
	for(int i = 0; i<G.arcnum-1; i++)
	{
		v1 = LocateVex(&G,EdgeVec[i].head);  //弧连接的顶点的序号
		v2 = LocateVex(&G,EdgeVec[i].tail);
		vs1 = Vexset[v1];                    //顶点所在连通分量的标记
		vs2 = VexSet[v2];
		if(vs1 != vs2)   //弧的两端不在同一连通分量
		{
			printf("%c -> %c \n",EdgeVec[i].head,EdgeVec[i].tail);
			for(int j = 0;j<G.vexnum-1;j++)
			{
				if(Vexset[j] == vs2) //所有和vs2在同一连通分量的顶点加入连通分量vs1
					Vexset[j] = vs1;
			}
		}
	}
}

//快速排序算法,将边集数组按权值大小从新排序
void Sort(EdgeGraph *G)
{
	int min_weight,addr;
	EdgeVec temp;
	for (int i = 0; i < G.arcnum-1; i++)
	{
		min_weight = EdgeVec[i].weight;
		addr = i;
		for (int j = i + 1; j<G.arcnum-1;j++)
		{
			if (min_weight>EdgeVec[j].weight)
			{
				min_weight = EdgeVec[j].weight;
					addr = j;
			}
		}
		if (addr != i)
		{
			temp = EdgeVec[i];
			EdgeVec[i] = EdgeVec[addr];
			EdgeVec[addr] = temp;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值