読者です 読者をやめる 読者になる 読者になる

ベルマンフォード法とダイカストラ法について

C# プログラミングコンテスト

メモ。
AOJのこの問題で、入力数が大きいパターンでタイムアウトになっていた。

有向グラフが与えられる。このとき、頂点rから各頂点までの最短経路を求めよ。

単一始点最短経路 | グラフ | Aizu Online Judge

ダイカストラではなくベルマンフォードで解いていたのが原因。この問題では、Vが最大100000、Eが最大500000と大きいので、O(VE)であるベルマンフォードだと制限時間内(3s)に処理が終わらない。 追記:ベルマンフォードを適切に実装したら問題なかったようだ。コードも差し替えておく。
ダイカストラ: O(E logV)
ベルマンフォード: O(VE)

教訓:負の辺がない場合はダイカストラの利用を考える

  • ベルマンフォードでの解答(抜粋)
/// <summary>
/// find the shortest path from start to all destination
/// </summary>
public class BellmanFord
{
    public List<edge> Edge = new List<edge>();
    public int V;   // number of vertex

    public class edge
    {
        public int from, to, cost;
        public edge(int _from, int _to, int _cost)
        {
            from = _from;
            to = _to;
            cost = _cost;
        }
    }

    public BellmanFord(int v)
    {
        V = v;
    }

    /// <summary>
    ///  return shortestPath[V] represents distance from startIndex
    /// </summary>
    public int[] GetShortestPath(int startIndex)
    {
        int[] shortestPath = new int[V];
        var INF = Int32.MaxValue;

        for (int i = 0; i < V; i++) shortestPath[i] = INF;
        shortestPath[startIndex] = 0;
        while (true)
        {
            bool update = false;
            foreach (edge e in Edge)
            {
                if (shortestPath[e.from] != INF && shortestPath[e.to] > shortestPath[e.from] + e.cost)
                {
                    shortestPath[e.to] = shortestPath[e.from] + e.cost;
                    update = true;
                }
            }
            if (!update) break;
        }

        return shortestPath;
    }

    /// <summary>
    ///  return true if it has negative close loop
    /// </summary>
    public bool HasNegativeLoop()
    {
        int[] d = new int[V];
        for (int i = 0; i < V; i++)
        {
            foreach (edge e in Edge)
            {
                if (d[e.to] > d[e.from] + e.cost)
                {
                    d[e.to] = d[e.from] + e.cost;
                    if (i == V - 1) return true;
                }
            }
        }
        return false;
    }
}
  • ダイカストラでの解答(抜粋)
/// <summary>
/// Get min cost between two points
/// </summary>
public class Dijkstra
{
    private class Edge
    {
        public int T { get; set; }
        public int C { get; set; }
    }

    private int maxIndex = -1;
    private const int INF = Int32.MaxValue;

    private Dictionary<int, List<Edge>> _edgeArray = new Dictionary<int, List<Edge>>();

    /// <summary>
    /// Dijkstra, get min cost between two points
    /// should not contain negatie cost path
    /// </summary>
    /// <param name="size">max index of vertice</param>
    public Dijkstra(int size)
    {
        maxIndex = size + 1;
    }

    // Add a path with its cost
    public void AddPath(int s, int t, int cost)
    {
        if (!_edgeArray.ContainsKey(s)) _edgeArray.Add(s, new List<Edge>());
        _edgeArray[s].Add(new Edge() { T = t, C = cost });
    }

    //Get the min cost from s
    //return Int32.MaxValue if no path
    public int[] GetMinCost(int s)
    {
        int[] cost = new int[maxIndex];
        for (int i = 0; i < cost.Length; i++) cost[i] = INF;
        cost[s] = 0;

        var priorityQueue = new PriorityQueue<ComparablePair<int, int>>(_edgeArray.Sum( e => e.Value.Count()) + 1);
        priorityQueue.Push(new ComparablePair<int, int>(0, s));

        while (priorityQueue.Count() > 0)
        {
            var costDestinationPair = priorityQueue.Pop();
            if (cost[costDestinationPair.Item2] < costDestinationPair.Item1) continue;
            if (!_edgeArray.ContainsKey(costDestinationPair.Item2)) continue;

            foreach (var edge in _edgeArray[costDestinationPair.Item2])
            {
                var newCostToi = costDestinationPair.Item1 + edge.C;
                if (newCostToi < cost[edge.T])
                {
                    cost[edge.T] = newCostToi;
                    priorityQueue.Push(new ComparablePair<int, int>(newCostToi, edge.T));
                }
            }
        }

        return cost;
    }
}

PriorityQueueを自作する必要があるのと、Dijkstra内を単純な配列にするとメモリが足りなくなるのが面倒だった。
こういう問題だとC++を使ったほうが楽そうだ。