13 votes

Mise en œuvre de BFS, DFS et Dijkstra

Est-il vrai que la mise en œuvre de BFS, DFS et Dijkstra est presque la même, à l'exception que BFS utilise une file, DFS utilise une pile, tandis que Dijkstra utilise une file de priorité minimale?

Plus précisément. Pouvons-nous utiliser le code suivant pour BFS, DFS et Dijkstra, avec Q étant une file pour BFS, une pile pour DFS, et une file de priorité minimale pour Dijkstra? Merci!

Init d[]=Inf; // distance du nœud s
Init c[]='w'; // couleur des nœuds: 'w': non découvert, 'g': découvert, 'b': entièrement exploré
Init p[]=null; // nœud précédent dans le chemin
c[s]='g';
d[s]=0;
Q.push(s);
while(!Q.empty()) {
    u = Q.front();
    Q.pop();
    for v in adj[u] {
        if(c(v)=='w') {
            c[v]='g';
            if(d[u]+w(u,v)

9voto

oksayt Points 2548

Oui

Supposons que nous avons ce graphe, et voulons trouver les distances les plus courtes en partant de A:

Graphe

Voici une interface simple NodeCollection qui permet les opérations nécessaires pour le parcours :

interface NodeCollection {
    void offer(E node);
    E extract();
    boolean isEmpty();
}

Et les implémentations pour la file, la pile et la file de priorité. Notez que cette interface et ces classes n'ont pas vraiment besoin d'être génériques :

static class NodeQueue implements NodeCollection {
    private final Queue queue = new LinkedList();
    @Override public void offer(E node) { queue.offer(node); }
    @Override public E extract() { return queue.poll(); }
    @Override public boolean isEmpty() { return queue.isEmpty(); }
}

static class NodeStack implements NodeCollection {
    private final Stack stack = new Stack();
    @Override public void offer(E node) { stack.push(node); }
    @Override public E extract() { return stack.pop(); }
    @Override public boolean isEmpty() { return stack.isEmpty(); }
}

static class NodePriorityQueue implements NodeCollection {
    private final PriorityQueue pq = new PriorityQueue();
    @Override public void offer(E node) { pq.add(node); }
    @Override public E extract() { return pq.poll(); }
    @Override public boolean isEmpty() { return pq.isEmpty(); }
}

Notez que pour que la PriorityQueue fonctionne comme prévu, la classe Node doit fournir une méthode compareTo(Node) :

static class Node implements Comparable {
    final String name;
    Map neighbors;
    int dist = Integer.MAX_VALUE;
    Node prev = null;
    char color = 'w';

    Node(String name) {
        this.name = name;
        this.neighbors = Maps.newHashMap();
    }

    @Override public int compareTo(Node o) {
        return ComparisonChain.start().compare(this.dist, o.dist).result();
    }
}

Et voici enfin la classe Graph. Notez que la méthode traverse prend une instance de NodeCollection, qui sera utilisée pour stocker les nœuds lors du parcours.

static class Graph {
    Map nodes = Maps.newHashMap();

    void addEdge(String fromName, String toName, int weight) {
        Node from = getOrCreate(fromName);
        Node to = getOrCreate(toName);
        from.neighbors.put(to, weight);
        to.neighbors.put(from, weight);
    }

    Node getOrCreate(String name) {
        if (!nodes.containsKey(name)) {
            nodes.put(name, new Node(name));
        }
        return nodes.get(name);
    }

    /**
     * Parcourt ce graphe en commençant par le nœud donné et renvoie une map des chemins les plus courts depuis le nœud de départ
     * vers chaque nœud.
     *
     * @param startName nœud de départ
     * @return chemin le plus court pour chaque nœud du graphe
     */
    public Map traverse(String startName, NodeCollection collection) {
        assert collection.isEmpty();
        resetNodes();

        Node start = getOrCreate(startName);
        start.dist = 0;
        collection.offer(start);

        while (!collection.isEmpty()) {
            Node curr = collection.extract();
            curr.color = 'g';
            for (Node neighbor : curr.neighbors.keySet()) {
                if (neighbor.color == 'w') {
                    int thisPathDistance = curr.dist + curr.neighbors.get(neighbor);
                    if (thisPathDistance < neighbor.dist) {
                        neighbor.dist = thisPathDistance;
                        neighbor.prev = curr;
                    }
                    collection.offer(neighbor);
                }
            }
            curr.color = 'b';
        }

        Map shortestDists = Maps.newTreeMap();
        for (Node node : nodes.values()) {
            shortestDists.put(node.name, node.dist);
        }
        return shortestDists;
    }

    private void resetNodes() {
        for (Node node : nodes.values()) {
            node.dist = Integer.MAX_VALUE;
            node.prev = null;
            node.color = 'w';
        }
    }
}

Enfin, voici la méthode main, qui parcourt le même graphe 3 fois, une fois avec chacun des types NodeCollection :

private static Graph initGraph() {
    Graph graph = new Graph();
    graph.addEdge("A", "B", 2);
    graph.addEdge("B", "C", 2);
    graph.addEdge("C", "D", 2);
    graph.addEdge("D", "E", 2);
    graph.addEdge("E", "F", 2);
    graph.addEdge("F", "L", 2);

    graph.addEdge("A", "G", 10);
    graph.addEdge("G", "H", 10);
    graph.addEdge("H", "I", 10);
    graph.addEdge("I", "J", 10);
    graph.addEdge("J", "K", 10);
    graph.addEdge("K", "L", 10);

    return graph;
}

public static void main(String[] args) {
    Graph graph = initGraph();
    System.out.println("Queue (BFS):\n" + graph.traverse("A", new NodeQueue()));
    System.out.println("Stack (DFS):\n" + graph.traverse("A", new NodeStack()));
    System.out.println("PriorityQueue (Dijkstra):\n" + graph.traverse("A", new NodePriorityQueue()));
}

Et les résultats !

Queue (BFS):
{A=0, B=2, C=4, D=6, E=8, F=10, G=10, H=20, I=30, J=40, K=22, L=12}
Stack (DFS):
{A=0, B=2, C=4, D=66, E=64, F=62, G=10, H=20, I=30, J=40, K=50, L=60}
PriorityQueue (Dijkstra):
{A=0, B=2, C=4, D=6, E=8, F=10, G=10, H=20, I=30, J=32, K=22, L=12}

Remarquez que DFS prend parfois la branche supérieure en premier, donnant des résultats différents mais symétriques.

Voici à quoi ressemblent les résultats sur le graphe :

Résultats

2voto

AndreyT Points 139512

En ce qui concerne BFS vs. DFS : oui et non, mais plus de "non" que de "oui".

Si tout ce qui vous importe est l'ordre de traversée avant, c'est-à-dire l'ordre dans lequel l'algorithme découvre les nouveaux sommets du graphe, alors oui : vous pouvez prendre l'algorithme classique BFS, remplacer la file FIFO par une pile LIFO, et vous obtiendrez un pseudo-algorithme DFS.

Cependant, je l'appelle algorithme pseudo-DFS car ce n'est pas vraiment la même chose que le DFS classique.

L'algorithme DFS obtenu de cette manière générera en effet un véritable ordre de découverte des sommets DFS. Cependant, il sera quand même différent du DFS classique à d'autres égards. Vous pouvez trouver la description du DFS classique dans n'importe quel livre sur les algorithmes (ou Wikipédia) et vous verrez que la structure de l'algorithme est notablement différente de BFS. Cela est fait ainsi pour une raison. Le DFS classique offre certains avantages supplémentaires en plus de produire le bon ordre de découverte des sommets DFS. Ces avantages supplémentaires incluent

  • Consommation de mémoire pic plus faible. Dans l'implémentation classique du DFS, la taille de la pile à chaque moment équivaut à la longueur du chemin de l'origine de la recherche au sommet actuel. Dans le pseudo-DFS, la taille de la pile à chaque moment est égale à la somme des degrés de tous les sommets de l'origine de la recherche au sommet actuel. Cela signifie que la consommation de mémoire pic de l'algorithme pseudo-DFS sera potentiellement beaucoup plus élevée.

    Pour un exemple extrême, considérez un graphe en forme de "flocon" composé d'un seul sommet au centre directement connecté à 1000 sommets l'entourant. Un DFS classique parcourra ce graphe avec une profondeur de pile maximale de 1. Pendant ce temps, un pseudo-DFS commencera en poussant les 1000 sommets dans la pile (à la manière de BFS), résultant en une profondeur de pile maximale de 1000. C'est une sacrée différence.

  • Retour arrière. Un algorithme DFS classique est un véritable algorithme récursif. En tant qu'algorithme récursif en plus de l'ordre de traversée avant (c'est-à-dire l'ordre de découverte des sommets), il vous fournit également un ordre de traversée arrière (retour arrière). Dans le DFS classique, vous visitez chaque sommet plusieurs fois : la première fois lorsque vous le découvrez pour la toute première fois, puis lorsque vous revenez d'un de ses sommets descendants pour passer au prochain sommet descendant, et enfin pour la toute dernière fois lorsque vous avez traité tous ses descendants. De nombreux algorithmes basés sur DFS sont construits en capturant et en gérant ces visites. Par exemple, l'algorithme de tri topologique est un DFS classique qui renvoie les sommets dans l'ordre de leur dernière visite DFS. L'algorithme pseudo-DFS, comme je l'ai dit ci-dessus, ne vous fournit que un accès clair au premier événement (découverte de sommets), mais ne note aucun événement de retour arrière.

0voto

Cris Stringfellow Points 2539

Oui, c'est vrai. Beaucoup d'algorithmes utiles ont des schémas similaires. Par exemple, pour les vecteurs propres de graphiques, l'algorithme de l'itération de puissance, si vous changez le vecteur de départ et le vecteur d'orthogonalisation, vous obtenez une famille entière d'algorithmes utiles, mais liés. Dans ce cas, on parle de projection ABS.

Dans ce cas, ils sont tous basés sur le primitive "d'addition incrémentielle"-à-un-arbre. C'est juste comment nous choisissons cette arête / ce sommet à ajouter qui détermine le type d'arbre et donc le type de navigation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X