Floyd-Warshall & A* Algorithm - CSU083 | Shoolini University

Floyd-Warshall & A* Algorithm

1. Prerequisites

Before understanding Floyd-Warshall and A* (A-Star) algorithms, you need to grasp the following:

2. What is it?

2.1 Floyd-Warshall Algorithm

The Floyd-Warshall algorithm is a dynamic programming approach to find the shortest paths between all pairs of nodes in a weighted graph.

2.2 A* Algorithm

A* (A-Star) is a heuristic-based pathfinding algorithm used to find the shortest path from a start node to a goal node efficiently.

3. Why does this algorithm exist?

3.1 Floyd-Warshall Applications

3.2 A* Algorithm Applications

4. When should you use it?

4.1 When to Use Floyd-Warshall

4.2 When to Use A*

5. How does it compare to alternatives?

5.1 Strengths & Weaknesses of Floyd-Warshall

5.2 Strengths & Weaknesses of A*

6. Basic Implementation

We will implement both Floyd-Warshall and A* algorithms in Python and perform a step-by-step dry run on a small input set.

7. Basic Implementation

7.1 Floyd-Warshall Algorithm

import sys

def floyd_warshall(graph):
    V = len(graph)
    dist = [row[:] for row in graph]  # Copy input graph

    for k in range(V):
        for i in range(V):
            for j in range(V):
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

    return dist

# Example graph (Adjacency matrix representation)
INF = sys.maxsize
graph = [
    [0, 3, INF, 5],
    [2, 0, INF, 4],
    [INF, 1, 0, INF],
    [INF, INF, 2, 0]
]

result = floyd_warshall(graph)
for row in result:
    print(row)

7.2 A* Algorithm

import heapq

def a_star(graph, start, goal, heuristic):
    open_list = []
    heapq.heappush(open_list, (0, start))
    came_from = {}
    g_score = {node: float('inf') for node in graph}
    g_score[start] = 0
    f_score = {node: float('inf') for node in graph}
    f_score[start] = heuristic[start]

    while open_list:
        _, current = heapq.heappop(open_list)

        if current == goal:
            path = []
            while current in came_from:
                path.append(current)
                current = came_from[current]
            return path[::-1] + [goal]

        for neighbor, cost in graph[current].items():
            tentative_g_score = g_score[current] + cost

            if tentative_g_score < g_score[neighbor]:
                came_from[neighbor] = current
                g_score[neighbor] = tentative_g_score
                f_score[neighbor] = g_score[neighbor] + heuristic[neighbor]
                heapq.heappush(open_list, (f_score[neighbor], neighbor))

    return None

# Example graph
graph = {
    'A': {'B': 1, 'C': 4},
    'B': {'A': 1, 'C': 2, 'D': 5},
    'C': {'A': 4, 'B': 2, 'D': 1},
    'D': {'B': 5, 'C': 1}
}

heuristic = {'A': 3, 'B': 2, 'C': 1, 'D': 0}  # Estimated cost to goal
path = a_star(graph, 'A', 'D', heuristic)
print("Path:", path)

8. Dry Run

8.1 Dry Run of Floyd-Warshall

Given the graph:

    0 → 3 → ∞ → 5
    2 → 0 → ∞ → 4
    ∞ → 1 → 0 → ∞
    ∞ → ∞ → 2 → 0

Step 1: Initialize distance matrix with given values.

    0   3   ∞   5
    2   0   ∞   4
    ∞   1   0   ∞
    ∞   ∞   2   0

Step 2: Use intermediate nodes to update distances.

Final distance matrix:

    0   3   7   5
    2   0   6   4
    3   1   0   5
    5   3   2   0

8.2 Dry Run of A*

Graph:

    A → B (1)
    A → C (4)
    B → C (2)
    B → D (5)
    C → D (1)

Start: A, Goal: D

9. Variable Tracking During Execution

9.1 Floyd-Warshall Variable Tracking

9.2 A* Variable Tracking

10. Time & Space Complexity Analysis (Big-O Mastery)

Understanding the time and space complexity of Floyd-Warshall and A* algorithms is crucial for optimizing their usage in real-world applications.

11. Worst-Case, Best-Case, and Average-Case Complexity

11.1 Floyd-Warshall Algorithm

11.2 A* Algorithm

12. Space Complexity Analysis

12.1 Floyd-Warshall Algorithm

12.2 A* Algorithm

13. Trade-offs Between Floyd-Warshall and A*

13.1 When to Use Floyd-Warshall?

13.2 When to Use A*?

13.3 Key Trade-offs

14. Optimizations & Variants (Making It Efficient)

