Mastering Graph Algorithms: BFS, DFS, Dijkstra, Floyd-Warshall, and Bellman-Ford in Java

4.71K 0 0 0 0

Chapter 1: Breadth-First Search (BFS) in Java

Breadth-First Search (BFS) is a graph traversal algorithm used to explore nodes and edges of a graph systematically. It starts from a source node, explores all its neighbors first, and then proceeds to their unvisited neighbors — effectively exploring the graph level by level.

BFS uses a queue data structure to maintain the order of nodes to be visited. Once a node is visited, it is marked so that it isn't visited again. This ensures that BFS does not get stuck in a loop in cyclic graphs.

In undirected graphs, BFS can be used to find connected components or to check if a graph is bipartite. In directed graphs, it can be used to determine reachability from a given node. It works well for unweighted graphs when you need to find the shortest path from the source to all other nodes.


2. BFS Coding (with 2 Examples)

a. Syllabus-Oriented Example (Graph Traversal)

import java.util.*;

 

public class BFSExample {

    public static void bfs(int start, List<List<Integer>> graph) {

        boolean[] visited = new boolean[graph.size()];

        Queue<Integer> queue = new LinkedList<>();

 

        visited[start] = true;

        queue.offer(start);

 

        while (!queue.isEmpty()) {

            int node = queue.poll();

            System.out.print(node + " ");

 

            for (int neighbor : graph.get(node)) {

                if (!visited[neighbor]) {

                    visited[neighbor] = true;

                    queue.offer(neighbor);

                }

            }

        }

    }

 

    public static void main(String[] args) {

        int V = 5;

        List<List<Integer>> graph = new ArrayList<>();

        for (int i = 0; i < V; i++) graph.add(new ArrayList<>());

 

        // Creating a sample graph

        graph.get(0).add(1);

        graph.get(0).add(2);

        graph.get(1).add(3);

        graph.get(2).add(4);

 

        bfs(0, graph);  // Output: 0 1 2 3 4

    }

}


b. Real-World Example (Shortest path in social network)

import java.util.*;

 

public class SocialNetworkBFS {

    public static int shortestConnection(String source, String target, Map<String, List<String>> network) {

        Set<String> visited = new HashSet<>();

        Queue<String> queue = new LinkedList<>();

        Map<String, Integer> distance = new HashMap<>();

 

        queue.offer(source);

        visited.add(source);

        distance.put(source, 0);

 

        while (!queue.isEmpty()) {

            String person = queue.poll();

            if (person.equals(target)) return distance.get(person);

 

            for (String friend : network.getOrDefault(person, new ArrayList<>())) {

                if (!visited.contains(friend)) {

                    visited.add(friend);

                    distance.put(friend, distance.get(person) + 1);

                    queue.offer(friend);

                }

            }

        }

        return -1;  // Not connected

    }

 

    public static void main(String[] args) {

        Map<String, List<String>> network = new HashMap<>();

        network.put("Alice", Arrays.asList("Bob", "Carol"));

        network.put("Bob", Arrays.asList("Dave"));

        network.put("Carol", Arrays.asList("Eve"));

        network.put("Dave", Arrays.asList("Frank"));

 

        System.out.println("Shortest path: " + shortestConnection("Alice", "Frank", network));  // Output: 3

    }

}


3. Pros of BFS

  • Guarantees the shortest path in unweighted graphs.
  • Easy to implement using queues and adjacency lists.
  • Works well for level-order traversal in trees and graphs.
  • Can detect cycles in undirected graphs.
  • Can be modified to find connected components, bipartiteness, and more.

4. Cons of BFS

  • Consumes a lot of memory in wide graphs due to queue size.
  • Not suitable for graphs with weighted edges.
  • Can be slower than DFS in deep but narrow graphs.
  • Needs extra space for visited arrays and queues.
  • Cannot detect back edges or perform topological sorting directly.

5. How Is BFS Better Than Other Traversal Techniques?

  • Compared to DFS: BFS guarantees shortest paths in unweighted graphs, which DFS doesn’t.
  • Compared to Dijkstra: BFS is faster and simpler for unweighted graphs; Dijkstra is overkill in such cases.
  • Compared to Bellman-Ford/Floyd-Warshall: BFS is significantly less complex and better suited for basic reachability or path-finding in simple graphs.

6. Best-Case Scenarios to Use BFS

  • Finding the shortest path in unweighted graphs.
  • Crawling websites where breadth-first logic is preferred.
  • Searching in a maze or grid-based game.
  • Exploring social networks (e.g., mutual friend recommendations).
  • Level-order traversal of a binary tree.

