1. 程式人生 > >最小生成樹演算法——Kruskal演算法、Prim演算法、堆優化的Prim演算法

最小生成樹演算法——Kruskal演算法、Prim演算法、堆優化的Prim演算法

什麼叫最小生成樹?
已知一個無向連通圖,那麼這個圖的最小生成樹是該圖的一個子圖,且這個子圖是一棵樹且把圖中所有節點連線到一起了。一個圖可能擁有多個生成樹。一個帶權重的無向連通圖的最小生成樹(minimum spanning tree),它的權重和是小於等於其他所有生成樹的權重和的。
生成樹的權重和,是把生成樹的每條邊上的權重加起來的和。

一顆最小生成樹有多少條邊?
已知帶權重無向連通圖有V個節點,那麼圖的最小生成樹的邊則有V-1條邊。

Kruskal演算法

思路

步驟如下:
1.以每條邊的權重排序,排非降序。
2.每次挑選一個權重最小的邊,檢查將其加入到最小生成樹時,是否會形成環(一顆樹是沒有環的啊)
3.重複步驟2直到最小生成樹中有V-1條邊。
在步驟2中,我們使用

Union-Find algorithm來檢測加入邊是否會形成環。

顯而易見,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的根是xv的根是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(V2):,所以O(LogV)和 O(LogE)是大約一樣的。PS:這句話從Kruskal演算法看來的,我也沒看懂,但有了這兩個條件,推導過程如下:
這裡寫圖片描述
所以,時間複雜度為O(ElogE)或者O(ElogV),一般認為是前者。
Prim演算法時間複雜度:為O(V2)。如果輸入的圖,用鄰接表表示,且使用了二叉堆(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替換為堆中最後一個元素lastNodesize大小減小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)。