普林姆演算法

普里姆演算法（Prim's algorithm）是圖論中的一種貪心演算法，可在一個加權連通圖中找到其最小生成樹。意即由此演算法搜尋到的邊子集所構成的樹中，不但包括了連通圖裡的所有頂點，且其所有邊的權值之和亦為最小。該演算法於 1930 年由捷克數學家發現；並在 1957 年由美國計算機科學家羅伯特・C・普里姆獨立發現；1959 年，艾茲格・迪科斯徹再次發現了該演算法。因此，在某些場合，普里姆演算法又被稱為 DJP 演算法、亞爾尼克演算法或普里姆－亞爾尼克演算法。

描述

從單一頂點開始，普里姆演算法按照以下步驟逐步擴大樹中所含頂點的數目，直到遍及連通圖的所有頂點。

輸入：一個加權連通圖，其中頂點集合為，邊集合為；
初始化：，其中為集合中的任一節點（起始點），；
重複下列操作，直到：
# 在集合中選取權值最小的邊，其中為集合中的元素，而則是中沒有加入的頂點（如果存在有多條滿足前述條件即具有相同權值的邊，則可任意選取其中之一）；
# 將加入集合中，將加入集合中；
輸出：使用集合和來描述所得到的最小生成樹。

時間複雜度

透過鄰接矩陣圖表示的簡易實現中，找到所有最小權邊共需的執行時間。使用簡單的二叉堆與鄰接表來表示的話，普里姆演算法的執行時間則可縮減為，其中為連通圖的邊集大小，為點集大小。如果使用較為複雜的斐波那契堆，則可將執行時間進一步縮短為，這在連通圖足夠密集時（當滿足條件時），可較顯著地提高執行速度。

例示

證明

已知圖 G 的邊數量為 numEdge, 頂點數量為 numVert, prim 生成的樹為 T0, 最小生成樹（MST）為 Tmin

則有，cost (Tmin) 0)

設：

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 是最小生成樹， 得證.

各語言程式程式碼

5.1 Pascal 語言程式

部分主程式段：

procedure prim (v0:integer);

var

lowcost,closest:array1..maxn of integer;

i,j,k,min,ans:integer;

for i:=1 to n do

begin

lowcosti:=costv0,i;

closesti:=v0;

end;

for i:=1 to n-1 do

begin

min:=maxint;

for j:=1 to n do

if (lowcostj0) then

begin

min:=lowcostj;

k:=j;

end;

inc (ans, lowcostk);

lowcostk:=0;

for j:=1 to n do

if costk,j

5.2 C 語言程式碼

// 來源：嚴蔚敏 吳偉民《資料結構 (C 語言版)》

void MiniSpanTree_PRIM (MGraph G, VertexType u) {

/* 用普利姆演算法從第 u 個頂點出發構造網 G 的最小生成樹 T, 輸出 T 的各條邊。

記錄從頂點集 U 到 V-U 的代價最小的邊的輔助陣列定義：

struct

{

VertexType adjvex;

VRtype lowcost;

} closedgeMAXVERTEXNUM;

/

k = LocateVex (G, u);

for (j = 0 ; j 0,Vi∈V-U}

printf (closedgek.adjvex, G.vexsk); // 輸出生成樹的邊

closedgek.lowcost = 0; // 第 k 條邊併入 U 集

for (j = 0; j

// 來源： 浙大 - 陳越 《資料結構》

define ERROR -1

Vertex FindMinDist (MGraph Graph, WeightType dist)

{

/ 返回未被收錄頂點中 dist 最小者 /

Vertex MinV, V;

WeightType MinDist = INFINITY;

for (V=0; VNv; V++) {if ( distV!=0 && distVNv; V++) {/ 這裡假設若 V 到 W 沒有直接的邊，則 Graph->GVW 定義為 INFINITY /

distV = Graph->G0V;

parentV = 0; / 暫且定義所有頂點的父結點都是初始點 0 /

}

TotalWeight = 0; /* .

.......... 初始化權重和 */

VCount = 0; / 初始化收錄的頂點數 /

/ 建立包含所有頂點但沒有邊的圖。注意用鄰接表版本 /

MST = CreateGraph (Graph->Nv);

E = (Edge) malloc (sizeof (struct ENode) ); / 建立空的邊結點 /

/ 將初始點 0 收錄進 MST /

dist0 = 0;

VCount ++;

parent0 = -1; / 當前樹根是 0 /

while (1) {V = FindMinDist ( Graph, dist);

/ V = 未被收錄頂點中 dist 最小者 /

if (V==ERROR) / 若這樣的 V 不存在 /

break; / 演算法結束 /

/ 將 V 及相應的邊收錄進 MST /

E->V1 = parentV;

E->V2 = V;

E->Weight = distV;

InsertEdge (MST, E);

TotalWeight += distV;

distV = 0;

VCount++;

for (W=0; WNv; W++) / 對圖中的每個頂點 W /

if (distW!=0 && Graph->GVWGVW GVW; / 更新 distW /

parentW = V; / 更新樹 /

}

}

} / while 結束 /

if (VCount Nv) / MST 中收的頂點不到 | V | 個 /

TotalWeight = ERROR;

return TotalWeight; / 演算法執行完畢，返回最小權重和或錯誤標記 /

}

5.3 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.nodesu,self.nodesv,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 visprst1:

continue

visprst1 = True

ret += prst0

for i in self.nodesprst1:

if not visi.to.id:

q.put ((i.w,i.to.id))

if all (vis):

return ret

else:

return -1

5.4 Java 語言實現

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

public class Prim {public static List vertexList = new ArrayList ;// 結點集

public static List EdgeQueue = new ArrayList ;// 邊集

public static List newVertex = new ArrayList ;// 已經 訪問過的結點

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