25

ダイクストラのアルゴリズムをJavaで実装しようとしています(自習)。ウィキペディア(リンク)が提供する擬似コードを使用します。アルゴリズムの終わり近くで、私はする必要がありdecrease-key v in Q;ます。BinaryHeapなどでQを実装する必要があったと思いますか?ここで使用するのに適切な(組み込みの)データ型は何でしょうか?

private void dijkstra(int source) {
        int[] dist = new int[this.adjacencyMatrix.length];
        int[] previous = new int[this.adjacencyMatrix.length];
        Queue<Integer> q = new LinkedList<Integer>();

        for (int i = 0; i < this.adjacencyMatrix.length; i++) {
            dist[i] = this.INFINITY;
            previous[i] = this.UNDEFINED;
            q.add(i);
        }

        dist[source] = 0;

        while(!q.isEmpty()) {
            // get node with smallest dist;
            int u = 0;
            for(int i = 0; i < this.adjacencyMatrix.length; i++) {
                if(dist[i] < dist[u])
                    u = i;
            }

            // break if dist == INFINITY
            if(dist[u] == this.INFINITY) break;

            // remove u from q
            q.remove(u);

            for(int i = 0; i < this.adjacencyMatrix.length; i++) {
                if(this.adjacencyMatrix[u][i] == 1) {
                    // in a unweighted graph, this.adjacencyMatrix[u][i] always == 1;
                    int alt = dist[u] + this.adjacencyMatrix[u][i]; 
                    if(alt < dist[i]) {
                        dist[i] = alt;
                        previous[i] = u;

                        // here's where I should "decrease the key"
                    }
                }
            }
        }
    }
4

5 に答える 5

39

最も簡単な方法は、優先キューを使用し、優先キューに以前に追加されたキーを気にしないことです。これは、各ノードがキューに複数回存在することを意味しますが、これによってアルゴリズムがまったく損なわれることはありません。あなたがそれを見ると、置き換えられたノードのすべてのバージョンが後でピックアップされ、それまでに最も近い距離がすでに決定されています。

if alt < dist[v]:ウィキペディアからのチェックがこれを機能させるものです。ランタイムはこれから少しだけ低下しますが、非常に高速なバージョンが必要な場合は、さらに最適化する必要があります。

ノート:

他の最適化と同様に、これは注意して処理する必要があり、好奇心が強く、見つけにくいバグにつながる可能性があります(たとえば、ここを参照)。ほとんどの場合、削除と再挿入を使用するだけで問題ありませんが、ここで説明したトリックにより、ダイクストラの実装がボトルネックになっている場合は、コードを少し高速化できます。

最も重要なこと:これを試す前に、優先キューが優先度をどのように処理するかを確認してください。キュー内の実際の優先度は決して変更されないはずです。そうしないと、キューの不変条件が台無しになる可能性があります。つまり、キューに格納されているアイテムを取得できなくなる可能性があります。たとえば、Javaでは、優先度はオブジェクトと一緒に保存されるため、追加のラッパーが必要です。

これは機能しません:

import java.util.PriorityQueue;

// Store node information and priority together
class Node implements Comparable<Node> {
  public int prio;
  public Node(int prio) { this.prio = prio; }

  public int compareTo(Node n) {
     return Integer.compare(this.prio, n.prio);
  }
}

...
...
PriorityQueue<Node> q = new PriorityQueue<Node>();
n = new Node(10);
q.add(n)
...
// let's update the priority
n.prio = 0;
// re-add
q.add(n);
// q may be broken now

n.prio=0キュー内のオブジェクトの優先度も変更しているためです。ただし、これは正常に機能します。

import java.util.PriorityQueue;

// Only node information
class Node {
  // Whatever you need for your graph
  public Node() {}
}

class PrioNode {
   public Node n;
   public int prio;
   public PrioNode(Node n, int prio) {
     this.n = n;
     this.prio = prio;
   }

