最小產生樹的概念
目的: 對於n個節點以及m條帶有權重邊的連通圖, 切掉若干條邊,使得剩餘的邊的權重和最小並且該圖是一顆樹(即含有n個節點以及n-1條邊的連通子圖) 為什麼需要最小產生樹
如果把邊的權重看成是串連兩個節點所需要的cost,那麼最小產生樹就可以使得原圖的各個節點仍舊保持互連的狀態,並且cost最小。其本質是一個最佳化的問題。 Kruskal演算法的基本思想
核心本質是逆向思維。你不是讓我去掉某些邊嗎。那麼我索性把所有邊都去掉,然後從權重小的邊開始一條一條加回來。每加回一條邊,就會使兩個節點連通,如果這兩個節點本身就是連通的,那麼這一條邊就不要加回來,如果這兩個節點本身是不連通的,那麼這一條邊就需要加回來。最後加回來的邊數到達節點數減1的時候,就是一顆最小產生數了。
虛擬碼如下:
node_list = [a,b,c,d] // 所有節點的數組tree_list = [{a}, {b}, {c}, {d}] // 所有獨立的樹的數組,由於一開始去掉了所有的邊,所以a,b,c,d四個節點都變成孤立的單節點樹,最終目的是通過加邊,使得tree_list逐漸層為[{a, b}, {c}, {d}] => [{a, b}, {c, d}] => [{a, b, c, d}]。edge_list = [e1, e2, e3, e4, e5] // 所有邊的容器selected_edge_list = [] // 挑選的邊的結果數組,一開始為空白while length(selected_edge_list) < length(node_list) - 1: // 最終需要挑選出n-1條邊,因為一棵樹是n-1條邊 // 每次從邊的容器中pop出一個最小的權重的邊 min_edge = get_and_pop_min_edge(edge_list) // 檢查min_edge的兩端節點在tree_list中是否處於兩顆樹中,如果是的話,就可以連通這兩顆樹 if can_merge_tree(tree_list, min_edge) is True: tree_list = merge_tree(tree_list, min_edge) selected_edge_list.append(min_edge)return selected_edge_listPrim演算法的基本思想
Prim的演算法的本質是加點法。先隨機挑選一個點作為最小產生樹A中的起始點,其他點作為候選點。每次從候選點集合裡面選擇一個到達樹A的距離最短的點並把這個點和這個點到樹A的一邊加入到最小產生樹A中去(即每次加一點一邊)。直到最小產生樹A包含了所有的點。
這個演算法比較容易理解,虛擬碼略。 注意
很明顯,Kruskal和Prim演算法都是通過貪心的尋找局部最優來解題。但是你要記住局部最優的方案並不一定是全域最優的。當然在最小產生樹的問題上,Kruskal和Prim演算法都已經被證明了這樣的演算法是可以全域最優的。 時間複雜度
Prim演算法的時間複雜度為O(n2),與網中的邊數無關,適合於稠密圖(即邊多點少);而Kruskal的演算法複雜度為O(elog e),與網中的邊數有關,適合於稀疏圖(即點多邊少)。