Defined
对于连通的无向图G(V,E),如果一个E的无环子集T,可以连接所有节点,并且又具有最小权重,称树g(V,T)为图G(V,E)的最小生成树。
Concept pseudo-code
Kruskal算法和Prim算法均使用贪心策略实现,两者的实现框架可由下列伪代码表示,首先,是一些叙述时使用的概念。集合A:某棵最小生成树的子集。安全边:加入集合A又不会破坏A性质的边。begin A初始为空 while(A未形成最小生成树) 选择一条安全边。 将安全边加入A。end
Proof of correctness of algorithm
主要使用 循环不变式进行证明(这个概念在算法导论中经常用到)循环不变式: 在每遍循环开始之前,A是某棵最小生成树的子集。初始化:集合A直接满足循环不变式保持:算法循环中,选择安全边保证了该性质。终止:当算法终止时,所有边均属于某棵最小生成树,所以算法正确。
About secure edges
预先的一些概念:切割:无向图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之外节点的权重最小的边。
The Kruskal algorithm mainly uses and checks the set to query whether the selected edge of the collection belongs to the same connected component. Pseudo code
begin A初始为空 建立并查集 按照权重对边进行升序排序。 按顺序考察每条边: 如果边端点分别属于不同连通分量,加入该边。 end
Code implementation
主要实现了并查集(按秩合并 和 路径压缩)
int a[101];int rk[101];void Init_set () {for (int i = 1; i <=; ++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]; The}}struct nod{int x,y,w;//represents the edge's endpoint x, Y, and the Edge's weight w respectively. Nod (): X (0), y (0), W (0) {} BOOL operator < (CONST nod& TMP) Const {return W < TMP.W; }};int Kruskal () {init_set (); int e = number of n*n;//edges; 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;}
Analysis of time complexity
O(ElgV)详细分析暂时跳过:
Prim algorithm
#define INF 0x7fffffffint in[101];struct nod{int id,key;//are the weights of the node's ordinal and collection A to the edge of the node with the least weight. Nod (): ID (0), key (0) {} BOOL operator < (CONST nod& TMP) const {return key > Tmp.key; }};int key[101];//The weight of the edge of a collection A to that node, no such side exists, and is set to infinity. 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;//uses priority queues to maintain the nodes to be selected. Q.push (TMP); while (!q.empty ())//operation 1, take point {tmp = Q.top (); Q.pop (); if (Key[tmp.id] < Tmp.key) continue; int id = tmp.id; In[id] = 1;//is marked as 1, and the identity belongs to the minimum spanning tree collection;//operation 2, examining edges. for (int i = 1; I <= n; ++i)//few edges can be implemented using adjacency edges, where matrix implementations are used, {if (!in[i] && a[id][i] < key[i]) {Key[i] = A[id][i]; Tmp.id =i; Tmp.key = Key[i]; Q.push (TMP); } } }}
Analysis of time complexity
操作1:使用优先队列实现,时间复杂度为lg(V)操作2:考察所有边,时间复杂度O(E)总的时间复杂度为:O(Elg(V));
Introduction to the main reference algorithm, if there are errors, please correct me
Minimum spanning tree algorithm: Kruskal algorithm prim algorithm