1. 程式人生 > >Project-2: Prim 和 Kruskal 演算法尋找最小生成樹

Project-2: Prim 和 Kruskal 演算法尋找最小生成樹

Prim 和 Kruskal 演算法尋找最小生成樹

  • 實驗原理

    • 堆是一種經過排序的完全二叉樹,其中任一非終端節點的資料值均不大於(或不小於)其左子節點和右子節點的值。最大堆和最小堆是二叉堆的兩種形式。最大堆:根結點的鍵值是所有堆結點鍵值中最大者。最小堆:根結點的鍵值是所有堆結點鍵值中最小者。本次實驗使用的是最小堆。

    • Python heapq 模組
      heapq 模組提供了相應的方法來對堆進行操作。
      最常用的有如下方法:
      heap = [] #建立了一個空堆
      heappush(heap,item) #往堆中插入一條新的值
      item = heappop(heap)

      #從堆中彈出最小值 item = heap[0] #檢視堆中最小值,不彈出
      heapify(x) #以線性時間將一個列表轉化為堆
      實驗中用到了三個方法:
      heapify(x):用第一個節點的所有鄰接邊建立堆,heappush(heap,item):將新的割邊不斷加入到堆中,heappop(heap):獲取割邊中最小權重的邊;

    • 並查集:union-find
      並查集:一種樹型的資料結構,用於處理一些不相交集合的合併及查詢問題。常常在使用中以森林來表示。
      :就是讓每個元素構成一個單元素的集合,也就是按一定順序將屬於同一組的元素所在的集合合併。

      • 使用並查集的主要步驟:

        (1) 初始化:把每個點所在集合初始化為其自身(根節點為自身), 時間複雜度均為 O(N);
        (2) 查詢:查詢元素所在的集合,即根節點。(遞迴查詢,查詢次數為樹的深度);
        (3) 合併:將兩個元素所在的集合合併為一個集合。
        通常來說,合併之前,先判斷兩個元素是否屬於同一集合,這可用“查詢”操作實現。
      • 實現思路
        (1)初始化:將圖的每個節點初始化為根節點是自身的樹初始高度為 0;
        (2)為了方便找到權重最小的邊,先把所有的邊按權重降序排序;然後獲取最小邊,判斷兩端節點是否屬於一棵樹上,如果不屬於就將他們合併,並把邊加入到最小生成樹上;否則繼續從列表中取邊,一直到最小生成樹裡面的邊數目等於圖的節點數-1 為止;
  • 利用最小堆實現 prim 演算法:
import Graph
from heapq import heapify, heappop, heappush
import Edge


def prim(graph):
    """
    prim演算法實現程式碼
    :param graph: 傳入要操作的圖
    :return: mst 最小生成樹
    """
    mst = Graph.Graph()     # 最小生成樹集合(特殊的圖)
    node_num = graph.get_node_num()    # 圖上的節點數
    nodes_in_mst = [graph.get_node_list()[0]]       # 樹上的節點集
    cut_edges = graph.get_node_edges(str(graph.get_node_list()[0]))       # 割邊集合,初始時是第一個節點的鄰邊
    heapify(cut_edges)                     # 初始化最小堆,將割邊加入到堆
    while cut_edges:
        weight, node_a, node_b = heappop(cut_edges)   # 彈出最小割邊
        if node_b not in nodes_in_mst:                  # 若該點沒有在樹上
            nodes_in_mst.append(node_b)
            mst.add_edge(Edge.Edge(node_a, node_b, weight))    # 把邊加到樹上
            if mst.get_edge_num() == node_num-1:  # 樹上邊數比節點數少1
                break
            for edge in graph.get_node_edges(str(node_b)):  # 遍歷與node_b鄰接的所有邊
                if edge[2] not in nodes_in_mst:             # 如果該條邊沒有在樹上
                    heappush(cut_edges, edge)       # 將新的割邊加入到堆中
    return mst

  • 利用並查集實現 kruskal 演算法
import Graph
import Util
from Edge import Edge
from copy import deepcopy
from operator import itemgetter


def Kruskal(graph, tree_set):
    """
    kruskal演算法實現程式碼
    @:param:graph  圖
    @:param:tree_set   圖節點構成的森林(樹的集合)
    @:return:mst 最小生成樹(特殊的圖)
    """
    edges = sorted(deepcopy(graph.get_all_edges()), key=itemgetter(0),  reverse=True)  # 對所有的邊進行降序排序
    node_num = graph.get_node_num()      # 獲取節點個數來控制迴圈次數
    mst = Graph.Graph()                  # 最小生成樹用圖來表示
    while edges:
        weight, node_a, node_b = edges.pop()
        """
        尋找某條邊上的兩個節點看其是否屬於同一個集合
        """
        root_a = Util.find(tree_set, node_a)
        root_b = Util.find(tree_set, node_b)
        if root_a != root_b:                      # 不屬於同一個集合就將邊加入到最小生成樹上並且合併兩個節點所在的集合
            mst.add_edge(Edge(node_a, node_b, weight))
            Util.union(tree_set, root_a, root_b)     # 合併root_a 和root_b
            if mst.get_edge_num() == node_num-1:  # 最小生成樹的邊數與原圖節點數-1相等就退出迴圈
                break
    return mst

  • 測試資料
1 2 9
1 4 1
1 5 8
2 1 9
2 3 1
2 4 2
3 2 1
3 5 1
4 1 9
4 5 10
4 2 2
5 1 8
5 3 1
5 4 10
  • 執行結果

在這裡插入圖片描述

  • 微信公眾號
    在這裡插入圖片描述