对于连通的无向图G(V,E),若是一个E的无环子集T,能够链接全部节点,而且又具备最小权重,称树g(V,T)为图G(V,E)的最小生成树。算法
Kruskal算法和Prim算法均使用贪心策略实现,二者的实现框架可由下列伪代码表示,首先,是一些叙述时使用的概念。
集合A:某棵最小生成树的子集。
安全边: 加入集合A又不会破坏A性质的边。(在这里也便是,知足: 加入到A中,且能保证A依旧是某棵最小生成树的子集 该性质的边)
begin
A初始为空
while(A未造成最小生成树)
选择一条安全边。
将安全边加入A。
end安全
主要使用 循环不变式进行证实(这个概念在算法导论中常常用到)
循环不变式:在每遍循环开始以前,A是某棵最小生成树的子集。
初始化:集合A直接知足循环不变式
保持:算法循环中,选择安全边保证了该性质。
终止:当算法终止时,全部边均属于某棵最小生成树,因此算法正确。框架
预先的一些概念:
切割:无向图G(V,E)的一个切割(S,V-S)是集合V的一个划分。
横跨:若是边e属于E,且e的一个端点属于S,另外一端点属于V-S,则该边横跨切割(S,V - S)
尊重:若是一个E的子集A中不存在横跨切割(S,V - S)的边,则称该切割尊重集合A.
轻量级边:在横跨一个切割的全部边中,权重最小的边称为轻量级边。
定理:对于无向连通图G(V,E),A为E的子集且包含在在某可最小生成树中,设集合(S,V-S)为图G中尊重A的一个切割,若e为横跨切割(S,V-S)的一条轻量级边,则e对于集合A是安全的,即e为集合A的一条安全边。
(下面的证实相似读书笔记,严谨的证实请参考算法导论)
证实:假设轻量级边e两个端点u,v,(e横跨切割,因此u,v分别属于S,V - S),假设A包含在最小生成子树T(假设T不包含e)中,则 T中存在一条简单路径p由u到v,而且T 存在一条边e';属于该路径而且横跨该切割,如今删除e‘而且加入e造成另外一个生成树T',由于权重e <= e'‘因此 权重T' <= T;由于T为最小生成树,因此T'也为最小生成树。即加入边e后集合A包含在最小生成树T'中,即边e对于集合A为安全边。
推论:对于无向连通图G(V,E),A为E的子集且包含在在某可最小生成树中,C(Vc,Ec)为森林G‘(V,A)(包含G全部顶点,以及集合A中边,由A的定义,G'无环,且包含多个连通份量,由此构成森林G')中的一个连通份量,若是e链接C和其余连通份量的一条轻量级边,则e对集合A是安全的。
证实:容易知道,切割(Vc,V - Vc)尊重A,由定理可得推论正确性。
Kruskal算法:集合A为森林。寻找安全边的方式是,权重最小且链接两个连通份量(树)的边。
Prim算法:集合A为树。寻找安全边的方式为,链接A与A以外节点的权重最小的边。code
主要使用并查集来查询集合选择的边是否属于同一连通份量。
伪代码
A初始为空
创建并查集
按照权重对边进行升序排序。
按顺序考察每条边:
若是边端点分别属于不一样连通份量,加入该边。排序
主要实现了并查集(按秩合并 和 路径压缩)队列
int a[101]; int rk[101]; void init_set() { for(int i = 1; i <= 100; ++i) { a[i] = i; rk[i] = 1; } } int find_set(int x) { int p = x; while(a[p] != p) { p = a[p]; } int now = x; int tmp = 0; while(now != p) { tmp = a[now]; a[now] = p; now = tmp; } return p; } void merge(int xp ,int yp) { if(rk[xp] > rk[yp]) { a[yp] = xp; } else { a[xp] = yp; if(rk[xp] == rk[yp]) { ++rk[yp]; } } } struct Nod { int x,y,w;//分别表示边的端点x,y和边的权重w. Nod():x(0),y(0),w(0){} bool operator < (const Nod& tmp)const { return w < tmp.w; } }; int kruskal() { init_set(); int e = n*n;//边的数量; sort(nod,nod + e); int s1,s2; for(int i = 1; i <= e; ++i) { s1 = find_set(nod[i].x); s2 = find_set(nod[i].y); if(s1 != s2) { merge(s1,s2); } } return ret; }
O(ElgV)
详细分析暂时跳过:it
#define INF 0x7fffffff int in[101]; struct Nod { int id,key;//分别为节点的序号和集合A到该节点权重最小的边的权重。 Nod():id(0),key(0){} bool operator <(const Nod& tmp)const { return key > tmp.key; } }; int key[101];//集合A到该节点的边的权重,不存在该边,设为无穷大。 void prim(int r) { for(int i = 1; i <= n; ++i) { key[i] = INF; in[i] = 0; } Nod tmp; key[r] = 0; tmp.id = r; tmp.key = 0; priority_queue<Nod> q;//使用优先队列维护待选择的节点。 q.push(tmp); while(!q.empty())//操做1,取点 { tmp = q.top(); q.pop(); if(key[tmp.id] < tmp.key)continue; int id = tmp.id; in[id] = 1;//标记为1,,标识属于最小生成树集合; //操做2,考察边。 for(int i = 1; i <= n; ++i)//边数较少可以使用邻接边实现,这里使用矩阵实现, { if(!in[i] && a[id][i] < key[i]) { key[i] = a[id][i]; tmp.id =i; tmp.key = key[i]; q.push(tmp); } } } }
操做1:使用优先队列实现,时间复杂度为lg(V)
操做2:考察全部边,时间复杂度O(E)
总的时间复杂度为:O(Elg(V));
主要参考算法导论,若有错误,恳请指正io