Graph Traversal

Shortest Path Problem

If a robot needs to find the optimal path to a target location, it must solve the shortest path problem.

  • Unweighted graph

Breadth-First Search can be used to find the shortest path by traversing all nodes (full traversal).

  • Weighted graph

Dijkstra's algorithm can be used to find the shortest path between a starting node and all other nodes in a weighted graph.

A* (A star) combines the best features of Dijkstra with a greedy heuristic that guides the robot to the goal node of a weighted graph.

Complete Traversal of Unweighted Graph

In navigation, the preferred algorithm to completely traverse a graph is Breadth-First Search. BFS uses a queue, as apposed to DFS which uses a stack, therefor the first path to a target node that is found will be the shortest path.

graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

start = 'A'
goal = 'F'

queue = [start]
visited = set(start)
parent = {}

while queue:
    v = queue.pop(0)
    for u in graph[v]:
        if u not in visited:
            queue.append(u)
            visited.add(u)
            parent[u] = v

key = goal
path = []
while key in parent.keys():
    key = parent[key]
    path.insert(0, key)
path.append(goal)

print(f'Shortest path from {start} to {goal}: ', path)

Complete Traversal of Weighted Graph

Dijkstra's algorithm is similar to breadth-first search but uses a priority queue or heap instead of a normal queue so that it is always popping the lowest cost edge from the queue.

from heapq import heapify, heappush, heappop
from collections import defaultdict

graph = {
    'A': [(5, 'B'), (3, 'C')],
    'B': [(5, 'A'), (3, 'D'), (1, 'E')],
    'C': [(3, 'A'), (3, 'F')],
    'D': [(3, 'B')],
    'E': [(1, 'B'), (1, 'F')],
    'F': [(3, 'C'), (1, 'E')]
}

start = 'A'
goal = 'F'
queue = [(0, start)]
heapify(queue)

distances = defaultdict(lambda: float("inf"))
distances[start] = 0

visited = {start}
parent = {}

while queue:
    (currentdist, v) = heappop(queue)
    visited.add(v)
    for (cost_vu, u) in graph[v]:
        if u not in visited:
            newcost = distances[v] + cost_vu
            if newcost < distances[u]:
                distances[u] = newcost
                heappush(queue, (newcost, u))
                parent[u] = v

Partial Traversal

Dijkstra's algorithm is computationally expensive for large graphs. A* adds a heuristic estimate to prioritize the nodes that are most likely to lead to the shortest path - this results in better performance for large graphs.