Prim's Algorithm (Adjacency Matrix)

using System;

namespace Algorithms.Graph.MinimumSpanningTree;

/// <summary>
///     Class that uses Prim's (Jarnik's algorithm) to determine the minimum
///     spanning tree (MST) of a given graph. Prim's algorithm is a greedy
///     algorithm that can determine the MST of a weighted undirected graph
///     in O(V^2) time where V is the number of nodes/vertices when using an
///     adjacency matrix representation.
///     More information: https://en.wikipedia.org/wiki/Prim%27s_algorithm
///     Pseudocode and runtime analysis: https://www.personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/GraphAlgor/primAlgor.htm .
/// </summary>
public static class PrimMatrix
{
    /// <summary>
    ///     Determine the minimum spanning tree for a given weighted undirected graph.
    /// </summary>
    /// <param name="adjacencyMatrix">Adjacency matrix for graph to find MST of.</param>
    /// <param name="start">Node to start search from.</param>
    /// <returns>Adjacency matrix of the found MST.</returns>
    public static float[,] Solve(float[,] adjacencyMatrix, int start)
    {
        ValidateMatrix(adjacencyMatrix);

        var numNodes = adjacencyMatrix.GetLength(0);

        // Create array to represent minimum spanning tree
        var mst = new float[numNodes, numNodes];

        // Create array to keep track of which nodes are in the MST already
        var added = new bool[numNodes];

        // Create array to keep track of smallest edge weight for node
        var key = new float[numNodes];

        // Create array to store parent of node
        var parent = new int[numNodes];

        for (var i = 0; i < numNodes; i++)
        {
            mst[i, i] = float.PositiveInfinity;
            key[i] = float.PositiveInfinity;

            for (var j = i + 1; j < numNodes; j++)
            {
                mst[i, j] = float.PositiveInfinity;
                mst[j, i] = float.PositiveInfinity;
            }
        }

        // Ensures that the starting node is added first
        key[start] = 0;

        // Keep looping until all nodes are in tree
        for (var i = 0; i < numNodes - 1; i++)
        {
            GetNextNode(adjacencyMatrix, key, added, parent);
        }

        // Build adjacency matrix for tree
        for (var i = 0; i < numNodes; i++)
        {
            if (i == start)
            {
                continue;
            }

            mst[i, parent[i]] = adjacencyMatrix[i, parent[i]];
            mst[parent[i], i] = adjacencyMatrix[i, parent[i]];
        }

        return mst;
    }

    /// <summary>
    ///     Ensure that the given adjacency matrix represents a weighted undirected graph.
    /// </summary>
    /// <param name="adjacencyMatrix">Adjacency matric to check.</param>
    private static void ValidateMatrix(float[,] adjacencyMatrix)
    {
        // Matrix should be square
        if (adjacencyMatrix.GetLength(0) != adjacencyMatrix.GetLength(1))
        {
            throw new ArgumentException("Adjacency matrix must be square!");
        }

        // Graph needs to be undirected and connected
        for (var i = 0; i < adjacencyMatrix.GetLength(0); i++)
        {
            var connection = false;
            for (var j = 0; j < adjacencyMatrix.GetLength(0); j++)
            {
                if (Math.Abs(adjacencyMatrix[i, j] - adjacencyMatrix[j, i]) > 1e-6)
                {
                    throw new ArgumentException("Adjacency matrix must be symmetric!");
                }

                if (!connection && float.IsFinite(adjacencyMatrix[i, j]))
                {
                    connection = true;
                }
            }

            if (!connection)
            {
                throw new ArgumentException("Graph must be connected!");
            }
        }
    }

    /// <summary>
    ///     Determine which node should be added next to the MST.
    /// </summary>
    /// <param name="adjacencyMatrix">Adjacency matrix of graph.</param>
    /// <param name="key">Currently known minimum edge weight connected to each node.</param>
    /// <param name="added">Whether or not a node has been added to the MST.</param>
    /// <param name="parent">The node that added the node to the MST. Used for building MST adjacency matrix.</param>
    private static void GetNextNode(float[,] adjacencyMatrix, float[] key, bool[] added, int[] parent)
    {
        var numNodes = adjacencyMatrix.GetLength(0);
        var minWeight = float.PositiveInfinity;

        var node = -1;

        // Find node with smallest node with known edge weight not in tree. Will always start with starting node
        for (var i = 0; i < numNodes; i++)
        {
            if (!added[i] && key[i] < minWeight)
            {
                minWeight = key[i];
                node = i;
            }
        }

        // Add node to mst
        added[node] = true;

        // Update smallest found edge weights and parent for adjacent nodes
        for (var i = 0; i < numNodes; i++)
        {
            if (!added[i] && adjacencyMatrix[node, i] < key[i])
            {
                key[i] = adjacencyMatrix[node, i];
                parent[i] = node;
            }
        }
    }
}