   public int compareTo(PrioNode p) {
      return Integer.compare(this.prio, p.prio);
   }
}

...
...
PriorityQueue<PrioNode> q = new PriorityQueue<PrioNode>();
n = new Node();
q.add(new PrioNode(n,10));
...
// let's update the priority and re-add
q.add(new PrioNode(n,0));
// Everything is fine, because we have not changed the value
// in the queue.
于 2011-06-07T15:28:08.317 に答える
22

TreeSet、(C ++ではを使用できます)を使用して、std::setダイクストラの優先度付きキューを実装できます。Aはセットを表しますが、セット内のアイテムの順序を記述するTreeSetこともできます。セットにノードを格納し、ノードの距離を使用してノードを並べ替える必要があります。距離が最小のノードがセットの先頭になります。

class Node {
    public int id;   // store unique id to distinguish elements in the set
    public int dist; // store distance estimates in the Node itself
    public int compareTo(Node other) {
        // TreeSet implements the Comparable interface.
        // We need tell Java how to compare two Nodes in the TreeSet.
        // For Dijstra, we want the node with the _smallest_ distance
        // to be at the front of the set.
        // If two nodes have same distance, choose node with smaller id
        if (this.dist == other.dist) {
            return Integer.valueOf(this.id).compareTo(other.id);
        } else {
            return Integer.valueOf(this.dist).compareTo(other.dist);
        }
    }
}
// ...
TreeSet<Node> nodes = new TreeSet<Node>();

extract-min操作は、以下を介して実装され、O(lgn)の最悪の場合の時間を要します。

Node u = nodes.pollFirst();

キーの減少操作では、古いキー(古い距離推定)を持つノードを削除し、小さいキー(新しい、より良い距離推定)を持つ新しいノードを追加します。どちらの操作も、最悪の場合O(lgn)の時間がかかります。

nodes.remove(v);
v.dist = /* some smaller key */
nodes.add(v);

