[Python] プリム法

プリム法を用いて、重み付き無向グラフの最小全域木を求めます。

プリム法はダイクストラ法とほぼ同じで、コードもダイクストラ法のものをほぼ流用しています。

Python で、ダイクストラ法を使って、重み付きの有向グラフの単一始点最短経路問題を解きます。 以下の続きです。 ダイクストラ法 ダイクストラ法(だいくすとらほう、英: Dijkstra's algorithm)はグラフ理論における辺の重みが...

全域木

全域木とは、グラフの中の全ての頂点を使って作られる木のことです。

全域木(ぜんいきぎ、: Spanning tree)、極大木(きょくだいき)、スパニング木スパニングツリーとは、グラフ理論において、以下のように定義されるのことである。
グラフ G(V,E) において T ⊆ E となる辺集合 T があるとき、グラフ S(V,T) が木(閉路を持たないグラフ)であるなら、 S(V,T) のことをグラフ G(V,E) の全域木であるとする。
つまり、あるグラフの全ての頂点とそのグラフを構成する辺の一部分のみで構成される木のことである。

出典: フリー百科事典『ウィキペディア(Wikipedia)』

最小全域木問題とは、グラフの各辺が重みをもつ場合、全ての辺の和が最小になる全域木を探し出す問題です。

minimum spanning tree (MST) or minimum weight spanning tree is a subset of the edges of a connected, edge-weighted undirected graph that connects all the vertices together, without any cycles and with the minimum possible total edge weight. That is, it is a spanning tree whose sum of edge weights is as small as possible. More generally, any edge-weighted undirected graph (not necessarily connected) has a minimum spanning forest, which is a union of the minimum spanning trees for its connected components.

From Wikipedia, the free encyclopedia

プリム法

プリム法とは、グラフ理論で重み付き連結グラフの最小全域木を求める最適化問題のアルゴリズムである。全域木(対象となるグラフの全頂点を含む辺の部分集合で構成される)のうち、その辺群の重みの総和が最小となる木を求めるものである。

出典: フリー百科事典『ウィキペディア(Wikipedia)』

以下の Wikipedia に載っているグラフの探索を行います。

探索の結果、以下の緑線の最小全域木が求まり、重さの総和は39になります。

import heapq
import sys

class Vertex(object):
    def __init__(self, id):
        self.id = id
        self.adjacent = {}
        self.distance = sys.maxsize
        self.visited = False
        self.previous = None
    
    def __lt__(self, other):
        return self.distance < other.distance

    def add_neighbor(self, neighbor, weight=0):
        self.adjacent[neighbor] = weight
    
    def get_neighbors(self):
        return self.adjacent

    def get_connections(self):
        return self.adjacent.keys()  

    def get_vertex_id(self):
        return self.id

    def get_weight(self, neighbor):
        return self.adjacent[neighbor]
    
    def set_distance(self, distance):
        self.distance = distance

    def get_distance(self):
        return self.distance

    def set_previous(self, previous_vertex):
        self.previous = previous_vertex 
    
    def get_previous(self):
        return self.previous
    
    def set_visited(self):
        self.visited= True


class Graph(object):
    def __init__(self):
        self.vertex_dict = {}
        self.num_vertex = 0
    
    def __iter__(self):
        return iter(self.vertex_dict.values())

    def __len__(self):
        return self.num_vertex

    def add_vertex(self, id):
        self.num_vertex = self.num_vertex + 1
        new_vertex = Vertex(id)
        self.vertex_dict[id] = new_vertex
        return new_vertex

    def get_vertex(self, id):
        if id in self.vertex_dict:
            return self.vertex_dict[id]
        else:
            return None

    def add_edge(self, frm, to, weight=0):
        if frm not in self.vertex_dict:
            self.add_vertex(frm)
        if to not in self.vertex_dict:
            self.add_vertex(to)
        self.vertex_dict[frm].add_neighbor(self.vertex_dict[to], weight)
        # プリム法は無向グラフ
        self.vertex_dict[to].add_neighbor(self.vertex_dict[frm], weight)

    def get_vertices(self):
        return self.vertex_dict.keys()

    def get_edges(self):
        edges = []
        for v in self.vertex_dict.values():
            for w in v.get_connections():
                vid = v.get_vertex_id()
                wid = w.get_vertex_id()
                edges.append((vid, wid, v.get_weight(w)))
        return edges


def prim(graph, source):
    source.set_distance(0)

    priority_queue = [(vertex.get_distance(), vertex) for vertex in graph]
    heapq.heapify(priority_queue)

    while len(priority_queue):
        nearest_vrtex = heapq.heappop(priority_queue)
        current = nearest_vrtex[1]
        current.set_visited()

        for next in current.adjacent:
            if next.visited:
                continue
            new_distance = current.get_weight(next)

            if next.get_distance() > new_distance:
                next.set_distance(new_distance)
                next.set_previous(current)
        
        heapq.heapify(priority_queue)

    total_distance = 0
    for vertex in graph.vertex_dict.values():
        if vertex.get_previous():
            print(f'辺 {vertex.get_vertex_id()}{vertex.previous.get_vertex_id()} --> {vertex.get_distance()}')
            total_distance += vertex.get_distance()
    print(f'総距離 {total_distance}')


if __name__ == '__main__':

    graph = Graph()
    graph.add_vertex('a')
    graph.add_vertex('b')
    graph.add_vertex('c')
    graph.add_vertex('d')
    graph.add_vertex('e')
    graph.add_vertex('f')
    graph.add_vertex('g')
    graph.add_edge('a', 'b', 7)  
    graph.add_edge('a', 'd', 5)
    graph.add_edge('b', 'c', 8)
    graph.add_edge('b', 'd', 9)
    graph.add_edge('b', 'e', 7)
    graph.add_edge('c', 'e', 5)
    graph.add_edge('d', 'e', 15)
    graph.add_edge('d', 'f', 6)
    graph.add_edge('e', 'f', 8)
    graph.add_edge('e', 'g', 9)
    graph.add_edge('f', 'g', 11)

    source = graph.get_vertex('d')
    prim(graph, source)
    # 辺 ad --> 5
    # 辺 ba --> 7
    # 辺 ce --> 5
    # 辺 eb --> 7
    # 辺 fd --> 6
    # 辺 ge --> 9
    # 総距離 39

   

想定通りの結果を得ることができました。