7. Applications of BFS

  • GPS systems for route planning (if graph is unweighted).
  • Social networks to find degrees of separation.
  • Peer-to-peer network discovery (like torrent clients).
  • AI in games for exploring all reachable states.
  • Finding connected components in a graph.

8. Worst-Case Scenarios to Use BFS


  • Graphs with millions of nodes and high branching factors — memory usage can explode.
  • Weighted graphs — it will give incorrect results for shortest paths.
  • Situations where depth-first exploration is required (e.g., backtracking problems like Sudoku).
  • If the target node is deep and isolated, BFS wastes time visiting all levels before it.
  • When graph structure requires cycle detection in directed graphs — DFS is better suited.

Back

FAQs


1. What is the main difference between BFS and DFS in terms of traversal strategy and use cases?

BFS (Breadth-First Search) explores a graph level-by-level using a queue, which makes it ideal for finding the shortest path in unweighted graphs or for level-order traversal. On the other hand, DFS (Depth-First Search) dives deep into each branch before backtracking using recursion or a stack, which is better suited for problems like cycle detection, topological sorting, or solving mazes. BFS is more memory-intensive for wide graphs, while DFS may hit stack limits in deep graphs.

2. Which algorithm is best suited for graphs containing negative edge weights, and why is Dijkstra not suitable in such cases?

Bellman-Ford is the preferred choice for graphs with negative edge weights because it relaxes all edges up to V-1 times and can detect negative weight cycles. Dijkstra's Algorithm assumes that once a node's shortest path is found, it won't change—which fails when a negative edge later offers a better path. Hence, Dijkstra produces incorrect results in graphs with negative weights.

3. Can we use BFS or DFS to find the shortest path in weighted graphs?

No, BFS and DFS do not work correctly for weighted graphs when computing the shortest path unless all edges have the same weight (in which case BFS can be used). In weighted graphs, algorithms like Dijkstra or Bellman-Ford should be used to account for varying edge weights.

4. Is Dijkstra’s Algorithm always faster than Bellman-Ford?

Yes, typically. Dijkstra’s Algorithm (especially with a min-priority queue) has a time complexity of O((V + E) log V) using a binary heap, which is significantly faster than Bellman-Ford’s O(V × E) in dense graphs. 
However, Dijkstra is restricted to graphs with non-negative weights, while Bellman-Ford is more versatile in terms of weight handling but slower in execution.

5. Does Floyd-Warshall detect negative cycles, and how does it differ from Bellman-Ford in that regard?

Floyd-Warshall does not explicitly detect negative cycles but can indirectly do so. If any diagonal element dist[i][i] becomes negative, it indicates a negative weight cycle. In contrast, Bellman-Ford directly checks for cycle presence after the V-1 iterations, making it more transparent and suitable for single-source shortest path detection with cycle checking.

6. How does memory usage differ between adjacency matrix and adjacency list in graph algorithms?

An adjacency matrix uses O(V²) space regardless of edge count, which is inefficient for sparse graphs. Adjacency lists use O(V + E) space, making them more space-efficient and faster for traversals in sparse graphs. Most graph algorithms like BFS, DFS, Dijkstra, and Bellman-Ford perform better with adjacency lists in large, sparse graphs.

7. In what scenarios would Floyd-Warshall be preferred over repeated Dijkstra's executions for all-pairs shortest paths?

Floyd-Warshall is preferred when the graph is dense and edge weights may be negative (but no negative cycles), as it provides a clean O(V³) solution for all-pairs shortest paths. Running Dijkstra’s Algorithm V times yields O(V × (V + E) log V), which may be better in sparse graphs but complex to implement compared to Floyd-Warshall’s simplicity.

8. Are graph algorithms like BFS or DFS guaranteed to work correctly in disconnected graphs?

No, by default, BFS or DFS starting from a single source will only explore the connected component of that source. To process the entire graph, especially for tasks like component counting or cycle detection, you must run BFS or DFS on each unvisited node to ensure all disconnected components are covered.

9. How can graph algorithms be optimized for real-time systems like GPS or social networks?

For real-time systems, efficiency and response time are critical. BFS or optimized Dijkstra (with Fibonacci or binary heaps) is ideal for fast queries in routing. Systems often precompute paths using Floyd-Warshall or store distance matrices. In large-scale applications like social networks, graph traversal is often distributed or approximated using heuristics like A*.

10. Do graph algorithms work equally well for both directed and undirected graphs?

Most graph algorithms work for both, but implementation varies. For DFS and BFS, directionality affects traversal order. For Dijkstra and Bellman-Ford, directed edges must be processed correctly for accurate pathfinding. Special attention must be paid in cycle detection, SCCs (Strongly Connected Components), and topological sorting, which only apply to directed graphs.