码迷,mamicode.com
首页 > 其他好文 > 详细

看数据结构写代码(42)最小生成树

时间:2015-04-13 09:47:10      阅读:187      评论:0      收藏:0      [点我收藏+]

标签:最小生成树   普里姆算法   克鲁斯卡尔算法   数据结构   

首先给出 一些 概念问题:

1.生成树: 一个n个顶点的 连通图 的 极小连通子图。 它含有n个顶点,但只有 n-1条边,不存在回路。

2.最小生成树:一个带权的 无向连通图,求出 各边权值相加  最小的 生成树,叫做最小生成树。

所以 求最小生成树  首先 要满足: 1. 首先 是 无向图 2. 必须是 连通图(任意两个顶点可达)3.带权

简单的说 就是 必须是 连通网。


求 最小生成树,严蔚敏的 数据结构 给出了 两种 方法:普里姆算法和 克鲁斯卡尔算法。

普里姆算法:

技术分享


克鲁斯卡尔算法:

技术分享

克鲁斯卡尔算法舍弃的边, 是因为造成了 回路。


由于普里姆 算法 里 需要 大量 查找 两个顶点之间的 权值,所以 用 图的 邻接矩阵 表示法,最为合适,时间复杂度为O(1)。其他表示 方法 都需要 遍历 链表,为O(len),len为链表的表长。


完整代码工程文件网盘地址:点击打开链接


下面 给出 普里姆算法的代码:

//普里姆最小生成树算法
struct LowCost//最小代价数组
{
	char toVex;//顶点名称
	int cost;//代价
};

int minLowCost(LowCost * array,int vexNum){
	int min = INFINITY;
	int k = -1;
	for (int i = 0; i < vexNum; i++){
		int cost = array[i].cost;
		if (cost < min && cost != 0 ){
			min = array[i].cost;
			k = i;
		}
	}
	return k;
}

void miniSpanTree_Prim(MGraph g,char vex){
	LowCost lcArray[MAX_VERTEX_NUM];
	int k = graphLocation(g,vex);
	for (int i = 0; i < g.vexNum; i++){ //初始化lcArray
		lcArray[i].toVex = vex;
		lcArray[i].cost = g.arcs[k][i].adj;
	}
	lcArray[k].cost = 0;//将k 加入到 集合 u
	printf("-----------最小生成树----------------\n");
	for (int i = 1; i < g.vexNum; i++){//总共n-1 次..
		k = minLowCost(lcArray,g.vexNum);
		printf("%c - %c 权值为%d\n",lcArray[k].toVex,g.vexs[k],lcArray[k].cost);
		lcArray[k].cost = 0;//将k 加入到集合u 中.
		for (int i = 0; i < g.vexNum; i++){//获取 新的 最小代价 
			int min = lcArray[i].cost;
			if (min != 0 && g.arcs[k][i].adj < min ){//集合u中的 不参与
				lcArray[i].toVex = g.vexs[k];
				lcArray[i].cost = g.arcs[k][i].adj;
			}
		}
	}

}

从代码可以 看出 普里姆 算法 的时间 复杂度 为 O(n*n),n为 顶点数,适合 求 边稠密的图。


克鲁斯卡尔算法代码如下:

具体思路是: 将 图的所有边 从小到大顺序存入数组中,然后 按顺组 顺序 取 n-1 个边,(n是顶点树),舍弃 会造成 回路的边,舍弃的边 不算在 n-1个边里。

克鲁斯卡尔算法 适合 用 多重邻接表 来 实现,邻接矩阵 数组 初始化 是 o(n * n/2) ,n是顶点数,邻接表 不宜 求出 所有边,而 多重链接表 的初始化 为 O(e),e为边数。

下面代码 没用用多重邻接表的方式,用的是 邻接矩阵。

//克鲁斯卡尔最小生成树.
struct Edge//边集
{
	int begin;
	int end;
	int cost;
};

//快速排序边集数组
void qSort(Edge * array,int left,int right){
	if (left >= right){
		return;
	}
	int i = left;
	int j = right;
	Edge base = array[i];
	while (j != i){
		while (j > i && array[j].cost >= base.cost) j--;
		while (j > i && array[i].cost <= base.cost) i++;
		if (j > i){
			Edge temp = array[j];
			array[j] = array[i];
			array[i] = temp;
		}
	}
	array[left] = array[i];
	array[i] = base;
	qSort(array,left,i-1);
	qSort(array,i+1,right);
}

//将邻接矩阵转换成边集数组,并用快速排序从 小到大排序.
void covertToEdgeArray(MGraph g,Edge * edgeArray){
	//根据 矩阵的上三角 部分 来初始化 边集数组
	int count = 0;
	for (int i = 0; i < g.vexNum; i++){
		for (int j = i+1; j < g.vexNum; j++){
			int cost = g.arcs[i][j].adj;
			if (cost != INFINITY){
				edgeArray[count].begin = i;
				edgeArray[count].end = j;
				edgeArray[count].cost = cost;
				count++;
			}
		}
	}
	//快速排序 边集数组
	qSort(edgeArray,0,g.arcNum-1);
}

int getParent(int k,int * parent){
	while (parent[k] > 0 ){
		k = parent[k];
	}
	return k;
}

void miniSpanTree_Cruskal(MGraph g){
	printf("\n-----------克鲁斯卡尔最小生成树----------------\n");	
	Edge edgeArray[MAX_EDGE_NUM];//边集.
	int parent[MAX_VERTEX_NUM] = {0};
	covertToEdgeArray(g,edgeArray);//将邻接矩阵的边按照从小到大顺序存入数组.
	int times = 0;
	for (int i = 0; i < g.arcNum; i++){
		Edge edge = edgeArray[i];
		int p1 = getParent(edge.begin,parent);
		int p2 = getParent(edge.end,parent);
		if (p1 != p2){//父节点不同,不存在回路
			//parent[edge.begin] = edge.end;
			parent[p1] = p2;
			printf("%c - %c 权值为%d\n",g.vexs[edge.begin],g.vexs[edge.end],edge.cost);
			times++;
			if (times >= g.vexNum-1){
				return;
			}
		}
	}
}

克鲁斯卡尔算法的时间复杂度为 O(eloge),e为图的边数,适合求 边稀疏的图。

运行截图:

技术分享


上面 的 无向网,根据下面书 上的 这张图来的,只不过 将 顶点名称 (v1,v2...v6) 改成 (abcdef)

技术分享

看数据结构写代码(42)最小生成树

标签:最小生成树   普里姆算法   克鲁斯卡尔算法   数据结构   

原文地址:http://blog.csdn.net/fuming0210sc/article/details/45010873

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!