iOS Interview - Depth-First Search

April 13, 20245 min read#swift, #interview, #leetcode

Depth First Search (DFS) is a fundamental graph traversal algorithm that explores as far as possible along each branch before backtracking. It is commonly used for traversing or searching tree or graph data structures. In this blog post, we will dive into the concept of DFS and provide a code example in Swift to illustrate its implementation.

The main idea behind DFS is to start at a given node and explore as far as possible along each branch before backtracking. It follows a specific order of traversal, usually going deep into the graph before exploring sibling nodes.

The DFS algorithm can be implemented using either a recursive approach or an iterative approach with a stack data structure. In the recursive approach, the algorithm starts at a given node, marks it as visited, and then recursively visits all its unvisited neighbors. If all the neighbors have been visited, the algorithm backtracks to the previous node.

Implementing DFS in Swift

Let’s see how we can implement the DFS algorithm in Swift. We’ll use an adjacency list representation of a graph and perform a recursive DFS traversal.

class Graph {
    var adjacencyList: [Int: [Int]] = [:]

    func addEdge(_ source: Int, _ destination: Int) {
        adjacencyList[source, default: []].append(destination)
        adjacencyList[destination, default: []].append(source)
    }

    func dfs(_ startNode: Int) {
        var visited: Set<Int> = []
        dfsHelper(startNode, &visited)
    }

    private func dfsHelper(_ node: Int, _ visited: inout Set<Int>) {
        visited.insert(node)
        print("Visited node: \(node)")

        guard let neighbors = adjacencyList[node] else { return }
        for neighbor in neighbors {
            if !visited.contains(neighbor) {
                dfsHelper(neighbor, &visited)
            }
        }
    }
}

In this code, we define a Graph class that represents an undirected graph using an adjacency list. The addEdge method adds an edge between two nodes in the graph.

The dfs method is the entry point for the DFS traversal. It takes a starting node as input and initializes a visited set to keep track of visited nodes. It then calls the dfsHelper method to perform the recursive DFS traversal.

The dfsHelper method is a recursive helper function that takes a node and the visited set as parameters. It marks the current node as visited, prints the visited node, and then recursively visits all its unvisited neighbors.

Let’s see how we can use the DFS implementation:

let graph = Graph()
graph.addEdge(0, 1)
graph.addEdge(0, 2)
graph.addEdge(1, 2)
graph.addEdge(2, 0)
graph.addEdge(2, 3)
graph.addEdge(3, 3)

print("DFS traversal starting from node 2:")
graph.dfs(2)

In this example, we create a Graph instance and add some edges to it. We then call the dfs method, passing the starting node as an argument. The DFS traversal will start from node 2 and explore the graph, printing the visited nodes in the order of traversal.

The output will be:

DFS traversal starting from node 2:
Visited node: 2
Visited node: 0
Visited node: 1
Visited node: 3

Problems

Problem 1: Binary Tree Inorder Traversal

Leetcode: 94. Binary Tree Inorder Traversal

class TreeNode {
  var val: Int
  var left: TreeNode?
  var right: TreeNode?
  init(_ val: Int) {
    self.val = val
    self.left = nil
    self.right = nil
  }
}

func inorderTraversal(_ root: TreeNode?) -> [Int] {
  var result = [Int]()
  dfsHelper(root, &result)
  return result
}

private func dfsHelper(_ node: TreeNode?, _ result: inout Array<Int>) {
  // Base case: reach end of the tree
  guard let node else { return }

  dfsHelper(node.left, &result)
  result.append(node.val)
  dfsHelper(node.right, &result)
}

// Test Case 1
let root = TreeNode(1)
let node2 = TreeNode(2)
root.right = node2
let node3 = TreeNode(3)
node2.left = node3
print(inorderTraversal(root)) // [1, 3, 2]

Problem 2: Same Tree

Leetcode: 100. Same Tree

class TreeNode {
  var val: Int
  var left: TreeNode?
  var right: TreeNode?
  init(_ val: Int) {
    self.val = val
    self.left = nil
    self.right = nil
  }
}

func inorderTraversal(_ root: TreeNode?) -> [Int] {
  var result = [Int]()
  dfsHelper(root, &result)
  return result
}

private func dfsHelper(_ node: TreeNode?, _ result: inout Array<Int>) {
  // Base case: reach end of the tree
  guard let node else { return }

  dfsHelper(node.left, &result)
  result.append(node.val)
  dfsHelper(node.right, &result)
}

// Test Case 1
let root1 = TreeNode(1)
let node12 = TreeNode(2)
root1.right = node12
let node13 = TreeNode(3)
node12.left = node13
print(inorderTraversal(root1)) // [1, 3, 2]

// Test Case 2
let root2 = TreeNode(1)
let node22 = TreeNode(2)
let node23 = TreeNode(3)
let node24 = TreeNode(4)
let node25 = TreeNode(5)
let node26 = TreeNode(6)
let node27 = TreeNode(7)
root2.left = node22
root2.right = node23
node22.left = node24
node22.right = node25
node23.left = node26
node23.right = node27
print(inorderTraversal(root2)) //[4, 2, 5, 1, 6, 3, 7]

Further Readings:

Quick Drop logo

Profile picture

Personal blog by An Tran. I'm focusing on creating useful apps.
#Swift #Kotlin #Mobile #MachineLearning #Minimalist


© An Tran - 2024