最小生成树:
在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的无回路子集,使得的 w(T) 最小,且包含了全部顶点。则此T 为 G 的最小生成树。
最小生成树其实是最小权重生成树的简称。生成树和最小生成树有许多重要的应用。例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
1.prime普里姆算法
思路:
1).输入:一个加权连通图(邻接矩阵存储),其中顶点集合为V(n个);
2).初始化:Vnew= {V0},其中V0为集合V中的任一节点(起始点),定义辅助矩阵edge[n-1],d初始化为V0到剩余节点【0,1,.......,n-1】的长度;
3)循环k=0:n-2共n-1次(确定n-1条边):
a.在辅助矩阵edge[k]---edge[n-1]选取权值最小的边<Vi,Vj>,其中Vi为集合Vnew中的元素,而Vj不在Vnew集合当中。
b.将edge[k]与edge[j]交换,即将Vj加入到了Vnew中d
c.更新到不在Vnew集合的顶点的最短路径。此时使用新加入的节点Vj到剩余节点的距离与已知的距离比大小去较小的即可。
4).输出:使用edge[n-1]描叙最小生成树。
注意:更新策略避免了每次都需要在Vnew和非Vnew集合中遍历寻找最短路径(O(n(n-k))),而是采用新加入的顶点到剩余顶点的路径,与已知的最短路径比较。这样单次循环更新时间花销减小为O(n-k)。
时间复杂度为O(n.^2),由于涉及到交换,空间复杂度为O(1)。
整体思路:即确定一个集合,开始仅仅包含顶点0。在n-1次循环中,确定该集合到剩余节点的最短距离,并将最短距离的终点添加进该集合(并无实质添加,程序中用交换即解决)。更新该集合到剩余顶点的最短距离。
代码:
-
-
void swap(edge& e1,edge& e2){
-
edge temp=e1;
-
e1=e2;
-
e2=temp;
-
}
-
-
-
void prime(int** adjMatrix,int n,edge* ct ){
-
-
int minWeight,minIndex;
-
int currentFrom,currentEnd;
-
bool flag;
-
-
-
for(int i=0;i<n-1;i++){
-
ct[i].from=0;
-
ct[i].end=i+1;
-
ct[i].weight=adjMatrix[0][i+1];
-
}
-
-
for(int i=1;i<n;i++){
-
-
-
minWeight=ct[i-1].weight;
-
minIndex=i-1;
-
flag=false;
-
for(int j=i;j<n-1;j++)
-
if(ct[j].weight<minWeight){
-
minWeight=ct[j].weight;
-
minIndex=j;
-
flag=true;
-
}
-
if(flag)
-
swap(ct[i-1],ct[minIndex]);
-
-
-
currentFrom=ct[i-1].end;
-
for(int j=i;j<n-1;j++){
-
currentEnd=ct[j].end;
-
if(adjMatrix[currentFrom][currentEnd]<ct[j].weight){
-
ct[j].from=currentFrom;
-
ct[j].weight=adjMatrix[currentFrom][currentEnd];
-
}
-
}
-
}
-
}
2.Kruskal克鲁斯卡尔算法
思路:
定义集合标号数组vexSet。
(1)基于图的边集数组存储来完成,实现需要依照权重从小大道排列边集数组。
(2)初始化每一个顶点为单独集合。
(3)遍历边集数组:
a.如果路径两端节点不在一个集合内,储存该路径并将遍历终节点所属的集合全部合并到起始节点所在集合。
b.如果路径两端节点在一个集合内,跳过,继续遍历。
注意:使用标号来标示集合,不在同一个集合保证了无回路存在.
由于需要辅助数组标记顶点所属集合,故算法的空间复杂度为O(n)。而内嵌的循环中,合并集合需要执行n次,故时间复杂度最坏为为O(ne),最好为O(n.^2)。但是注意到,边集数组减小了图的空间复杂度为O(e),其中e为边数。
整体思路:集合合并问题。初始化每个顶点单独成为一个集合。每一轮循环中,确定顶点终点不在同一集合的最短边,保存该最短边,将顶点和终点所属集合合并(此时同一用边的顶点序号来标定同一集合)。
代码:
-
-
void getSet(edge* &edgeSet,int &size,int** adjMatrix,int n){
-
for(int i=1;i<n;i++){
-
for(int j=0;j<i;j++)
-
if(adjMatrix[i][j]!=MAX){
-
size++;
-
if(!edgeSet){
-
edgeSet=(edge*)malloc(sizeof(edge));
-
edgeSet[0].from=j;
-
edgeSet[0].end=i;
-
edgeSet[0].weight=adjMatrix[i][j];
-
}
-
else{
-
edgeSet=(edge*)realloc(edgeSet,size*sizeof(edge));
-
int k=size-2;
-
while(k>=-1&&edgeSet[k].weight>adjMatrix[i][j]){
-
edgeSet[k+1]=edgeSet[k];
-
k--;
-
}
-
edgeSet[k+1].from=j;
-
edgeSet[k+1].end=i;
-
edgeSet[k+1].weight=adjMatrix[i][j];
-
}
-
}
-
}
-
}
-
-
void Kruskal(int** adjMatrix,int n,edge* ct){
-
edge* edgeSet=NULL;
-
int size=0;
-
getSet(edgeSet,size,adjMatrix,n);
-
-
int i,k;
-
int currentEnd;
-
int* vexSet=new int[n];//集合标记辅助数组
-
for(i=0;i<n;i++)
-
vexSet[i]=i;
-
-
for(i=0,k=0;i<size&&k<n-1;i++){//至多有n-1条边
-
if(vexSet[edgeSet[i].from]!=vexSet[edgeSet[i].end]){
-
ct[k++]=edgeSet[i];
-
currentEnd=vexSet[edgeSet[i].end];
-
for(int j=0;j<n;j++)//遍历终节点所属的集合全部合并到起始节点所在集合
-
if(vexSet[j]==currentEnd)
-
vexSet[j]=vexSet[edgeSet[i].from];
-
}
-
}
-
-
free(edgeSet);
-
delete []vexSet;
-
}
3.主程序
使用数组矩阵模拟邻接矩阵,假设最大权重为100。
-
#include<iostream>
-
using namespace std;
-
#define MAX 100
-
-
#include"algo.h"
-
void main(){
-
-
-
int** a=new int*[7];
-
for(int i=0;i<7;i++){
-
a[i]=new int[7];
-
for(int j=0;j<7;j++)
-
a[i][j]=100;
-
}
-
a[3][0]=a[0][3]=5;
-
a[1][0]=a[0][1]=8;
-
a[2][1]=a[1][2]=12;
-
a[3][1]=a[1][3]=3;
-
a[4][1]=a[1][4]=10;
-
a[4][2]=a[2][4]=6;
-
a[5][2]=a[2][5]=2;
-
a[5][3]=a[3][5]=7;
-
a[6][3]=a[3][6]=15;
-
a[5][4]=a[4][5]=9;
-
-
-
edge* ct=new edge[7];
-
-
-
Kruskal(a,7,ct);
-
-
-
for(int i=0;i<6;i++){
-
cout<<ct[i].from<<"--"<<ct[i].end<<": "<<ct[i].weight<<endl;
-
}
-
-
for(int i=0;i<7;i++){
-
delete[]a[i];
-
}
-
delete []a;
-
delete []ct;
-
}