Both Floyd-Warshall and A* algorithms can be optimized for better performance in real-world scenarios. Here, we explore common optimizations, variants, and different implementations.

15. Common Optimizations

15.1 Optimizing Floyd-Warshall

15.2 Optimizing A*

16. Variants of the Algorithms

16.1 Variants of Floyd-Warshall

16.2 Variants of A*

17. Iterative vs. Recursive Implementations

17.1 Floyd-Warshall: Iterative vs. Recursive

17.2 A*: Iterative vs. Recursive

17.3 Efficiency Comparison

18. Edge Cases & Failure Handling

Understanding edge cases and failure handling ensures the robustness of Floyd-Warshall and A* algorithms in different scenarios.

19. Common Pitfalls and Edge Cases

19.1 Floyd-Warshall Edge Cases

19.2 A* Algorithm Edge Cases

20. Test Cases to Verify Correctness

20.1 Test Cases for Floyd-Warshall

import sys

INF = sys.maxsize

def floyd_warshall(graph):
    V = len(graph)
    dist = [row[:] for row in graph]

    for k in range(V):
        for i in range(V):
            for j in range(V):
                if dist[i][k] != INF and dist[k][j] != INF:
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

    return dist

# Test 1: Small graph with positive weights
graph_1 = [
    [0, 3, INF, 5],
    [2, 0, INF, 4],
    [INF, 1, 0, INF],
    [INF, INF, 2, 0]
]
assert floyd_warshall(graph_1) == [
    [0, 3, 7, 5],
    [2, 0, 6, 4],
    [3, 1, 0, 5],
    [5, 3, 2, 0]
]

# Test 2: Graph with a negative weight cycle
graph_2 = [
    [0, 1, INF],
    [INF, 0, -2],
    [-1, INF, 0]
]
# This should be handled properly to detect the negative cycle.

20.2 Test Cases for A* Algorithm

import heapq

def a_star(graph, start, goal, heuristic):
    open_list = []
    heapq.heappush(open_list, (0, start))
    came_from = {}
    g_score = {node: float('inf') for node in graph}
    g_score[start] = 0
    f_score = {node: float('inf') for node in graph}
    f_score[start] = heuristic[start]

    while open_list:
        _, current = heapq.heappop(open_list)

        if current == goal:
            path = []
            while current in came_from:
                path.append(current)
                current = came_from[current]
            return path[::-1] + [goal]

        for neighbor, cost in graph[current].items():
            tentative_g_score = g_score[current] + cost

            if tentative_g_score < g_score[neighbor]:
                came_from[neighbor] = current
                g_score[neighbor] = tentative_g_score
                f_score[neighbor] = g_score[neighbor] + heuristic[neighbor]
                heapq.heappush(open_list, (f_score[neighbor], neighbor))

    return None

# Test 1: Simple Path
graph_3 = {
    'A': {'B': 1, 'C': 4},
    'B': {'A': 1, 'C': 2, 'D': 5},
    'C': {'A': 4, 'B': 2, 'D': 1},
    'D': {'B': 5, 'C': 1}
}

heuristic_3 = {'A': 3, 'B': 2, 'C': 1, 'D': 0}
assert a_star(graph_3, 'A', 'D', heuristic_3) == ['A', 'B', 'C', 'D']

# Test 2: No Path Exists
graph_4 = {
    'A': {'B': 1},
    'B': {'A': 1},
    'C': {}
}
assert a_star(graph_4, 'A', 'C', {'A': 2, 'B': 1, 'C': 0}) == None

21. Real-World Failure Scenarios

21.1 Floyd-Warshall Failures

21.2 A* Failures

By understanding these edge cases and failure scenarios, we can design more robust implementations of Floyd-Warshall and A* algorithms.

22. Real-World Applications & Industry Use Cases

Floyd-Warshall and A* algorithms are widely used across industries, including networking, gaming, robotics, and logistics. Their ability to compute shortest paths makes them essential in various domains.

23. Real-World Applications of These Algorithms

23.1 Floyd-Warshall Algorithm Applications

23.2 A* Algorithm Applications

24. Open-Source Implementations

25. Project: Practical Implementation of These Algorithms

We will create a Python-based script that utilizes Floyd-Warshall and A* algorithms for a transportation network.

25.1 Project: City Traffic Shortest Route Planner

This script will find the shortest paths between multiple cities (using Floyd-Warshall) and determine the best route from one city to another (using A*).

25.1.1 Implementation
import sys
import heapq

INF = sys.maxsize

# Sample city distance graph (adjacency matrix)
city_graph = [
    [0, 10, INF, INF, INF, 15],
    [10, 0, 25, INF, INF, 20],
    [INF, 25, 0, 30, INF, INF],
    [INF, INF, 30, 0, 5, INF],
    [INF, INF, INF, 5, 0, 10],
    [15, 20, INF, INF, 10, 0]
]

