普林演算法
此條目或許過多或不當使用受版權保護的文字、圖片及多媒體檔案。 (2020年4月8日) |
普里姆算法(英語:Prim's algorithm)是圖論中的一種貪心算法,可在一個加權連通圖中找到其最小生成樹。意即由此算法搜索到的邊子集所構成的樹中,不但包括了連通圖裡的所有頂點,且其所有邊的權值之和亦為最小。該算法於1930年由捷克數學家沃伊捷赫·亞爾尼克發現;並在1957年由美國計算機科學家羅伯特·C·普里姆獨立發現;1959年,艾茲格·迪科斯徹再次發現了該算法。因此,在某些場合,普里姆算法又被稱為DJP算法、亞爾尼克算法或普里姆-亞爾尼克算法。
描述
從單一頂點開始,普里姆算法按照以下步驟逐步擴大樹中所含頂點的數目,直到遍及連通圖的所有頂點。
- 輸入:一個加權連通圖,其中頂點集合為 ,邊集合為 ;
- 初始化: ,其中 為集合 中的任一節點(起始點), ;
- 重複下列操作,直到 :
- 在集合 中選取權值最小的邊 ,其中 為集合 中的元素,而 則是 中沒有加入 的頂點(如果存在有多條滿足前述條件即具有相同權值的邊,則可任意選取其中之一);
- 將 加入集合 中,將 加入集合 中;
- 輸出:使用集合 和 來描述所得到的最小生成樹。
時間複雜度
最小邊、權的數據結構 | 時間複雜度(總計) |
---|---|
鄰接矩陣、搜索 | |
二叉堆(後文偽代碼中使用的數據結構)、鄰接表 | |
斐波那契堆、鄰接表 |
通過鄰接矩陣圖表示的簡易實現中,找到所有最小權邊共需 的運行時間。使用簡單的二叉堆與鄰接表來表示的話,普里姆算法的運行時間則可縮減為 ,其中 為連通圖的邊集大小, 為點集大小。如果使用較為複雜的斐波那契堆,則可將運行時間進一步縮短為 ,這在連通圖足夠密集時(當 滿足 條件時),可較顯著地提高運行速度。
例示
圖例 | 說明 | 不可選 | 可選 | 已選 |
---|---|---|---|---|
此為原始的加權連通圖。每條邊一側的數字代表其權值。 | - | - | - | |
頂點D被任意選為起始點。頂點A、B、E和F通過單條邊與D相連。A是距離D最近的頂點,因此將A及對應邊AD以高亮表示。 | C, G | A, B, E, F | D | |
下一個頂點為距離D或A最近的頂點。B距D為9,距A為7,E距D為15,F距D為6。因此,F距D或A最近,因此將頂點F與相應邊DF以高亮表示。 | C, G | B, E, F | A, D | |
算法繼續重複上面的步驟。距離A為7的頂點B被高亮表示。 | C | B, E, G | A, D, F | |
在當前情況下,可以在C、E與G間進行選擇。C距B為8,E距B為7,G距F為11。E最近,因此將頂點E與相應邊BE高亮表示。 | 無 | C, E, G | A, D, F, B | |
這裡,可供選擇的頂點只有C和G。C距E為5,G距E為9,故選取C,並與邊EC一同高亮表示。 | 無 | C, G | A, D, F, B, E | |
頂點G是唯一剩下的頂點,它距F為11,距E為9,E最近,故高亮表示G及相應邊EG。 | 無 | G | A, D, F, B, E, C | |
現在,所有頂點均已被選取,圖中綠色部分即為連通圖的最小生成樹。在此例中,最小生成樹的權值之和為39。 | 無 | 無 | A, D, F, B, E, C, G |
證明
已知圖G的邊數量為numEdge, 頂點數量為numVert, prim生成的樹為T0, 最小生成樹(MST)為Tmin
則有,cost(Tmin)<=cost(T0)
設: T0 的 numVert-1 條邊按照權重由小到大排列依次為:ek1, ek2, ek3, ..., ekn
Tmin 的 numVert-1 條邊按照權重由小到大排列依次為:eg1, eg2, eg3, ..., egn
其中n=numVert-1
兩棵樹的邊從小到大權重比較,設第一個屬於 T0 但不屬於 Tmin 的邊為 ed1, 連接該邊的兩個頂點為 (vs, ve1)
同時存在第一個屬於 Tmin 但不屬於 T0 且以vs為頂點的邊,記為 ed2, 連接該邊的兩個頂點為 (vs, ve2)。
兩個邊的起點相同。由Prim算法性質可知,w(ed2) >= w(ed1)
此時,在 Tmin 中刪除 ed2 ,添加 ed1,邊的數量和頂點數量均不變,且不存在環,因此得到新的生成樹Tnew,且cost(Tmin)>=cost(Tnew)
又因為 Tmin 是MST 所以 cost(Tmin)=cost(Tnew)。
以此類推,cost(Tmin)=cost(T0)
T0是最小生成樹, 得證.
各語言程序代碼
Pascal語言程序
部分主程序段:
procedure prim(v0:integer);
var
lowcost,closest:array[1..maxn] of integer;
i,j,k,min,ans:integer;
for i:=1 to n do
begin
lowcost[i]:=cost[v0,i];
closest[i]:=v0;
end;
for i:=1 to n-1 do
begin
min:=maxint;
for j:=1 to n do
if (lowcost[j]<min) and (lowcost[j]<>0) then
begin
min:=lowcost[j];
k:=j;
end;
inc(ans, lowcost[k]);
lowcost[k]:=0;
for j:=1 to n do
if cost[k,j]<lowcost[j] then
begin
lowcost[j]:=cost[k,j];
closest[j]:=k;
end;
end;
writeln(ans);
end;
C語言代碼
Python語言實現
此份源碼使用了堆優化
from queue import PriorityQueue as priority_queue
from math import inf
class Node:
def __init__(self,id,**kwargs):
self.id = id
self.fst = self.lst = None
def __iter__(self):
return NodeIterator(self)
def __repr__(self):
return "Node(%d)"%self.id
class NodeIterator:
def __init__(self,Node):
self.prst = Node.fst
def __next__(self):
if self.prst == None:
raise StopIteration()
ret = self.prst
self.prst = self.prst.nxt
return ret
class Edge:
def __init__(self,fr,to,**kwargs):
if fr.fst == None:
fr.fst = self
else:
fr.lst.nxt = self
fr.lst = self
self.to = to
self.nxt = None
self.w = 1 if 'w' not in kwargs else kwargs['w']
def __repr__(self):
return "Edge({},{},w = {})",format(self.fr,self.to,self.w)
class Graph:
def __init__(self,V):
self.nodecnt = V
self.nodes = [Node(i) for i in range(V)]
self.edges = []
def add(self,u,v,**kwargs):
self.edges.append(Edge(self.nodes[u],self.nodes[v],**kwargs))
def MST_prim(self,begin):
'''
prim algorithm on a graph(with heap),
returns the weight sum of the tree
or -1 if impossible
'''
q = priority_queue()
vis = [False for _ in range(self.nodecnt)]
q.put((0,begin))
ret = 0
while not q.empty():
prst = q.get()
if vis[prst[1]]:
continue
vis[prst[1]] = True
ret += prst[0]
for i in self.nodes[prst[1]]:
if not vis[i.to.id]:
q.put((i.w,i.to.id))
if all(vis):
return ret
else:
return -1
Java語言實現
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Prim {
public static List<Vertex> vertexList = new ArrayList<Vertex>();//结点集
public static List<Edge> EdgeQueue = new ArrayList<Edge>();//边集
public static List<Vertex> newVertex = new ArrayList<Vertex>();//已经 访问过的结点
public static void main(String[] args) {
primTree();
}
public static void buildGraph() {
Vertex v1 = new Vertex("a");
Prim.vertexList.add(v1);
Vertex v2 = new Vertex("b");
Prim.vertexList.add(v2);
Vertex v3 = new Vertex("c");
Prim.vertexList.add(v3);
Vertex v4 = new Vertex("d");
Prim.vertexList.add(v4);
Vertex v5 = new Vertex("e");
Prim.vertexList.add(v5);
addEdge(v1, v2, 6);
addEdge(v1, v3, 7);
addEdge(v2, v3, 8);
addEdge(v2, v5, 4);
addEdge(v2, v4, 5);
addEdge(v3, v4, 3);
addEdge(v3, v5, 9);
addEdge(v5, v4, 7);
addEdge(v5, v1, 2);
addEdge(v4, v2, 2);
}
public static void addEdge(Vertex a, Vertex b, int w) {
Edge e = new Edge(a, b, w);
Prim.EdgeQueue.add(e);
}
public static void primTree() {
buildGraph();
Vertex start = vertexList.get(0);
newVertex.add(start);
for (int n = 0; n < vertexList.size() - 1; n++) {
Vertex temp = new Vertex(start.key);
Edge tempedge = new Edge(start, start, 1000);
for (Vertex v : newVertex) {
for (Edge e : EdgeQueue) {
if (e.start == v && !containVertex(e.end)) {
if (e.key < tempedge.key) {
temp = e.end;
tempedge = e;
}
}
}
}
newVertex.add(temp);
}
Iterator it = newVertex.iterator();
while (it.hasNext()) {
Vertex v = (Vertex) it.next();
System.out.println(v.key);
}
}
public static boolean containVertex(Vertex vte) {
for (Vertex v : newVertex) {
if (v.key.equals(vte.key))
return true;
}
return false;
}
}
class Vertex {
String key;
Vertex(String key) {
this.key = key;
}
}
class Edge {
Vertex start;
Vertex end;
int key;
Edge(Vertex start, Vertex end, int key) {
this.start = start;
this.end = end;
this.key = key;
}
}
參考
普林演算法與迪科斯徹演算法的策略相似。