博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[数据结构]最小生成树的实现
阅读量:6217 次
发布时间:2019-06-21

本文共 9487 字,大约阅读时间需要 31 分钟。

一、问题描述

在电子电路设计中,我们常常需要将多个组件连接在一起,显然我们希望所用的线能够最短,由此引出最小生成树问题。

在本实验中,我们将讨论解决最小生成树问题的两种算法:Prim算法和Kruskal算法。其中Prim算法的时间复杂度为O(N^2),如果使用二叉堆来优化寻找新加入的结点,则可以将时间复杂度降到O(E logN),如果使用斐波那契堆,时间复杂度将改善为O(E+N logN);Kruskal算法的时间复杂度为O(E logE)。

本实验根据算法的不同,对图的存储方式也是不一样的。在Prim算法中,使用邻接表存储图;而在Kruskal算法的实现中,考虑到实现的方便,直接存储了边的相关信息。具体实现下面将会给出。

二、数据结构——邻接表

1、邻接表(Prim算法)

邻接表是图的一种链式存储结构。首先有一个包含所有结点的顺序表,顺序表的每个元素对应一个单链表,表示从该结点出发的弧。每个结点由3部分组成:指针域,指向从当前结点出发的下一条弧的尾结点;尾结点标号;数据域,保存当前弧的信息,比如权重等等。

需要说明的是,这适用于有向图与无向图,不妨认为无向图的边u--v就是有向图的两条弧u->v与v->u。

2、另一种存储图的方式(Kruskal算法)

由Kruskal算法的特点,为了方便找边,我们可以直接用一个“集合”(可以用静态查找表实现,这里的集合的意思是边之间没有逻辑关系)保存所有的边。“集合”中的每个结点由3部分组成:结点1、结点2和当前边的信息(比如权重)。

可以看出,这样的方式更适合于存储无向图,且对点的信息的访问需求不大,否则将会增大查询的时间复杂度。

三、算法的设计和实现

1、Prim算法

(1)简述

Prim算法的工作原理与解决单源点最短路的Dijkstra算法相似。我们定义一个集合A,在集合A里面的所有结点已经形成了一棵最小生成树;然后不断向其添加新结点,使得集合A的性质保持不变,直到网络中的所有结点加入到集合A中,算法终止;此时,得到一棵最小生成树。

本策略属于贪心策略,保证每次添加的结点所对应的新边是权重增加得最小的。

(2)伪代码

1 Prim(G, w, x) 2     for each u∈G.V 3         u: cost = ∞ 4         u: father = NULL 5     x: cost = 0 6     for k: 2 to n 7         u = MinCost(A) //表示从A出发到V\A的最短弧的尾结点 8         for each v∈G.adj[u] 9             if v∈G && w(u, v) < v: cost10                 v: father = u11                 v: cost = w(u, v)12                 Add v into A

(3)时间复杂度分析

由伪代码可以看出,Prim算法的时间复杂度为O(n^2)。

为了减小算法的时间复杂度,需要找一种更高效的选择新边的方法。由于是寻找最小的边,可以维护一个优先队列,队列排序的关键字为cost,即集合A中的结点到达某个点的最小花费。我们约定,如果不存在这样的边,则v: cost = ∞;集合A中的结点有v: cost = 0。此时,算法的运行时间取决于优先队列的实现方式。

如果用二叉堆来实现,算法的总时间代价为O(E logN);如果使用斐波拉契堆来实现,算法的运行时间将改进到O(E + N logN)。

2、Kruskal算法

(1)简述

Kruskal算法也属于贪心算法。每次选择一条连接两个不同的连通分量的最短的边,加入到森林,直到添加了n-1条边,得到一棵树,就是需要找的最小生成树。

(2)伪代码

1 Kruskal(G, w)2     A = ∅3     for each v∈G.V4         Make_Set(v)5     将G.E的边不递减排序6     for each edge(u, v)∈G.E //按照不递减顺序访问,直到加入n-1条边7         if Find_Set(u) != Find_Set(v) //两结点不在同一连通分量8             Add (u, v) into A9             Union(u, v) //合并两个连通分量

