Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Breadth-First Search (BFS) is a fundamental algorithm for traversing or searching tree or graph data structures. It starts at a chosen node (often the root in the case of a tree) and explores all of the neighbor nodes at the present depth before moving on to the nodes at the next depth level. BFS is particularly useful for finding the shortest path on unweighted graphs.
In this tutorial, we’ll explore the implementation of the BFS algorithm in Kotlin.
As we’ve touched upon, the BFS algorithm begins its traversal at a specific node, often the root in tree structures, and then explores all neighboring nodes at the current depth before advancing to nodes at the next depth level.
This level-wise traversal approach ensures that all nodes are reached efficiently, making it ideal for finding the shortest paths in graphs where all edges are of equal weight. The simple principle behind BFS, processing nodes level by level, allows for thoroughly exploring graphs, emphasizing its foundational role in computer science and its applicability across various programming languages.
Now that we’ve covered the theory, we’ll implement the BFS algorithm in Kotlin.
First, we need to define a graph. We’ll use an adjacency list as it’s a common and efficient way to represent graphs in Kotlin. Additionally, to provide a concrete example, we’ll initialize our graph with some nodes and edges to illustrate how BFS works in practice:
val graph = mutableMapOf<Int, List<Int>>(
1 to listOf(2, 3, 4),
2 to listOf(5, 6),
3 to listOf(),
4 to listOf(7, 8),
5 to listOf(),
6 to listOf(),
7 to listOf(),
8 to listOf()
)
To better understand the graph structure we’ll be traversing, let’s consider its visual representation:
1
/ | \
2 3 4
/ \ / \
5 6 7 8
This diagram helps us visualize the BFS traversal starting from the root node of “1”.
Let’s implement bfs() and relate it line by line to the theory we studied earlier:
fun bfs(graph: Map<Int, List<Int>>, start: Int): Set<Int> {
val visited = mutableSetOf<Int>()
val queue = ArrayDeque<Int>()
queue.add(start)
while (queue.isNotEmpty()) {
val vertex = queue.removeFirst()
if (vertex !in visited) {
visited.add(vertex)
graph[vertex]?.let { neighbors -> queue.addAll(neighbors.filterNot { it in visited }) }
}
}
return visited
}
The code snippet above provides a practical implementation of the BFS algorithm, translating the theoretical concepts we’ve discussed into actionable Kotlin code. At its core, this implementation leverages a queue to explore nodes level by level, ensuring a systematic visitation pattern that adheres to the BFS paradigm.
The process begins by marking the start node as visited, then iteratively explores each node’s neighbors, traversing the graph in the broadest manner possible. This methodical approach guarantees that all nodes reachable from the starting point are visited, one level at a time.
Having observed the BFS algorithm in action, let’s delve into the specifics of our implementation:
Finally, to ensure our BFS functions as expected, we need to test our implementation. Let’s validate that our code is correct by writing a JUnit test. Using the graph we first defined, we’ll traverse it with our BFS implementation and assert the order in which we’ve visited the nodes:
@Test
fun `test BFS traversal order`() {
val graph = mapOf(
1 to listOf(2, 3, 4),
2 to listOf(5, 6),
3 to listOf(),
4 to listOf(7, 8),
5 to listOf(),
6 to listOf(),
7 to listOf(),
8 to listOf()
)
val traversalOrder = bfs(graph, 1)
val levelOne = listOf(traversalOrder.first())
assertThat(levelOne).containsExactly(1)
val levelTwo = traversalOrder.drop(1).take(3)
assertThat(levelTwo).containsExactlyInAnyOrder(2, 3, 4)
val levelThree = traversalOrder.drop(4)
assertThat(levelThree).containsExactlyInAnyOrder(5, 6, 7, 8)
}
This test proves that our BFS implementation correctly traverses the graph in the correct order from the root node. After invoking our bfs() function to obtain the traversal order, we slice the traversalOrder list to isolate each level of the graph. Finally, using assertions, we check that the nodes for each level are as expected, emphasizing the algorithm’s breadth-first approach.
Implementing the BFS algorithm in Kotlin provides a great way to explore graphs and trees efficiently. By leveraging Kotlin’s concise syntax and powerful standard library, developers can implement and apply BFS to solve a wide range of problems in both academic and real-world applications.