最小生成樹演算法——Kruskal演算法、Prim演算法、堆優化的Prim演算法
什麼叫最小生成樹?
已知一個無向連通圖,那麼這個圖的最小生成樹是該圖的一個子圖,且這個子圖是一棵樹且把圖中所有節點連線到一起了。一個圖可能擁有多個生成樹。一個帶權重的無向連通圖的最小生成樹(minimum spanning tree),它的權重和是小於等於其他所有生成樹的權重和的。
生成樹的權重和,是把生成樹的每條邊上的權重加起來的和。
一顆最小生成樹有多少條邊?
已知帶權重無向連通圖有V個節點,那麼圖的最小生成樹的邊則有V-1條邊。
Kruskal演算法
思路
步驟如下:
1.以每條邊的權重排序,排非降序。
2.每次挑選一個權重最小的邊,檢查將其加入到最小生成樹時,是否會形成環(一顆樹是沒有環的啊)
3.重複步驟2直到最小生成樹中有V-1條邊。
在步驟2中,我們使用
顯而易見,Kruskal演算法是一種貪心演算法,以下面例子開始實際講解。
此圖包含9個節點和14條邊,所以該圖的最小生成樹會有(9-1)=8條邊。該示例的處理步驟如下:
1. 挑選邊7-6,此時沒有環形成,則加入這條邊。
2. 挑選邊8-2,此時沒有環形成,則加入這條邊。
3. 挑選邊6-5,此時沒有環形成,則加入這條邊。
4. 挑選邊0-1,此時沒有環形成,則加入這條邊。
5. 挑選邊2-5,此時沒有環形成,則加入這條邊。
6. 挑選邊8-6,此時有環形成,則不加入這條邊。
7. 挑選邊2-3,此時沒有環形成,則加入這條邊。
8. 挑選邊7-8,此時有環形成,則不加入這條邊。
9. 挑選邊0-7,此時沒有環形成,則加入這條邊。
10. 挑選邊1-2,此時有環形成,則不加入這條邊。
11. 挑選邊3-4,此時沒有環形成,則加入這條邊。
執行到這裡,已經包含了8條邊,所以演算法終止。
程式碼
from collections import defaultdict
class Graph:
def __init__(self,vertices):
self.V= vertices #頂點的數量
self.graph = [] # 二維list用來存邊的起點、終點、權重
# 新增每條邊
def addEdge(self,u,v,w):
self.graph.append([u,v,w])
# 遞迴找到每個節點所在子樹的根節點
def find(self, parent, i):
if parent[i] == i:
return i
return self.find(parent, parent[i])
# 聯合兩顆子樹為一顆子樹,誰附在誰身上的依據是rank
def union(self, parent, rank, x, y):
xroot = self.find(parent, x)
yroot = self.find(parent, y)
#進行路徑壓縮
if(xroot != parent[x]):
parent[x] = xroot
if(yroot != parent[y]):
parent[y] = yroot
# 將較小rank的子樹附在較大rank的子樹上去
if rank[xroot] < rank[yroot]:
parent[xroot] = yroot
elif rank[xroot] > rank[yroot]:
parent[yroot] = xroot
# 若rank一樣,程式這裡寫死即可
else :
parent[yroot] = xroot
rank[xroot] += 1
# 主函式用來構造最小生成樹
def KruskalMST(self):
result =[] #存MST的每條邊
i = 0 # 用來遍歷原圖中的每條邊,但一般情況都遍歷不完
e = 0 # 用來判斷當前最小生成樹的邊數是否已經等於V-1
#按照權重對每條邊進行排序,如果不能改變給的圖,那麼就建立一個副本,內建函式sorted返回的是一個副本
self.graph = sorted(self.graph,key=lambda item: item[2])
parent = [] ; rank = []
# 建立V個子樹,都只包含一個節點
for node in range(self.V):
parent.append(node)
rank.append(0)
# MST的最終邊數將為V-1
while e < self.V -1 :
# 選擇權值最小的邊,這裡已經排好序
u,v,w = self.graph[i]
i = i + 1
x = self.find(parent, u)
y = self.find(parent ,v)
# 如果沒形成邊,則記錄下來這條邊
if x != y:
#不等於才代表沒有環
e = e + 1
result.append([u,v,w])
self.union(parent, rank, x, y)
# 否則就拋棄這條邊
print ("Following are the edges in the constructed MST")
for u,v,weight in result:
print ("%d -- %d == %d" % (u,v,weight))
g = Graph(4)
g.addEdge(0, 1, 10)
g.addEdge(0, 2, 6)
g.addEdge(0, 3, 5)
g.addEdge(1, 3, 15)
g.addEdge(2, 3, 4)
#雖然是無向圖,但每條邊只存一次
g.KruskalMST()
加了一些必要的中文註釋,關於檢測環的思路具體請參考Union-Find algorithm,不過值得注意的是,原始碼並沒有路徑壓縮(來自Union-Find)的操作,是我自己加上的。
程式碼優化
發現一處可以優化的地方,就是在union
函式中會重複尋找兩個節點所在子樹的根節點,而這兩個根節點已經在主函式KruskalMST
中的while迴圈中就被找到了(u
的根是x
,v
的根是y
),所以,在union
函式中應該有四個引數,分別是兩個點以及對應的兩個根節點。
修改後可以避免每次迴圈中,兩次union
函式的重複呼叫。
修改如下:
KruskalMST
中的while迴圈中的這句self.union(parent, rank, x, y)
修改成self.union(parent, rank, u, v, x, y)
。
union
函式修改為:
def union(self, parent, rank, x, y, xroot, yroot):
#xroot = self.find(parent, x)不需要重複此函式
#yroot = self.find(parent, y)不需要重複此函式
#進行路徑壓縮
if(xroot != parent[x]):
parent[x] = xroot
if(yroot != parent[y]):
parent[y] = yroot
# Attach smaller rank tree under root of high rank tree (Union by Rank)
if rank[xroot] < rank[yroot]:
parent[xroot] = yroot
elif rank[xroot] > rank[yroot]:
parent[yroot] = xroot
# If ranks are same, then make one as root and increment its rank by one
else :
parent[yroot] = xroot
rank[xroot] += 1
Prim演算法
思路
prim演算法也是一種貪心演算法。開始時,最小生成樹(MST)為空(不包含任何節點),然後維持兩個集合,一個集合是包含已經進入MST中的節點,另一個集合時包含未被加入到MST中的節點。在演算法的每個步驟中,從連線這兩個集合的所有邊中,挑選一個最小權值的邊。在挑選之後,要把這條邊上不在MST中的另一個節點加入到MST中去。
注意,連線這兩個集合的所有邊,肯定是一個節點在MST集合中,另一個節點在非MST集合中。
Prim演算法的思想:首先要考慮連通性,最小生成樹中的每條邊,在原圖中肯定也是已存在的邊。然後通過連線MST集合和非MST集合中的節點來生成最小生成樹。在連線的過程中,必須挑選最小權值的邊來連線。
Prim演算法的步驟:(節點個數為V)
1.建立一個集合mstSet用來判斷是否所有節點都已經被包括到MST中,當集合大小為V時,則MST包括了所有節點。
2.根據原圖邊的權重為每個節點分配一個key值,初始時每個節點的key值都是無窮大,當0節點作為第一個被挑選的節點時,將0節點的key值賦值為0。
3.只要當mstSet還沒有包含所有節點時,就重複以下步驟:
…(i)選擇一個節點u,u是非MST集合中key值最小的那個節點。
…(ii)把u加入到mstSet。
…(iii)更新u節點的鄰接點v的key值。遍歷u的鄰接點v,每當邊u-v的權值小於v的key值時,就更新v的key值為邊u-v的權值,同時記錄下v的父節點為u。
實際例子處理步驟:
已知帶權值的無向連通圖如下:
初始時,mstSet為空,每個節點的key值為{0, INF, INF, INF, INF, INF, INF, INF},INF代表無窮大。現在挑選具有最小key值的節點,所以0節點被挑選,將其加入到mstSet中。所以mstSet現在為{0}。
在加入節點到mstSet中後,更新鄰接點的key值,0的鄰接點為1和7,然後1和7的key值分別被更新為4和8。
圖中只顯示出key值為有限值的節點。綠色節點代表在mstSet集合中的節點。
挑選不在mstSet集合中的key值最小的那個節點。1被挑選且加入到mstSet中去,mstSet變成{0, 1}。更新1的鄰接點的key值,所以2的key值變成8.
挑選不在mstSet集合中的key值最小的那個節點。看上圖發現,我們既可以挑選2節點,也可以挑選7節點,此時挑選7節點。mstSet變成{0, 1, 7}。更新7的鄰接點的key值,所以6和8的key值分別變成7和1。
挑選不在mstSet集合中的key值最小的那個節點。6被挑選且加入到mstSet中去,mstSet變成{0, 1, 7, 6}。更新6的鄰接點的key值,所以5和8的key值分別變成2和6。
重複以上步驟,直到mstSet包含了所有的節點。
程式碼
class Graph():
def __init__(self, vertices):
self.V = vertices
self.graph = [[0 for column in range(vertices)]
for row in range(vertices)]
# 使用parent[]來列印MST
def printMST(self, parent):
print ("Edge \tWeight")
#列印每個點與父節點的邊就可以了,注意根節點不用列印
for i in range(1,self.V):
print (parent[i],"-",i,"\t",self.graph[i][ parent[i] ])
# 在非MST集合中選擇具有最小key值的節點
def minKey(self, key, mstSet):
min = float("inf")
for v in range(self.V):
if key[v] < min and mstSet[v] == False:
#False代表是非MST集合中的節點,然後就是普通的尋找最小的操作
min = key[v]
min_index = v
return min_index
# 主函式,用來構造MST
def primMST(self):
#每個節點分配一個key值,在cut圖中用來挑選最小權值的那條邊,初始為無窮大
key = [float("inf")] * self.V
parent = [None] * self.V # 陣列用來記錄每個節點的父節點
key[0] = 0 # 挑選0節點作為選擇的起點
mstSet = [False] * self.V#記錄每個節點是否被加入到了MST集合中
parent[0] = -1 # 第一個節點作為樹的根節點
for cout in range(self.V):
# 從非MST集合中挑選最小距離的節點
u = self.minKey(key, mstSet)
# 把挑選到的節點放到MST集合中去
mstSet[u] = True
# 更新被挑選節點的鄰接點的距離,只有當新距離比當前距離要小,且這個鄰接點在非MST集合中
for v in range(self.V):
# self.graph[u][v] > 0代表v是u的鄰接點
# mstSet[v] == False代表當前節點還沒有加入到MST中
# key[v] > self.graph[u][v]只有新距離比當前記錄距離要小時更新
# 兩種情況,一是key[v]為無窮,肯定更新;二是key[v]為常數,但新距離更小
if (self.graph[u][v] > 0) and (mstSet[v] == False) and (key[v] > self.graph[u][v]):
key[v] = self.graph[u][v]#更新距離
parent[v] = u#更新父節點
self.printMST(parent)
g = Graph(5)
g.graph = [ [0, 2, 0, 6, 0],
[2, 0, 3, 8, 5],
[0, 3, 0, 0, 7],
[6, 8, 0, 0, 9],
[0, 5, 7, 9, 0],
]
#圖的表示用鄰接矩陣,根據矩陣元素的索引判斷是哪條邊
#根據矩陣元素判斷該邊是否有邊,不為0代表有邊
g.primMST();
注意,對節點u
的鄰接點v
進行key值的減小,這裡一般稱為對v
進行鬆弛操作,鬆弛操作類似最短路徑演算法的鬆弛操作。
思考與總結
Kruskal演算法因為是從所有邊中挑選權值最小的邊,所以在每次加入邊的時候,需要判斷是否形成環。
Prim演算法相比Kruskal演算法的優勢在於,Prim演算法考慮邊的連通性(Kruskal從所有邊中挑選,而Prim從連通的邊中挑選)。
而且Prim演算法不用考慮在加入節點的過程中,是否會形成環。形成環的條件是圖中有回邊(back edge),但每次挑選節點加入mstSet
中時,都是從非MST集合中挑選,所以不可能讓更新後的最小生成樹形成回邊。
需要排序的次數不同:Kruskal演算法是在演算法開始前對所有邊的權值進行排序,但就這一次排序。Prim演算法是每次挑選節點時,都需要進行排序,但每次排序都是一部分邊就行。明顯可見,Prim演算法排序的總次數,肯定比Kruskal演算法排序的總次數要多。
Kruskal演算法時間複雜度:為O(ElogE)或者O(ElogV)。排序所有邊需要O(ELogE)的時間,排序後,還需要對所有邊應用Union-find演算法,Union-find操作最多需要O(LogV)時間,再乘以邊數E,所以就是O(ElogV)。
所以總共需要O(ELogE + ELogV) 時間。由於一般圖中,邊數E是大約為O():,所以O(LogV)和 O(LogE)是大約一樣的。PS:這句話從Kruskal演算法看來的,我也沒看懂,但有了這兩個條件,推導過程如下:
所以,時間複雜度為O(ElogE)或者O(ElogV),一般認為是前者。
Prim演算法時間複雜度:為O()。如果輸入的圖,用鄰接表表示,且使用了二叉堆(binary heap)的資料結構,複雜度能降到O(E log V)或O(V log V)。
適用範圍:Prim一般用於稠密圖,因為其複雜度為O(V log V),則主要取決於點數,而與邊數無關。Kruskal一般用於稀疏圖,因為其複雜度為O(ElogE),則主要取決於邊數,而與點數無關。
堆優化的Prim演算法
思路
上面提到,如果使用二叉堆,prim演算法的複雜度能降到O(E log V),接下來本文將講解使用堆優化的Prim演算法。
之前實現的這個Prim演算法,是用鄰接矩陣表示圖。而堆優化的Prim演算法,將用鄰接表來表示圖,且使用最小堆來尋找,連線MST集合和非MST集合的邊中,最小權值的那條邊。
基本思想:基本思想和原Prim演算法大體相同,但此演算法是,根據鄰接表,通過廣度優先遍歷(BFS)來遍歷所有節點,遍歷的總操作為O(V+E)次數。同時使用最小堆儲存非MST集合中的節點,每次遍歷時用最小堆來選擇節點。最小堆操作的時間複雜度為O(LogV)。
基本步驟:
1.建立一個大小為V的最小堆,V是圖的節點個數。最小堆的每個元素,儲存的是節點id和節點的key值。
2.初始化時,讓堆的第一個元素作為最小生成樹的根節點,賦值根節點的key值為0。其餘節點的key值賦值為無窮大。
3.只要最小堆不為空,就重複以下步驟:
…(i)從最小堆中,抽取最小key值的節點,作為u。
…(ii)對於u的每個鄰接點v,檢查v是否在最小堆中(即還沒有加入到MST中)。如果v在最小堆中,且v的key值是大於邊u-v的權值時,就更新v的key值為邊u-v的權值。
實際例子處理步驟:
與上面Prim演算法的一樣。
程式碼
程式碼來自Prim’s MST using min heap,稍微修改,因為原本是python2的程式碼,且加上了中文註釋方便讀者理解。
已知帶權值的無向連通圖如下:
from collections import defaultdict
class Heap():
def __init__(self):
self.array = []#用陣列形式儲存堆的樹結構
self.size = 0#堆內節點個數
self.pos = []#判斷節點是否在堆中
def newMinHeapNode(self, v, dist):
minHeapNode = [v, dist]
return minHeapNode
# 交換堆中兩個節點
def swapMinHeapNode(self, a, b):
t = self.array[a]
self.array[a] = self.array[b]
self.array[b] = t
def minHeapify(self, idx):#遞迴,下濾根節點
#符合完全二叉樹中,索引規律
smallest = idx
left = 2 * idx + 1
right = 2 * idx + 2
print(self.array,self.size)
print(self.pos)
if left < self.size and self.array[left][1] < \
self.array[smallest][1]:
smallest = left
if right < self.size and self.array[right][1] < \
self.array[smallest][1]:
smallest = right
#最終smallest為三個點中最小的那個的索引,非左即右
# smallest將與左或右節點交換
if smallest != idx:
# Swap positions
self.pos[ self.array[smallest][0] ] = idx
self.pos[ self.array[idx][0] ] = smallest
# Swap nodes
self.swapMinHeapNode(smallest, idx)
self.minHeapify(smallest)
# 抽取堆中最小節點
def extractMin(self):
if self.isEmpty() == True:
return
# 找到根節點
root = self.array[0]
# 把最後一個節點放在根節點上去
lastNode = self.array[self.size - 1]
self.array[0] = lastNode
print()
print('當前堆最後面位置的元素為',lastNode)
# 更新根節點和最後一個節點的pos
self.pos[lastNode[0]] = 0
self.pos[root[0]] = self.size - 1#此時堆大小已經減小1
# 減小size,從根節點開始從新構造
self.size -= 1
self.minHeapify(0)
return root#返回的是被替換掉的那個
def isEmpty(self):
return True if self.size == 0 else False
def decreaseKey(self, v, dist):#上濾節點
# 獲得v在堆中的位置
i = self.pos[v]
# 更新堆中v的距離為dist,雖說是更新,但肯定是減小key
self.array[i][1] = dist
# 一直尋找i的父節點,檢查父節點是否更大
while i > 0 and self.array[i][1] < self.array[int((i - 1) / 2)][1]:
# pos陣列交換,array也得交換
self.pos[ self.array[i][0] ] = int((i-1)/2)
self.pos[ self.array[int((i-1)/2)][0] ] = i
self.swapMinHeapNode(i, int((i - 1)/2) )
# i賦值為父節點索引
i = int((i - 1) / 2)
# 檢查v是否在堆中,很巧妙的是,由於size一直在減小
# 當pos小於size說明該點在堆中不可能的位置,即不在堆中
def isInMinHeap(self, v):
if self.pos[v] < self.size:
return True
return False
def printArr(parent, n):
for i in range(1, n):
print ("% d - % d" % (parent[i], i))
class Graph():
def __init__(self, V):
self.V = V
self.graph = defaultdict(list)
# 新增無向圖的每條邊
def addEdge(self, src, dest, weight):
# 當前邊從src到dest,權值為weight
# 新增到src的鄰接表中,新增元素為[dest, weight]
# 注意都是新增到0索引位置
newNode = [dest, weight]
self.graph[src].insert(0, newNode)
# 因為是無向圖,所以反向邊也得新增
newNode = [src, weight]
self.graph[dest].insert(0, newNode)
# 主函式用來構造最小生死樹(MST)
def PrimMST(self):
# V是節點的個數
V = self.V
# 存每個節點的key值
key = []
# 記錄構造的MST
parent = []
# 建立最小堆
minHeap = Heap()
# 初始化以上三個資料結構
for v in range(V):
parent.append(-1)#初始時,每個節點的父節點是-1
key.append(float('inf'))#初始時,每個節點的key值都是無窮大
minHeap.array.append( minHeap.newMinHeapNode(v, key[v]) )
#newMinHeapNode方法返回一個list,包括節點id、節點key值
#minHeap.array成員儲存每個list,所以是二維list
#所以初始時堆裡的每個節點的key值都是無窮大
minHeap.pos.append(v)
#pos成員新增每個節點id
#minHeap.pos初始時是0-8,都小於9即節點數
minHeap.pos[0] = 0#不懂這句,本來pos的0索引元素就是0啊
key[0] = 0#讓0節點作為第一個被挑選的節點
minHeap.decreaseKey(0, key[0])
#把堆中0位置的key值變成key[0],函式內部重構堆
# 初始化堆的大小為V即節點個數
minHeap.size = V
print('初始時array為',minHeap.array)
print('初始時pos為',minHeap.pos)
print('初始時size為',minHeap.size)
# 最小堆包含所有非MST集合中的節點
# 所以當最小堆為空,迴圈終止
while minHeap.isEmpty() == False:
# 抽取最小堆中key值最小的節點
newHeapNode = minHeap.extractMin()
print('抽取了最小元素為',newHeapNode)
u = newHeapNode[0]
# 遍歷所有的鄰接點然後更新它們的key值
for pCrawl in self.graph[u]:
v = pCrawl[0]
# 如果v在當前最小堆中,且新的key值比當前key值更小,就更新
if minHeap.isInMinHeap(v) and pCrawl[1] < key[v]:
key[v] = pCrawl[1]
parent[v] = u
# 也更新最小堆中節點的key值,重構
minHeap.decreaseKey(v, key[v])
printArr(parent, V)
graph = Graph(9)
graph.addEdge(0, 1, 4)
graph.addEdge(0, 7, 8)
graph.addEdge(1, 2, 8)
graph.addEdge(1, 7, 11)
graph.addEdge(2, 3, 7)
graph.addEdge(2, 8, 2)
graph.addEdge(2, 5, 4)
graph.addEdge(3, 4, 9)
graph.addEdge(3, 5, 14)
graph.addEdge(4, 5, 10)
graph.addEdge(5, 6, 2)
graph.addEdge(6, 7, 1)
graph.addEdge(6, 8, 6)
graph.addEdge(7, 8, 7)
graph.PrimMST()
分析程式碼:
由於堆的本質是一顆完全二叉樹,而完全二叉樹的父節點的索引與其左右孩子的索引之間,具有良好運算關係,如下圖所示:
若某節點索引為x,那麼其左孩子索引為2*x+1,其右孩子索引為2*x+2。觀察上圖發現此運算關係成立,所以上圖所示完全二叉樹則可以一個數組[0,1,2,3,4,5,6]來表示,而不需要真的建立一種樹的資料結構。
首先看程式中的資料結構:
..0)若節點個數為V
,則這V個節點id為[0,1,2…V-1]。分別稱為節點0、節點1、節點2、… 、節點V-1.
..1)在PrimMST
函式中的key
陣列:與原Prim演算法中的一樣,用來記錄每個節點的key值。
..2)在PrimMST
函式中的parent
陣列:與原Prim演算法中的一樣,用來記錄最小生成樹。
..3)在minHeap
物件中的size
(整數型):代表當前堆的大小,初始時size為V。以當前例子分析,初始時,size為9(原圖一共有9個點),每當將一個點加入MST中後,size就會減小1。
..4)在minHeap
物件中的pos
陣列:position,節點在最小堆中位置,大小為V。其索引i
代表的是節點i,而pos[i]
代表的是節點i在堆中的位置。比如pos[0]=3,則代表節點0處在堆中索引為3的位置上。且pos[i]需要和size配合使用:當pos[i]<size
時,代表節點i在堆的pos[i]
的索引的位置上;當pos[i]>=size
時,代表節點i已經不在堆中了,而是已經加入到了最小生成樹中了。以當前例子分析,初始時,pos為[0,1,2…8]。
..5)在minHeap
物件中的array
陣列:用這個陣列來表示堆,由於之前提到的完全二叉樹的良好性質,大小為V。其索引i
代表的是堆中的各個位置,而array[i]
代表的是,在堆的i索引位置存的節點以及該節點的key值。且array[i]需要和size配合使用:對於小於size的i,這些array[i]都是堆中的元素;對於大於等於size的i,這些array[i]都不能看因為沒有意義(注意它們不是加入到MST中的元素,因為程式是這麼設計)。
初始時,array
陣列是符合最小堆定義的,因為堆的0位置元素的key值為0,其餘位置元素的key值為INF。而且值得注意的是,在程式執行過程中,一般都是這個堆除了前幾個元素的key值都為有限數字以外,其餘元素的key值都為INF,這是因為,連線MST集合與非MST集合的邊是有限的,屬於非MST集合的節點組成了堆,但只有這些邊在非MST集合那頭的節點的key才會是有限數字。
再看程式中函式的功能:
..1)PrimMST
函式:主函式,用來構造最小生成樹,生成必須的資料結構,並對其進行初始化。其中的while
迴圈是其主要功能,每次迴圈中,抽取最小堆的根節點(即最小的key值的節點,此時最小堆已構建好)作為u
,遍歷u
的鄰接表即遍歷u
的每個鄰接點v
,判斷是否需要更新v
的key值,是則更新key值,即對v
進行鬆弛操作。
..2)extractMin
函式:抽取最小堆的根節點root
作為返回值,然後將root
替換為堆中最後一個元素lastNode
,size
大小減小1,在函式的最後執行minHeapify
函式,最後return。
..3)minHeapify
函式:在替換root
替換為堆中最後一個元素lastNode
後,需要對堆重新構造,使其保持最小堆性質。 此函式是一個遞迴函式,功能為將根節點在堆中下濾,遞迴到不能下濾為止。
..4)decreaseKey
函式:用於更新u
的鄰接點v
的key值,而且顧名思義,肯定是減小key值。此函式功能為,將某更新過key值的節點在堆中上濾,此函式不用設計成遞迴的原因是,上濾最多能到達根節點,而下濾是沒有一個明確的終點的。
minHeapify
函式和decreaseKey
函式都是為了,在改變堆後,使得堆保持最小堆的性質。
時間複雜度分析:
觀察PrimMST
函式的內部迴圈,因為在遍歷節點的過程類似BFS,所以是被執行O(V+E)次數。在內部迴圈中,decreaseKey
函式被執行,其執行時間為O(LogV)。所以總時間為O(E+V)*O(LogV)=O(ELogV) ,因為一般連通圖中V = O(E)。