(3)时间复杂度分析

排序可以使用快排,时间复杂度为O(E logE)。合并两个连通分量时,可以使用并查集且使用路径压缩的方式优化,总运行时间为O((N + E)α(N))。所以Kruskal算法的时间复杂度为O(E logE)。

3、MST性质及其证明

(1)MST性质:设G=(N,E)是一个在边E上定义了实数值权重函数w的连通无向图。设集合A为E的一个子集,且A包括在图G的某棵最小生成树中,设(S,N-S)是图G中尊重集合A的任意一个切割(如果一条边(u,v)∈E的一个端点位于集合S,另一个断点位于集合N-S,则称该条边横跨切割(S,N-S)。如果集合A中不存在横跨该切割的边,则称该切割尊重集合A),又设(u,v)是横跨切割(S,N-S)的一条轻量级边。那么边(u,v)对于集合A是安全的(对于(u,v),如果加入集合A后,使得A不违反循环不变式,即A∪{(u,v)}也是某棵最小生成树的子集,则称(u,v)为集合A的安全边)。

(2)证明:(证明参考《算法导论》)

设T是一棵包括A的最小生成树,假设T不包含轻量级边(u,v)。

边(u,v)与T中从结点u到结点v的简单路径p形成一个环路,如下图。由于结点u和结点v分别处在切割(S,N-S)的两端,T中至少有一条边属于简单路径p并且横跨该切割。设(x,y)为这样的一条边。因为切割(S,N-S)尊重集合A,故边(x,y)不在集合A中。由于边(x,y)位于T中从u到v的唯一简单路径上,将该条边删除会导致T被分解为两个连通分量。将(u,v)加上去可以将这两个连通分量连接起来,形成一棵新的生成树T’=T\{(x,y)}∪{(u,v)}。

如图,黑色结点位于集合S里,白色结点位于集合N-S里。图中仅描述了最小生成树T中的边,而没有绘出图G中的其他边。集合A中的边都为灰色粗边,边(u,v)是横跨(S,N-S)的一条轻量级边。边(x,y)是树T里面从结点u到结点v的唯一简单路径上的一条边。要形成一棵包含(u,v)的最小生成树T’,只需要在T中删除边(x,y),然后加上边(u,v)即可。

下证T’为一棵最小生成树。

由于边(u,v)是横跨切割(S,N-S)的一条轻量级边,且(x,y)也横跨该切割,所以w(u,v)<=w(x,y)。因此,w(T’) = w(T) - w(x,y) + w(u,v) <= w(T)。又T是一棵最小生成树,从而有w(T) = w(T’),故T’也是一棵最小生成树。

下证边(u,v)对于集合A是安全的。

由于A包含于T,且(x,y)∉A,所以A包含于T’;因此A∪{(u,v)}包含于T’。由于T’是最小生成树,故边(u,v)对于集合A是安全的。证毕。

四、预期结果和实验中的问题

1、预期结果

程序可以正确地找出图中的一棵最小生成树。

当最小生成树唯一存在时,所找到的最小生成树是唯一的;当最小生成树存在不唯一时,算法将会找出其中一棵生成树,当然程序每次运行所找出的最小生成树都是相同的。

2、实验中的问题及思考

(1)次小生成树

a)定义:设G=(N,E)为一连通无向图,其权重函数为w,假定|E| >= |N|并且所有的权重都互不相同。设τ为G的所有生成树的集合,T’为G的一棵最小生成树,T是一棵生成树,若满足w(T) = min{w(T’’)},T’’∈τ\T’,则称T是一棵次小生成树。

b)还没想出除了暴力搜索以外的算法。

(2)瓶颈生成树

a)定义:无向图G的瓶颈生成树T是G的一棵生成树,其最大边的权重是G的所有生成树中最小的。

b)可以发现,最小生成树都是瓶颈生成树,而瓶颈生成树不一定都是最小生成树。

附:c++源代码:

1、Prim算法实现