# Floyd-Warshall for all-pairs shortest paths
def floyd_warshall(graph):
    V = len(graph)
    dist = [row[:] for row in graph]

    for k in range(V):
        for i in range(V):
            for j in range(V):
                if dist[i][k] != INF and dist[k][j] != INF:
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

    return dist

# A* for single-source shortest path
def a_star(graph, start, goal, heuristic):
    open_list = []
    heapq.heappush(open_list, (0, start))
    came_from = {}
    g_score = {node: float('inf') for node in graph}
    g_score[start] = 0
    f_score = {node: float('inf') for node in graph}
    f_score[start] = heuristic[start]

    while open_list:
        _, current = heapq.heappop(open_list)

        if current == goal:
            path = []
            while current in came_from:
                path.append(current)
                current = came_from[current]
            return path[::-1] + [goal]

        for neighbor, cost in graph[current].items():
            tentative_g_score = g_score[current] + cost

            if tentative_g_score < g_score[neighbor]:
                came_from[neighbor] = current
                g_score[neighbor] = tentative_g_score
                f_score[neighbor] = g_score[neighbor] + heuristic[neighbor]
                heapq.heappush(open_list, (f_score[neighbor], neighbor))

    return None

# Example city names
city_names = ["A", "B", "C", "D", "E", "F"]

# Compute shortest paths using Floyd-Warshall
shortest_paths = floyd_warshall(city_graph)

print("All-Pairs Shortest Paths (Floyd-Warshall):")
for i in range(len(city_names)):
    for j in range(len(city_names)):
        print(f"Shortest distance from {city_names[i]} to {city_names[j]}: {shortest_paths[i][j]}")

# A* Heuristic (Estimated straight-line distance to goal)
heuristic = {'A': 15, 'B': 10, 'C': 5, 'D': 4, 'E': 2, 'F': 0}

# Convert adjacency matrix to adjacency list for A*
graph_dict = {
    'A': {'B': 10, 'F': 15},
    'B': {'A': 10, 'C': 25, 'F': 20},
    'C': {'B': 25, 'D': 30},
    'D': {'C': 30, 'E': 5},
    'E': {'D': 5, 'F': 10},
    'F': {'A': 15, 'B': 20, 'E': 10}
}

# Find shortest path using A*
source, destination = "A", "E"
path = a_star(graph_dict, source, destination, heuristic)
print(f"Shortest Path from {source} to {destination} using A*: {path}")
25.1.2 Expected Output
All-Pairs Shortest Paths (Floyd-Warshall):
Shortest distance from A to B: 10
Shortest distance from A to C: 35
Shortest distance from A to D: 65
Shortest distance from A to E: 70
Shortest distance from A to F: 15
...
Shortest Path from A to E using A*: ['A', 'F', 'E']
25.1.3 Summary

26. Competitive Programming & System Design Integration

Floyd-Warshall and A* algorithms are widely used in competitive programming and system design. Mastering them requires solving problems, integrating them into large-scale systems, and practicing implementation under time constraints.

27. Assignments

27.1 Solve at Least 10 Problems Using These Algorithms

Solve the following problems to reinforce understanding:

Floyd-Warshall Problems:
  1. Basic All-Pairs Shortest Path: Given a weighted graph, compute the shortest paths between all pairs of nodes.
  2. Detect Negative Cycle: Modify Floyd-Warshall to check if a negative weight cycle exists.
  3. Minimum Flight Hops: Find the minimum number of flights needed to travel between two airports.
  4. Warshall’s Algorithm (Transitive Closure): Modify Floyd-Warshall to determine if a path exists between any two nodes.
  5. Optimal Meeting Point: Given multiple locations, find the best meeting point to minimize travel cost.
A* Algorithm Problems:
  1. Grid Pathfinding: Find the shortest path in a 2D grid with obstacles.
  2. Dynamic Maze Solving: Solve a dynamically changing maze using A*.
  3. Warehouse Robot Navigation: Find the shortest path for a robot in a warehouse with obstacles.
  4. Game NPC Pathfinding: Implement an NPC that navigates a game map using A*.
  5. GPS Route Optimization: Use real-world map data to compute the fastest route between two points.

27.2 System Design Problem: Real-World Integration

Design a system that optimally routes delivery vehicles using Floyd-Warshall and A*.

Problem Statement:
Requirements:
Implementation Plan:
Key Trade-offs:

27.3 Practice Implementing Under Time Constraints

To simulate real-world coding challenges:

Timed Challenges:
Speed Optimization Exercises:
Competitive Coding Platforms: