首先给出 一些 概念问题:
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)