1 /*  2 项目:最小生成树——Prim算法  3 作者:张译尹  4 */  5 #include 
6 #include
7 #include
8 9 using namespace std; 10 #define MaxN 120 //结点数 11 12 //结点 13 struct node 14 { 15 int v, w; 16 node *next; 17 }; 18 19 //图 20 class My_graph 21 { 22 private: 23 //NodeList L; 24 node *adj[MaxN]; //单链表头指针 25 int len; //链表长度(结点个数) 26 27 int VexNum, ArcNum; //图的结点数和边数 28 int Kind; //图的种类:0-无向图,1-有向图 29 public: 30 void Init(int vNum, int kind) 31 { 32 VexNum = vNum; 33 ArcNum = 0; 34 Kind = kind; 35 36 //L.Init(); 37 memset(adj, 0, sizeof(adj)); 38 len = 0; 39 } 40 void D_Addedge(int u, int v, int w) //u->v,权值为w。有向图 41 { 42 node *p = new node; 43 p -> v = v; 44 p -> w = w; 45 p -> next = adj[u]; 46 adj[u]= p; 47 } 48 void UD_Addedge(int u, int v, int w) //u->v,权值为w。无向图 49 { 50 node *p = new node; 51 p -> v = v; 52 p -> w = w; 53 p -> next = adj[u]; 54 adj[u]= p; 55 56 p = new node; 57 p -> v = u; 58 p -> w = w; 59 p -> next = adj[v]; 60 adj[v]= p; 61 } 62 void Addedge(int u, int v, int w) 63 { 64 if(Kind == 0) 65 UD_Addedge(u, v, w); 66 else 67 D_Addedge(u, v, w); 68 ArcNum++; 69 } 70 int Get_VexNum() 71 { 72 return VexNum; 73 } 74 void Prim() 75 { 76 int i, j; 77 int n = VexNum, Ans = 0; 78 int cost[MaxN], MinCost; 79 int fa[MaxN], v, x; 80 bool vis[MaxN]; 81 memset(cost, 0x3f, sizeof(cost)); 82 memset(vis, false, sizeof(vis)); 83 84 cost[1] = 0; //选择了结点1 85 vis[1] = true; 86 fa[1] = 1; 87 for(node *p =adj[1]; p != NULL; p = p -> next) 88 { 89 cost[p -> v] = p -> w; 90 fa[p -> v] = 1; 91 } 92 93 for(i = 2; i <= n; i++) //寻找第i个结点 94 { 95 MinCost = 0x3f; 96 v = -1; 97 for(j = 1; j <= n; j++) 98 { 99 if(!vis[j] && cost[j] < MinCost)100 {101 v = j;102 MinCost = cost[j];103 }104 }105 if(v != -1)106 {107 vis[v] = true; //将v加入108 printf("(%d %d): %d\n", fa[v], v, MinCost);109 110 Ans += MinCost;111 for(node *p = adj[v]; p != NULL; p = p -> next)112 {113 x = p -> v;114 if(!vis[x] && cost[x] > (p -> w))115 {116 cost[x] = p -> w;117 fa[x] = v;118 }119 }120 }121 }122 printf("最小生成树的权值和为:%d\n", Ans);123 } //Prim 124 };125 126 void Read_and_Build(My_graph &G)127 {128 int n, m, Kind;129 int u, v, w;130 int i;131 //My_graph G;132 133 printf("请输入图的类型:1为有向图,0为无向图。\n");134 scanf("%d", &Kind);135 136 printf("请输入图的结点数和边数,用空格隔开。\n");137 scanf("%d%d", &n, &m);138 G.Init(n, Kind);139 140 printf("请输入图中所有的边,格式为空格相隔的3个数,有向图表示“起始点 结束点 边权”,无向图表示“点1 点2 边权”。其中结点的标号范围为[1,n]。\n");141 for(i = 1; i <= m; i++)142 {143 scanf("%d%d%d", &u, &v, &w);144 G.Addedge(u, v, w);145 }146 }147 148 int main()149 {150 My_graph G;151 Read_and_Build(G);152 G.Prim();153 return 0;154 }
View Code

