La complexité temporelle pour parcourir chaque arête adjacente d'un sommet est par exemple O(N)
, où N est le nombre d'arêtes adjacentes. Ainsi, pour V nombre de sommets, la complexité temporelle devient O(V*N)
= O(E)
, où E est le nombre total d'arêtes dans le graphique. Étant donné que la suppression et l'ajout d'un sommet de/vers la file d'attente est O(1)
, pourquoi il est ajouté à la complexité temporelle globale de BFS en tant que O(V+E)
.
J'espère que cela sera utile à tous ceux qui ont du mal à comprendre la complexité du temps de calcul pour Breadth First Search alias BFS.
Queue graphTraversal.add(firstVertex);
// This while loop will run V times, where V is total number of vertices in graph.
while(graphTraversal.isEmpty == false)
currentVertex = graphTraversal.getVertex();
// This while loop will run Eaj times, where Eaj is number of adjacent edges to current vertex.
while(currentVertex.hasAdjacentVertices)
graphTraversal.add(adjacentVertex);
graphTraversal.remove(currentVertex);
La complexité temporelle est la suivante:
V * (O(1) + O(Eaj) + O(1))
V + V * Eaj + V
2V + E(total number of edges in graph)
V + E
J'ai essayé de simplifier le code et le calcul de complexité, mais si vous avez des questions, faites-le moi savoir.
En considérant le graphique suivant, nous voyons comment la complexité temporelle est O (| V | + | E |) mais pas O (V * E).
Liste d'adjacence
V E
v0:{v1,v2}
v1:{v3}
v2:{v3}
v3:{}
Fonctionnement du fonctionnement de BFS pas à pas
Étape 1:
Listes d'adjacence:
V E
v0: {v1,v2} mark, enqueue v0
v1: {v3}
v2: {v3}
v3: {}
Étape 2:
Listes d'adjacence:
V E
v0: {v1,v2} dequeue v0;mark, enqueue v1,v2
v1: {v3}
v2: {v3}
v3: {}
Étape 3:
Listes d'adjacence:
V E
v0: {v1,v2}
v1: {v3} dequeue v1; mark,enqueue v3
v2: {v3}
v3: {}
Étape 4:
Listes d'adjacence:
V E
v0: {v1,v2}
v1: {v3}
v2: {v3} dequeue v2, check its adjacency list (v3 already marked)
v3: {}
Étape 5:
Listes d'adjacence:
V E
v0: {v1,v2}
v1: {v3}
v2: {v3}
v3: {} dequeue v3; check its adjacency list
Étape 6:
Listes d'adjacence:
V E
v0: {v1,v2} |E0|=2
v1: {v3} |E1|=1
v2: {v3} |E2|=1
v3: {} |E3|=0
Nombre total d'étapes:
|V| + |E0| + |E1| + |E2| +|E3| == |V|+|E|
4 + 2 + 1 + 1 + 0 == 4 + 4
8 == 8
Supposons une représentation de liste d'adjacence, V est le nombre de sommets, E le nombre d'arêtes.
Chaque sommet est mis en file d'attente et retiré de la file d'attente au plus une fois.
La recherche de tous les sommets adjacents prend O(|E|) temps, puisque la somme des longueurs des listes d'adjacence est | E |.
D'où la complexité temporelle de BFS donne un O(|V|+|E|) complexité temporelle.
L'exécution d'une opération O(1)
L
fois entraîne une complexité O(L)
. Ainsi, supprimer et ajouter un sommet de/à la file d'attente est O (1), mais lorsque vous faites cela pour les sommets V
, vous obtenez la complexité O(V)
. Par conséquent, O(V) + O(E) = O(V+E)
Les autres réponses ici font un excellent travail montrant comment BFS fonctionne et comment l'analyser. Je voulais revoir votre analyse mathématique originale pour montrer où, spécifiquement, votre raisonnement vous donne une estimation inférieure à la vraie valeur.
Votre analyse se présente comme suit:
Vous êtes très près d'avoir la bonne estimation ici. La question est de savoir d'où vient le terme V manquant. Le problème ici est que, étrangement, vous ne pouvez pas dire que O(V) · O (E/V) = O (E).
Vous avez tout à fait raison de dire que le travail moyen par nœud est O (E/V). Cela signifie que le travail total effectué asympotiquement est délimité par le haut par un multiple de E/V. Si nous pensons à ce que BFS fait réellement, le travail effectué par nœud ressemble probablement plus à c1 + c2E/V, car il y a une quantité de base de travail effectuée par nœud (configuration de boucles, vérification des conditions de base, etc.), ce qui est pris en compte par le c1 terme, plus une certaine quantité de travail proportionnelle au nombre de bords visités (E/V, fois le travail effectué par bord). Si nous multiplions cela par V, nous obtenons que
V · (c1 + c2E/V)
= c1V + c2E
= Θ (V + E)
Ce qui se passe ici, c'est que ces jolis termes d'ordre inférieur que big-O nous laisse si facilement ignorer sont en fait importants ici, donc nous ne pouvons pas facilement les éliminer. C'est donc mathématiquement du moins ce qui se passe.
Ce qui se passe en fait ici, c'est que peu importe le nombre d'arêtes dans le graphique, il y a une quantité de travail de base que vous devez faire pour chaque nœud indépendamment de ces arêtes. C'est la configuration pour faire des choses comme exécuter les instructions if de base, configurer des variables locales, etc.