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

首先给出 一些 概念问题:

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)

时间: 2024-10-09 23:08:43

看数据结构写代码(42)最小生成树的相关文章

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(44) 判断无向图是否有环路

在 看 严蔚敏的 数据结构 一书 7.5小节时,书上 说" 判断有向图是否存在环要不无向图复杂.对于无向图来说,深度优先遍历过程中遇到回边(即指向已访问过的顶点的边),则必定存在环路". 看的不明白,所以 网上 百度了一下. 有了思路:故写下算法 和思路,以便以后 温故. 思路: 1.一个n个顶点,e条边的 无向图,若 e>= n,必有环路. 2.若 e < n ,需要 深度 遍历,并把 父节点传入 参数中,如果 遇到 一个 节点 被访问过 并且 不是 父节点,那么 就有环

看数据结构写代码(67) 置换 _ 选择排序(完结篇)

杂谈: 严蔚敏版<数据结构(C语言版)> 一书 终于看完了.这是 一个完结,也是 一个新的开端.<算法导论> 已到手. 置换选择排序的思想 是 将 归并段 尽量 变的 更大,而不是根据 内存 大小 限制在 固定的 大小. 这样 可以 利用赫夫曼树 来 进行 最优归并树,从而 使 外存 读写次数 最少. 下面给出 具体 代码:欢迎指出代码不足. // Replace_Selcetion.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h&q

看数据结构写代码(50)伙伴系统

伙伴系统 是一种 只 可以 分配 2的 幂次方 个 空间的 ,回收 内存 时 只 合并 "伙伴空间" 的一种 动态内存管理方式. 例如 一个 空间 大小 为 64 的 内存,伙伴 系统 为 这 64 的内存  建立 一组 双向循环 链表,分别 管理着  2的 0 次方,2的1 次方幂,2的 2 次方幂...2的6次方幂的 可用空间. 即使 我们 只想分配 一个 大小 为3的 空间,系统 却 只能 返回 一个 内存 大小 为 4(2的2次方)的 一个空间. 系统 在 初始化的 时候 ,并

看数据结构写代码(47)迪杰斯特拉最短路径算法

这个算法的 思想 根 求 最小生成树算法 普里姆(Prim)算法 极其相似.迪杰斯算法 是求 一个顶点 到其他 顶点的 最短路径算法. 下面 上代码:(用的是 邻接矩阵 表示法) //迪杰斯特拉 最短路径. //从 vex顶点 到其他 顶点的 最短路径 void shortestPath_Dij(MGraph g,char vex){ int loc = graphLocation(g,vex); int minArray[MAX_VERTEX_NUM]={0};//最小路径值 bool fin

看数据结构写代码(15)链式队列的实现

队列 和 栈 是 一种 受限制的 线性表.所以 他们的 实现方式 都 相差 无几.之前有过  链栈 和 链式线性表 的 实现经验,自然 写 链队 ,也毫无问题. 下面详细讲解每一段代码 的技术要点 下面是队列节点的数据结构 struct QueueNode { ElementType data; QueueNode * next; }; //生成一个节点 QueueNode * queueNodeMake(ElementType data){ QueueNode * pNode = (Queue

看数据结构写代码(35) 图的邻接矩阵表示法

杂谈:最近清明小长假,好好的放松了一下.节前 和 节后 都有点 松懈.不好,不好.贵在坚持.加油. 图的邻接矩阵表示法是用 两个数组 来表示 图的数据结构.一个是顶点数组,另一个是邻接矩阵数组.邻接矩阵 里存放着 顶点的关系. 用邻接矩阵表示图,在 看 顶点之间 是否有边,或者 求顶点的度等操作时比较简单.但空间浪费巨大,在插入,删除 顶点 和边 操作时 需要 移动大量数据,造成不便.所以在插入删除比较多,节点数比较多的时候 不宜 使用这种结构. 下面上代码: 源代码网盘地址:点击打开链接 //

看数据结构写代码(43) 关节点

首先 说明一下 概念问题: 关节点 :如果删除无向 图中的一个顶点,以及与顶点相关的边,把 图的 一个连通 分量 变成 两个 以上的 连通 分量.这样的顶点叫做关节点. 没有 关节点的 无向图,叫做 重连通图.重连通图中 任意 两个顶点 至少 存在 两条以上的 通路. 如果 删除 连通图上的 k个 节点,才能 破坏 他的连通性,那么 这个连通图的 连通度 为k. 下面的算法 是 求 连通图的 关节点,并没有 考虑 求图的 关节点,不过 要 改成 图的 关节点 也不难,只要 加 一个 for i

看数据结构写代码(57) AVL树的删除

上一节 已经说了 AVL树的插入 操作,可是 只有 插入,没有删除,怎么能叫 动态 查找表呢. 呵呵,博主 赶紧 去 研究了一番.下面 是成果: AVL树的删除 大致 分为 两大块: 1. 查找节点 并 删除 2. 保持 删除 后 平衡因子的 影响 1. 首先 找到 这个 节点,如果 节点 不存在,直接 退出 函数 if (*tree == NULL){//没找到 return false; } 2.如果 存在,分为 四种情况:(根 二叉 排序树的 删除 类似) 1.节点 为 叶子 节点,直接