いくつかの追加の注意:

  • 上記は実装が非常に簡単であり、これらの操作は両方ともnで対数であるため、全体として、実行時間はO((n + e)lgn)になります。これは、ダイクストラの基本的な実装にとって効率的であると考えられています。この複雑さの証拠については、 CLRSブックISBN:978-0-262-03384-8)の第19章を参照してください。
  • ほとんどの教科書はダイクストラ、プリム、A *などの優先キューを使用しますが、残念ながらJavaもC ++も、同じO(lgn)減少キー操作で優先キューを実装していません。

  • PriorityQueueJavaには存在しますが、remove(Object o)メソッドは対数ではないため、キーの減少操作はO(lgn)ではなくO(n)になり、(漸近的に)より遅いDikjstraが得られます!

  • 何もないところからTreeSetを構築するには(forループを使用)、時間O(nlgn)がかかります。これは、n個のアイテムからヒープ/優先度キューを初期化するO(n)の最悪の場合の時間と比較して少し遅いです。ただし、ダイクストラのメインループには、この初期化時間を支配する時間O(nlgn + elgn)がかかります。したがって、Dijkstraの場合、TreeSetを初期化しても大幅な速度低下は発生しません。

  • キーHashSetの順序を気にするため、を使用することはできません。最初に最小のものを引き出すことができるようにしたいのです。これにより、最適な距離推定値を持つノードが得られます。

  • TreeSetJavaでは、赤黒木(自己平衡二分探索木)を使用して実装されます。そのため、これらの操作には対数の最悪の場合の時間があります。

  • intグラフノードを表すためにsを使用していますが、これは優れていますが、Nodeクラスを導入するときに、2つのエンティティを関連付ける方法が必要になります。グラフを作成するときにを作成することをお勧めします。これは、どれが何に対応するかHashMap<Integer, Node>を追跡するのに役立ちます。`intNode

于 2017-02-24T16:10:15.177 に答える
3

提案されPriorityQueueたものは、キーを減らす操作を提供しません。ただし、要素を削除してから新しいキーで再挿入することでエミュレートできます。これにより、アルゴリズムの漸近実行時間が長くなることはありませんが、組み込みのサポートを使用するとわずかに高速化できます。

編集O(log n):decrease-keyはヒープ用であると予想されますが、であるため、これにより漸近実行時間が増加しremove(Object)ますO(n)効率的な減少キーをサポートする組み込みの優先度キューがJavaにないようです。

于 2011-06-07T15:08:00.670 に答える
1

wikiの記事による優先キュー。これは、現在の古典的な実装が「フィボナッチヒープによって実装された最小優先度キュー」を使用することであることを示唆しています。

于 2011-06-07T14:59:44.107 に答える
1

はい、JavaはPriorityQueueを介して最小ヒープの減少キーを提供しないため、削除の操作はlogNに最適化できるO(N)になります。

私はdecreaseKeyを使用して最小ヒープを実装しました(実際にはdecreaseKeyとincreaseKeyの両方ですが、ここではdecreaseKeyだけで十分です)。実際のデータ構造は最小ヒープマップです(HashMapはすべてのノードのインデックスを格納し、現在の頂点を介して現在の頂点の隣接する最小パス値を更新するのに役立ちます)

ジェネリックスを使用して最適化されたソリューションを実装しました。コーディングに約3〜4時間かかりました(初めて)。時間計算量はO(logV.E)です。

これがお役に立てば幸いです。

 package algo;

 import java.util.*;

 public class Dijkstra {

/*
 *
 * @author nalin.sharma
 *
 */

/**
 *
 * Heap Map Data Structure
 * Heap stores the Nodes with their weight based on comparison on Node's weight
 * HashMap stores the Node and its index for O(1) look up of node in heap
 *
 *
 *
 *
 * Example -:
 *
 *                                   7
 *                         [2]----------------->[4]
 *                       ^  |                   ^ \
 *                     /   |                   |   \ 1
 *                 2 /    |                   |     v
 *                 /     |                   |       [6]
 *               /      | 1               2 |       ^
 *             /       |                   |      /
 *          [1]       |                   |     /
 *             \     |                   |    / 5
 *            4 \   |                   |   /
 *               v v                   |  /
 *                [3]---------------->[5]
 *                         3
 *
 *        Minimum distance from source 1
 *         v  | d[v] | path
 *         --- ------  ---------
 *         2 |  2  |  1,2
 *         3 |  3  |  1,2,3
 *         4 |  8  |  1,2,3,5,4
 *         5 |  6  |  1,2,3,5
 *         6 |  9  |  1,2,3,4,6
 *
 *
 *
 *     Below is the Implementation -:
 *
 */

static class HeapMap<T> {
    int size, ind = 0;
    NodeWeight<T> arr [];
    Map<T,Integer> map;

    /**
     *
     * @param t is the Node(1,2,3..or A,B,C.. )
     * @return the index of element in min heap
     */
    int index(T t) {
        return map.get(t);
    }

    /**
     *
     * @param index is the Node(1,2,3..or A,B,C.. )
     * @return Node and its Weight
     */
    NodeWeight<T> node(int index) {
        return arr[index];
    }

    /**
     *
     * @param <T> Node of type <T> and its weight
     */
    static class NodeWeight<T> {
        NodeWeight(T v, int w) {
            nodeVal = v;
            weight = w;
        }
        T nodeVal;
        int weight;
        List<T> path = new ArrayList<>();
    }

    public HeapMap(int s) {
        size = s;
        arr = new NodeWeight[size + 1];
        map = new HashMap<>();
    }

    private void updateIndex(T key, int newInd) {
        map.put(key, newInd);
    }

    private void shiftUp(int i) {
        while(i > 1) {
            int par = i / 2;
            NodeWeight<T> currNodeWeight = arr[i];
            NodeWeight<T> parentNodeWeight = arr[par];
            if(parentNodeWeight.weight > currNodeWeight.weight) {
                updateIndex(parentNodeWeight.nodeVal, i);
                updateIndex(currNodeWeight.nodeVal, par);
                swap(par,i);
                i = i/2;
            }
            else {
                break;
            }
        }
    }

    /**
     *
     * @param nodeVal
     * @param newWeight
     * Based on if the value introduced is higher or lower shift down or shift up operations are performed
     *
     */
    public void update(T nodeVal, int newWeight) {
        int i = ind;
        NodeWeight<T> nodeWeight = arr[map.get(nodeVal)];
        int oldWt = nodeWeight.weight;
        nodeWeight.weight = newWeight;
        if(oldWt < newWeight) {
            shiftDown(map.get(nodeVal));
        }
        else if(oldWt > newWeight) {
            shiftUp(map.get(nodeVal));
        }
    }

    /**
     *
     * @param nodeVal
     * @param wt
     *
     * Typical insertion in Min Heap and storing its element's indices in HashMap for fast lookup
     */
    public void insert(T nodeVal, int wt) {
        NodeWeight<T> nodeWt = new NodeWeight<>(nodeVal, wt);
        arr[++ind] = nodeWt;
        updateIndex(nodeVal, ind);
        shiftUp(ind);
    }

    private void swap(int i, int j) {
        NodeWeight<T> tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    private void shiftDown(int i) {
        while(i <= ind) {
            int current = i;
            int lChild = i * 2;
            int rChild = i * 2 + 1;
            if(rChild <= ind) {
                int childInd = (arr[lChild].weight < arr[rChild].weight) ? lChild : rChild;
                if(arr[childInd].weight < arr[i].weight) {
                    updateIndex(arr[childInd].nodeVal, i);
                    updateIndex(arr[i].nodeVal, childInd);
                    swap(childInd, i);
                    i = childInd;
                }
            }
            else if(lChild <= ind && arr[lChild].weight < arr[i].weight) {
                updateIndex(arr[lChild].nodeVal, i);
                updateIndex(arr[i].nodeVal, lChild);
                swap(lChild, i);
                i = lChild;
            }
            if(current == i) {
                break;
            }
        }
    }

    /**
     *
     * @return
     *
     * Typical deletion in Min Heap and removing its element's indices in HashMap
     *
     */
    public NodeWeight<T> remove() {
        if(ind == 0) {
            return null;
        }
        map.remove(arr[1].nodeVal);
        NodeWeight<T> out = arr[1];
        out.path.add(arr[1].nodeVal);
        arr[1] = arr[ind];
        arr[ind--] = null;
        if(ind > 0) {
            updateIndex(arr[1].nodeVal, 1);
            shiftDown(1);
        }
        return out;
    }
}

/**
 *
 *  Graph representation -: It is an Map(T,Node<T>) of Map(T(neighbour), Integer(Edge's weight))
 *
 */
static class Graph<T> {

    void init(T... t) {
        for(T z: t) {
            nodes.put(z, new Node<>(z));
        }
    }

    public Graph(int s, T... t) {
        size = s;
        nodes = new LinkedHashMap<>(size);
        init(t);
    }

    /**
     *
     * Node class
     *
     */
    static class Node<T> {
        Node(T v) {
            val = v;
        }
        T val;
        //Set<Edge> edges = new HashSet<>();
        Map<T, Integer> edges = new HashMap<>();
    }

    /*static class Edge {
        Edge(int to, int w) {
            target = to;
            wt = w;
        }
        int target;
        int wt;
        }
    }*/

    int size;

    Map<T, Node<T>> nodes;

    void addEdge(T from, T to, int wt) {
        nodes.get(from).edges.put(to, wt);
    }
}

/**
 *
 * @param graph
 * @param from
 * @param heapMap
 * @param <T>
 *
 * Performs initialisation of all the nodes from the start vertex
 *
 */
    private static <T> void init(Graph<T> graph, T from, HeapMap<T> heapMap) {
    Graph.Node<T> fromNode = graph.nodes.get(from);
    graph.nodes.forEach((k,v)-> {
            if(from != k) {
                heapMap.insert(k, fromNode.edges.getOrDefault(k, Integer.MAX_VALUE));
            }
        });
    }


static class NodePathMinWeight<T> {
    NodePathMinWeight(T n, List<T> p, int c) {
        node = n;
        path = p;
        minCost= c;
    }
    T node;
    List<T> path;
    int minCost;
}

/**
 *
 * @param graph
 * @param from
 * @param <T>
 * @return
 *
 * Repeat the below process for all the vertices-:
 * Greedy way of picking the current shortest distance and updating its neighbors distance via this vertex
 *
 * Since Each Vertex V has E edges, the time Complexity is
 *
 * O(V.logV.E)
 * 1. selecting vertex with shortest distance from source in logV time -> O(logV) via Heap Map Data structure
 * 2. Visiting all E edges of this vertex and updating the path of its neighbors if found less via this this vertex. -> O(E)
 * 3. Doing operation step 1 and step 2 for all the vertices -> O(V)
 *
 */

    static <T> Map<T,NodePathMinWeight<T>> dijkstra(Graph<T> graph, T from) {
    Map<T,NodePathMinWeight<T>> output = new HashMap<>();
    HeapMap<T> heapMap = new HeapMap<>(graph.nodes.size());
    init(graph, from, heapMap);
    Set<T> isNotVisited = new HashSet<>();
    graph.nodes.forEach((k,v) -> isNotVisited.add(k));
    isNotVisited.remove(from);
    while(!isNotVisited.isEmpty()) {
        HeapMap.NodeWeight<T> currNodeWeight = heapMap.remove();
        output.put(currNodeWeight.nodeVal, new NodePathMinWeight<>(currNodeWeight.nodeVal, currNodeWeight.path, currNodeWeight.weight));
        //mark visited
        isNotVisited.remove(currNodeWeight.nodeVal);
        //neighbors
        Map<T, Integer> neighbors = graph.nodes.get(currNodeWeight.nodeVal).edges;
        neighbors.forEach((k,v) -> {
            int ind = heapMap.index(k);
            HeapMap.NodeWeight<T> neighbor = heapMap.node(ind);
            int neighborDist = neighbor.weight;
            int currentDistance = currNodeWeight.weight;
            if(currentDistance + v < neighborDist) {
                //update
                neighbor.path = new ArrayList<>(currNodeWeight.path);
                heapMap.update(neighbor.nodeVal, currentDistance + v);
            }
        });
    }
    return output;
}

public static void main(String[] args) {
    Graph<Integer> graph = new Graph<>(6,1,2,3,4,5,6);
    graph.addEdge(1,2,2);
    graph.addEdge(1,3,4);
    graph.addEdge(2,3,1);
    graph.addEdge(2,4,7);
    graph.addEdge(3,5,3);
    graph.addEdge(5,6,5);
    graph.addEdge(4,6,1);
    graph.addEdge(5,4,2);

    Integer source = 1;
    Map<Integer,NodePathMinWeight<Integer>> map = dijkstra(graph,source);
    map.forEach((k,v) -> {
        v.path.add(0,source);
        System.out.println("source vertex :["+source+"] to vertex :["+k+"] cost:"+v.minCost+" shortest path :"+v.path);
    });
}

}

出力-:

ソース頂点:[1]から頂点:[2]コスト:2最短パス:[1、2]

ソース頂点:[1]から頂点:[3]コスト:3最短パス:[1、2、3]

ソース頂点:[1]から頂点:[4]コスト:8最短パス:[1、2、3、5、4]

ソース頂点:[1]から頂点:[5]コスト:6最短パス:[1、2、3、5]

ソース頂点:[1]から頂点:[6]コスト:9最短パス:[1、2、3、5、4、6]

于 2021-05-26T14:01:50.337 に答える