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

4.31K 0 0 0 0

Overview



Introduction to Graph Algorithms

Graphs are a fundamental data structure used to represent networks of interconnected data. Whether modeling social networks, road maps, or the web, graphs play a pivotal role in modern computing. Graphs consist of nodes (or vertices) and edges (connections). Graph algorithms enable us to traverse, search, and optimize paths within these networks.

In this tutorial, we’ll explore five essential graph algorithms: Breadth-First Search (BFS), Depth-First Search (DFS), Dijkstra’s Algorithm, Floyd-Warshall Algorithm, and Bellman-Ford Algorithm — all with Java examples. These algorithms serve a wide range of applications from pathfinding in games to routing in GPS systems.


1. Breadth-First Search (BFS)

Description:
BFS explores the graph layer by layer, visiting all nodes at the present depth before moving to the next.

Java Code:

import java.util.*;

 

public class BFS {

    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.add(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.add(neighbor);

                }

            }

        }

    }

}

Time Complexity: O(V + E)
Space Complexity: O(V)


2. Depth-First Search (DFS)

Description:
DFS explores as far down a branch as possible before backtracking.

Java Code:

public class DFS {

    public static void dfs(int node, List<List<Integer>> graph, boolean[] visited) {

        visited[node] = true;

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

       

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

            if (!visited[neighbor]) {

                dfs(neighbor, graph, visited);

            }

        }

    }

}

Time Complexity: O(V + E)
Space Complexity: O(V) (due to recursion stack)


3. Dijkstra’s Algorithm

Description:
Used for finding the shortest path from a source node to all other nodes in a graph with non-negative weights.

Java Code:

import java.util.*;

 

public class Dijkstra {

    static class Node implements Comparable<Node> {

        int vertex, weight;

        Node(int v, int w) { vertex = v; weight = w; }

        public int compareTo(Node n) { return this.weight - n.weight; }

    }

 

    public static void dijkstra(int source, List<List<Node>> graph, int V) {

        int[] dist = new int[V];

        Arrays.fill(dist, Integer.MAX_VALUE);

        dist[source] = 0;

 

        PriorityQueue<Node> pq = new PriorityQueue<>();

        pq.add(new Node(source, 0));

 

        while (!pq.isEmpty()) {

            Node curr = pq.poll();

            for (Node neighbor : graph.get(curr.vertex)) {

                if (dist[curr.vertex] + neighbor.weight < dist[neighbor.vertex]) {

                    dist[neighbor.vertex] = dist[curr.vertex] + neighbor.weight;

                    pq.add(new Node(neighbor.vertex, dist[neighbor.vertex]));

                }

            }

        }

 

        System.out.println("Distances: " + Arrays.toString(dist));

    }

}

Time Complexity: O((V + E) log V)
Space Complexity: O(V)


4. Floyd-Warshall Algorithm

Description:
All-pairs shortest paths algorithm that works with both positive and negative edge weights (no negative cycles).

Java Code:

public class FloydWarshall {

    public static void floydWarshall(int[][] graph, int V) {

        int[][] dist = new int[V][V];

       

        for (int i = 0; i < V; i++)

            dist[i] = Arrays.copyOf(graph[i], V);

       

        for (int k = 0; k < V; k++) {

            for (int i = 0; i < V; i++) {

                for (int j = 0; j < V; j++) {

                    if (dist[i][k] + dist[k][j] < dist[i][j])

                        dist[i][j] = dist[i][k] + dist[k][j];

                }

            }

        }

 

        for (int[] row : dist)

            System.out.println(Arrays.toString(row));

    }

}

Time Complexity: O(V³)
Space Complexity: O(V²)


5. Bellman-Ford Algorithm

Description:
Used to compute shortest paths from a single source, capable of handling negative weights and detecting negative cycles.

Java Code:

import java.util.*;

 

public class BellmanFord {

    static class Edge {

        int u, v, w;

        Edge(int u, int v, int w) { this.u = u; this.v = v; this.w = w; }

    }

 

    public static void bellmanFord(List<Edge> edges, int V, int source) {

        int[] dist = new int[V];

        Arrays.fill(dist, Integer.MAX_VALUE);

        dist[source] = 0;

 

        for (int i = 1; i < V; i++) {

            for (Edge e : edges) {

                if (dist[e.u] != Integer.MAX_VALUE && dist[e.u] + e.w < dist[e.v])

                    dist[e.v] = dist[e.u] + e.w;

            }

        }

 

        // Detect negative cycle

        for (Edge e : edges) {

            if (dist[e.u] != Integer.MAX_VALUE && dist[e.u] + e.w < dist[e.v]) {

                System.out.println("Graph contains negative weight cycle");

                return;

            }

        }

 

        System.out.println("Distances: " + Arrays.toString(dist));

    }

}

Time Complexity: O(V * E)
Space Complexity: O(V)


Comparison Table of Graph Algorithms

Algorithm

Time Complexity

Space Complexity

Handles Negative Weights

Use Case

BFS

O(V + E)

O(V)

No

Level-wise traversal

DFS

O(V + E)

O(V)

No

Topological sorting, cycle detect

Dijkstra

O((V + E) log V)

O(V)

No

Single-source shortest path

Floyd-Warshall

O(V³)

O(V²)

Yes (no cycles)

All-pairs shortest path

Bellman-Ford

O(V * E)

O(V)

Yes

Negative weight graphs


Real-World Applications of Graph Algorithms

  • BFS/DFS: Web crawlers, maze solvers, and social network analysis.
  • Dijkstra’s Algorithm: GPS and route-finding apps like Google Maps.
  • Floyd-Warshall: Network routing tables and communication cost matrices.
  • Bellman-Ford: Financial systems to detect arbitrage opportunities and network routing with potential negative weights.



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.

Posted on 10 Apr 2025, this text provides information on Graph Algorithms in Java. Please note that while accuracy is prioritized, the data presented might not be entirely correct or up-to-date. This information is offered for general knowledge and informational purposes only, and should not be considered as a substitute for professional advice.

Similar Tutorials


Trendlines

Advanced Excel Charts Tutorial: How to Create Prof...

Learn how to create professional charts in Excel with our advanced Excel charts tutorial. We'll show...

Productivity tips

Advanced Excel Functions: Tips and Tricks for Boos...

Are you tired of spending hours working on Excel spreadsheets, only to find yourself stuck on a prob...

Data aggregation

Apache Flume Tutorial: An Introduction to Log Coll...

Apache Flume is a powerful tool for collecting, aggregating, and moving large amounts of log data fr...