2、Kruskal算法实现

1 /*  2 项目:最小生成树——Kruskal算法  3 作者:张译尹  4 */  5 #include 
6 #include
7 #include
8 #include
9 10 using namespace std; 11 #define MaxN 120 //结点数 12 #define MaxM 10020 //边数 13 14 //边 15 struct E 16 { 17 int u, v, w; 18 }; 19 20 class My_graph 21 { 22 private: 23 E Edge[MaxM]; //边 24 25 int VexNum, ArcNum; //图的结点数和边数 26 public: 27 void Init(int vNum) 28 { 29 VexNum = vNum; 30 ArcNum = 0; 31 memset(Edge, 0, sizeof(Edge)); 32 } 33 void Addedge(int u, int v, int w) //u->v,权值为w 34 { 35 ArcNum++; 36 Edge[ArcNum].u = u; 37 Edge[ArcNum].v = v; 38 Edge[ArcNum].w = w; 39 } 40 static bool Cmp(E a, E b) 41 { 42 return (a.w) < (b.w); 43 } 44 int Find(int x, int fa[]) 45 { 46 if(fa[x]==-1) 47 return x; 48 return fa[x]=Find(fa[x], fa); 49 } 50 void Kruskal() 51 { 52 sort(Edge + 1, Edge + ArcNum + 1, Cmp); 53 int i, j = 1; 54 int u, v; 55 int fa[MaxN]; 56 int Ans = 0; 57 memset(fa, -1, sizeof(fa)); 58 for(i = 1; i <= VexNum - 1; i++) 59 { 60 while(j <= ArcNum) 61 { 62 u = Edge[j].u; u = Find(u, fa); 63 v = Edge[j].v; v = Find(v, fa); 64 if(u != v) 65 { 66 if(u > v) 67 swap(u, v); 68 fa[v] = u; 69 Ans += Edge[j].w; 70 printf("(%d,%d):%d\n", u, v, Edge[j].w); 71 j++; 72 break; 73 } 74 j++; 75 } 76 } 77 printf("最小生成树的权值和为:%d\n", Ans); 78 } //Kruskal 79 }; 80 81 void Read_and_Build(My_graph &G) 82 { 83 int n, m; 84 int u, v, w; 85 int i; 86 //My_graph G; 87 88 printf("请输入图的结点数和边数,用空格隔开。\n"); 89 scanf("%d%d", &n, &m); 90 G.Init(n); 91 92 printf("请输入图中所有的边,格式为空格相隔的3个数,表示“点1 点2 边权”。其中结点的标号范围为[1,n]。\n"); 93 for(i = 1; i <= m; i++) 94 { 95 scanf("%d%d%d", &u, &v, &w); 96 G.Addedge(u, v, w); 97 } 98 } 99 100 int main()101 {102 My_graph G;103 Read_and_Build(G);104 G.Kruskal();105 return 0;106 }
View Code

 

转载于:https://www.cnblogs.com/CQBZOIer-zyy/p/5185412.html

你可能感兴趣的文章
scala第八天
查看>>
JAVA图书管理系统汇总共27个 ——下载目录
查看>>
Linux下将ISO文件安装为本地YUM源
查看>>
flash light
查看>>
PullableView
查看>>
IT人不要一直做技术
查看>>
opencv编译打包指令
查看>>
html5 canvas详解
查看>>
打包war 提示xxx v2不存在解决方法
查看>>
strongswan ikev2 server on ubuntu 14.04
查看>>
javascript操作DOM
查看>>
Hadoop环境配置
查看>>
我的友情链接
查看>>
spring的事务管理
查看>>
【总结】BeautifulSoup速查手册
查看>>
清除默认共享文件夹账号密码
查看>>
Python3学习笔记——数据类型和变量
查看>>
Intel 11代全新核显出现了!好强 好乱
查看>>
Windows Server 2012四个版本的异同
查看>>
新的开始
查看>>