Site icon Meccanismo Complesso

Graph Algorithms: Minimum Spanning Tree, Kruskal and Prim Algorithms

Minimum Spanning Tree
Minimum Spanning Tree header

The “Minimum Spanning Tree” (MST) of a connected graph is a subset of the edges of that graph that forms a tree, and this tree must connect all the nodes of the graph. Furthermore, the sum of the weights of the edges in this tree must be as low as possible.

[wpda_org_chart tree_id=43 theme_id=50]

The Minimum Spanning Tree algorithm

In other words, imagine you have a graph where each edge has an associated weight. A Minimum Spanning Tree of this graph is a set of edges that connects all the nodes without forming cycles and the sum of the weights of these edges is the lowest possible among all the trees that can be formed by connecting all the nodes of the graph.

This problem is relevant in many practical situations. For example, it might represent the street network of a city, where you want to build a series of streets so that every area of the city is accessible and the sum of the street lengths is minimal. Or it could represent a computer network, where the arcs represent links between computers and you want to minimize the cost of connectivity.

Algorithms such as Kruskal’s algorithm and Prim’s algorithm are used to solve the Minimum Spanning Tree problem. These algorithms are implemented to efficiently find the set of edges that constitute a Minimum Spanning Tree in a weighted graph.

Kruskal algorithm

The Kruskal algorithm is a greedy algorithm used to find the Minimum Spanning Tree (MST) of an undirected and weighted graph. Its basic idea is to select the lightest edges in ascending order and add them to the tree, avoiding forming loops. The algorithm ends when all nodes are connected or when all edges have been considered.

Here is a detailed description of how Kruskal’s algorithm works:

Features of the Kruskal algorithm:

Here is an example of implementation

class Graph:
    def __init__(self, vertices):
        self.V = vertices
        self.graph = []

    def add_edge(self, u, v, w):
        self.graph.append([u, v, w])

    def kruskal(self):
        self.graph = sorted(self.graph, key=lambda item: item[2])
        parent = [i for i in range(self.V)]
        result = []

        def find(x):
            if parent[x] != x:
                parent[x] = find(parent[x])
            return parent[x]

        def union(x, y):
            root_x = find(x)
            root_y = find(y)
            if root_x != root_y:
                parent[root_x] = root_y
                return True
            return False

        for edge in self.graph:
            u, v, w = edge
            if union(u, v):
                result.append([u, v, w])

        return result

# Example of usage
g = Graph(4)
g.add_edge(0, 1, 10)
g.add_edge(0, 2, 6)
g.add_edge(0, 3, 5)
g.add_edge(1, 3, 15)
g.add_edge(2, 3, 4)

mst = g.kruskal()
print("Minimum Spanning Tree:")
for edge in mst:
    print(edge)

In questo esempio, g.kruskal() restituirà il Minimo Albero Ricoprente del grafo definito. I pesi degli archi sono dati dalla terza componente di ciascun arco nella rappresentazione [u, v, w].

Executing the code you will get the following result:

Minimum Spanning Tree:
[2, 3, 4]
[0, 3, 5]
[0, 1, 10]

Prim algorithm

The Prim algorithm is another greedy algorithm used to find the Minimum Spanning Tree (MST) of an undirected and weighted graph. Unlike Kruskal, Prim’s algorithm starts from an arbitrary node and iteratively adds the lightest edge connecting a node already included in the tree to one outside the tree.

Here’s how Prim’s algorithm works:

Features of Prim’s algorithm:

Here is an example Python implementation of Prim’s algorithm:

import heapq

class Graph:
    def __init__(self, vertices):
        self.V = vertices
        self.graph = {}

    def add_edge(self, u, v, w):
        if u not in self.graph:
            self.graph[u] = {}
        if v not in self.graph:
            self.graph[v] = {}

        self.graph[u][v] = w
        self.graph[v][u] = w

    def prim(self, start_node):
        heap = [(0, start_node)]
        included = set()
        mst = []

        while heap:
            weight, node = heapq.heappop(heap)
            if node not in included:
                included.add(node)

                for neighbor, edge_weight in self.graph[node].items():
                    heapq.heappush(heap, (edge_weight, neighbor))
                if len(mst) < self.V - 1:
                    mst.append((node, neighbor, edge_weight))

        return mst

# Example of usage
g = Graph(4)
g.add_edge(0, 1, 10)
g.add_edge(0, 2, 6)
g.add_edge(0, 3, 5)
g.add_edge(1, 3, 15)
g.add_edge(2, 3, 4)

mst = g.prim(0)
print("Minimum Spanning Tree:")
for edge in mst:
    print(edge)

In this example, g.prim(0) will return the Minimum Spanning Tree of the defined graph, starting from node 0 as the starting node. The weights of the edges are given by the values associated with the edges in the graph dictionary.

Executing you will get the following result:

Minimum Spanning Tree:
(0, 3, 5)
(3, 2, 4)
(2, 3, 4)

Some considerations on these algorithms

Both the Kruskal and Prim algorithms for calculating the Minimum Spanning Tree (MST) in a weighted graph have asymptotic complexities that mainly depend on the way they are implemented and the data structures used.

Kruskal algorithm:
The asymptotic complexity of Kruskal’s algorithm depends mainly on the ordering of the edges and the Union-Find data structure used. Assuming that an efficient sorting algorithm such as Merge Sort or Heap Sort is used, and a Union-Find data structure based on union-per-rank and path compression, the asymptotic complexity of Kruskal’s algorithm is O(E log E), where E is the number of edges in the graph.

Prim algorithm:
The asymptotic complexity of Prim’s algorithm depends mainly on the implementation of the priority queue (heap). Using a min-heap-based priority queue, Prim’s algorithm has an asymptotic complexity of O((V + E) log V), where V is the number of nodes and E is the number of edges in the graph.

In both cases, the complexity is dominated by the sorting operation (for Kruskal) or the priority queue operations (for Prim). By choosing efficient algorithms for these operations, it is possible to achieve acceptable performance even for large graphs.

Data Structures Used:

Time Complexity:

Best Case and Worst Case:

Tree Structure Produced:

Implementation and Memory:

Choice of Starting Node:

Use in Specific Contexts:

In general, the choice between Kruskal and Prim depends on the specific characteristics of the graph and the performance requirements of the context in which they are used. Both algorithms are efficient and provide optimal solutions for the Minimum Spanning Tree problem.

Exit mobile version