{"id":"dsa-graphs-dfs","slug":"dfs","title":"Depth-first search and flood fill","text":"BFS spreads out in rings. **Depth-first search** (DFS) does the opposite: it follows one way as far as it goes, and only when it's stuck does it back up to the last vertex with a way it hasn't tried yet. Many questions don't care about the order, only about *which* vertices can be reached. Can I get from here to there? Which cells belong to this region? How many separate groups are there? DFS answers those, and its natural form is a recursive function:\n\n```python\ngraph = {1: [2, 3], 2: [4], 3: [4, 5], 4: [6], 5: [], 6: []}\norder = []\n\n\ndef dfs(v, seen):\n    seen.add(v)\n    order.append(v)\n    for w in graph[v]:\n        if w not in seen:\n            dfs(w, seen)\n\n\ndfs(1, set())\nprint(order)\n```\n\n```output\n[1, 2, 4, 6, 3, 5]\n```\n\nFrom 1 it goes to 2, from 2 to 4, from 4 to 6, and only then backs up all the way to 1 to try 3. BFS would visit 1, 2, 3, 4, 5, 6. Both find every vertex that can be reached from the start, in O(V + E).\n\n### Python's recursion limit\n\nEach call of `dfs` waits on Python's call stack until the vertices below it are done, so the recursion goes as deep as the longest path the search follows. Python stops at about 1000 waiting calls:\n\n```python\ndef dfs(graph, v, seen):\n    seen.add(v)\n    for w in graph[v]:\n        if w not in seen:\n            dfs(graph, w, seen)\n\n\nchain = {v: [v + 1] for v in range(5000)}\nchain[5000] = []\nseen = set()\ndfs(chain, 0, seen)\nprint(len(seen))\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 11, in <module>\n    dfs(chain, 0, seen)\n    ~~~^^^^^^^^^^^^^^^^\n  File \"main.py\", line 5, in dfs\n    dfs(graph, w, seen)\n    ~~~^^^^^^^^^^^^^^^^\n  File \"main.py\", line 5, in dfs\n    dfs(graph, w, seen)\n    ~~~^^^^^^^^^^^^^^^^\n  File \"main.py\", line 5, in dfs\n    dfs(graph, w, seen)\n    ~~~^^^^^^^^^^^^^^^^\n  [Previous line repeated 996 more times]\nRecursionError: maximum recursion depth exceeded\n```\n\nA big image, a maze with long corridors or a long chain of friends easily goes deeper than that. Raising the limit with `sys.setrecursionlimit` is a risky fix: the recursion goes deeper, but every waiting call still takes memory, and the browser's Python, with its smaller stack, can crash outright. Interviewers like to ask about this. The answer is to keep the waiting work in a list of your own, a stack, as in the module on trees: `append` pushes an item (O(1) amortized) and `pop` takes the one pushed last (O(1)). A list can hold millions of items.\n\n### Flood fill with a stack\n\nA paint bucket fills the region of same-colored pixels around the one you click. That's a search on the grid, where the neighbors of a cell are the cells above, below, left and right of it with the old color. Strings can't be changed, so the picture is a list of lists of characters:\n\n```python\ndef fill(picture, row, col, color):\n    old = picture[row][col]\n    if old == color:\n        return\n    rows, cols = len(picture), len(picture[0])\n    picture[row][col] = color\n    stack = [(row, col)]\n    while stack:\n        r, c = stack.pop()\n        for nr, nc in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:\n            if 0 <= nr < rows and 0 <= nc < cols and picture[nr][nc] == old:\n                picture[nr][nc] = color\n                stack.append((nr, nc))\n\n\npicture = [list(line) for line in [\"..##..\", \".#..#.\", \"##..#.\", \"..##..\"]]\nfill(picture, 1, 2, \"o\")\nfill(picture, 0, 0, \"~\")\nfor line in picture:\n    print(\"\".join(line))\n```\n\n```output\n~~##..\n~#oo#.\n##oo#.\n..##..\n```\n\nThe new color marks the cells that are done, so the picture is its own visited set. A cell is painted when it's pushed, so it's never pushed twice. The stack takes the cells in another order than the recursion would, but it reaches the same cells, and that's all a fill needs. Mind the first `if`: when the old color is already the new color, painted cells still look unpainted, and the loop would never end.\n\n### Counting groups\n\nThe pieces of a graph that are connected inside but not to each other are its **connected components**: circles of friends, islands on a map, separate computer networks. To find them all, try every vertex as a start. A vertex that has been seen belongs to a group found before. Any other vertex starts a new group, and a search from it collects the whole group:\n\n```python\ndef groups(n, edges):\n    graph = [[] for _ in range(n)]\n    for a, b in edges:\n        graph[a].append(b)\n        graph[b].append(a)\n    seen = [False] * n\n    result = []\n    for start in range(n):\n        if seen[start]:\n            continue\n        seen[start] = True\n        stack = [start]\n        group = []\n        while stack:\n            v = stack.pop()\n            group.append(v)\n            for w in graph[v]:\n                if not seen[w]:\n                    seen[w] = True\n                    stack.append(w)\n        result.append(sorted(group))\n    return result\n\n\nprint(groups(8, [(0, 3), (5, 1), (3, 6), (2, 7), (6, 0)]))\n```\n\n```output\n[[0, 3, 6], [1, 5], [2, 7], [4]]\n```\n\nThe key is that `seen` is shared by all the searches. Each vertex is pushed and popped once in the whole run, and each edge is looked at twice: O(V + E) in all. Searching from every vertex with a fresh `seen` each time finds each group again from every one of its vertices: O(V·(V + E)), billions of steps for 100,000 people in a few big groups.\n\n### What it costs\n\nDFS takes O(V + E) time like BFS, and O(V) memory for the visited marks and the stack: O(R·C) for both on a grid of R by C cells. Use BFS when the number of steps matters, and either one when only reaching matters. In Python, write it with your own stack whenever the input can be big.\n","task":"`reachable(graph, start)` counts the vertices that can be reached from `start`, with a stack of its own. Fill in the blanks: take the next vertex off the stack, and mark each new neighbor as seen before pushing it. The program should print:\n\n```\n10000 10\n3 4\n```\n","kind":"fill","seed":"def reachable(graph, start):\n    seen = {start}\n    stack = [start]\n    while stack:\n        v = [[stack.pop()]]\n        for w in graph[v]:\n            if w not in seen:\n                [[seen.add(w)]]\n                stack.append(w)\n    return len(seen)\n\n\nchain = {v: [v + 1] for v in range(9999)}\nchain[9999] = []\nprint(reachable(chain, 0), reachable(chain, 9990))\nloops = {1: [2], 2: [3, 1], 3: [1], 4: [1]}\nprint(reachable(loops, 1), reachable(loops, 4))\n","solution":"def reachable(graph, start):\n    seen = {start}\n    stack = [start]\n    while stack:\n        v = stack.pop()\n        for w in graph[v]:\n            if w not in seen:\n                seen.add(w)\n                stack.append(w)\n    return len(seen)\n\n\nchain = {v: [v + 1] for v in range(9999)}\nchain[9999] = []\nprint(reachable(chain, 0), reachable(chain, 9990))\nloops = {1: [2], 2: [3, 1], 3: [1], 4: [1]}\nprint(reachable(loops, 1), reachable(loops, 4))\n","hints":["A stack takes the item that was pushed last: `stack.pop()`.","Mark the neighbor before pushing it, so it's never pushed twice and the loops in the graph don't send the search around forever: `seen.add(w)`."],"tests":[{"name":"Output","stdin":"","expect":"10000 10\n3 4","hidden":false}],"require":[],"forbid":[],"more":[{"task":"This `count_islands` counts the islands of a map: `#` is land and `.` is water, and land cells that touch up, down, left or right belong to the same island. It gives the right answers, but `explore` calls itself once for every cell of an island, and the hidden tests have islands of thousands of cells, where Python stops with a `RecursionError`. Rewrite `explore` with a stack of your own, so that it works on any map.\n\nFor example, this program:\n\n```python\nprint(count_islands([\".#..##\", \".#...#\", \"...#..\", \"##.#..\"]))\nprint(count_islands([\"..#\", \".#.\", \"#..\"]))\n```\n\nprints (cells that only touch at a corner are separate islands):\n\n```\n4\n3\n```\n","kind":"code","seed":"def explore(grid, r, c, seen):\n    if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]):\n        return\n    if grid[r][c] != \"#\" or (r, c) in seen:\n        return\n    seen.add((r, c))\n    explore(grid, r - 1, c, seen)\n    explore(grid, r + 1, c, seen)\n    explore(grid, r, c - 1, seen)\n    explore(grid, r, c + 1, seen)\n\n\ndef count_islands(grid):\n    seen = set()\n    islands = 0\n    for r in range(len(grid)):\n        for c in range(len(grid[0])):\n            if grid[r][c] == \"#\" and (r, c) not in seen:\n                islands += 1\n                explore(grid, r, c, seen)\n    return islands\n\n\nif __name__ == \"__main__\":\n    print(count_islands([\".#..##\", \".#...#\", \"...#..\", \"##.#..\"]))\n    print(count_islands([\"..#\", \".#.\", \"#..\"]))\n","solution":"def explore(grid, r, c, seen):\n    rows, cols = len(grid), len(grid[0])\n    seen.add((r, c))\n    stack = [(r, c)]\n    while stack:\n        r, c = stack.pop()\n        for nr, nc in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:\n            if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == \"#\" and (nr, nc) not in seen:\n                seen.add((nr, nc))\n                stack.append((nr, nc))\n\n\ndef count_islands(grid):\n    seen = set()\n    islands = 0\n    for r in range(len(grid)):\n        for c in range(len(grid[0])):\n            if grid[r][c] == \"#\" and (r, c) not in seen:\n                islands += 1\n                explore(grid, r, c, seen)\n    return islands\n\n\nif __name__ == \"__main__\":\n    print(count_islands([\".#..##\", \".#...#\", \"...#..\", \"##.#..\"]))\n    print(count_islands([\"..#\", \".#.\", \"#..\"]))\n","hints":["`count_islands` can stay as it is. Only `explore` needs to change: it gets a land cell that hasn't been seen, and must mark its whole island as seen.","Mark the first cell as seen and push it on a list `stack`. While the stack isn't empty, pop a cell and look at its four neighbors.","A neighbor goes on the stack (and into `seen`) when it's inside the grid, is land and isn't in `seen` yet. That's the lesson's flood fill, with `seen` as the paint."],"tests":[{"name":"Test 1","stdin":"","call":"print(count_islands([\".#..##\", \".#...#\", \"...#..\", \"##.#..\"]))\nprint(count_islands([\"..#\", \".#.\", \"#..\"]))\n","expect":"4\n3","hidden":false},{"name":"count_islands([\"..\", \"..\", \"..\"])","stdin":"","call":"count_islands([\"..\", \"..\", \"..\"])\n","expect":"0","hidden":true,"returns":true},{"name":"count_islands([\"###\", \"#.#\", \"###\"])","stdin":"","call":"count_islands([\"###\", \"#.#\", \"###\"])\n","expect":"1","hidden":true,"returns":true},{"name":"count_islands([\"#.#.#\", \".....\", \"#####\"])","stdin":"","call":"count_islands([\"#.#.#\", \".....\", \"#####\"])\n","expect":"4","hidden":true,"returns":true},{"name":"count_islands([\"#\" * 4000])","stdin":"","call":"count_islands([\"#\" * 4000])\n","expect":"1","hidden":true,"returns":true},{"name":"count_islands([\"#\" * 70] * 70)","stdin":"","call":"count_islands([\"#\" * 70] * 70)\n","expect":"1","hidden":true,"returns":true},{"name":"count_islands([(\"#.\" * 60)[r % 2:][:100] for r in range(100)])","stdin":"","call":"count_islands([(\"#.\" * 60)[r % 2:][:100] for r in range(100)])\n","expect":"5000","hidden":true,"returns":true}],"require":[],"forbid":[{"pattern":"setrecursionlimit","message":"Don't raise the recursion limit: the browser's Python can crash. Rewrite `explore` with a stack of your own instead."}]},{"task":"A school has n students, numbered 0 to n - 1, and `friendships` lists pairs `(a, b)` of students who are friends. A **circle** of friends is a group of students connected by friendships, directly or through other students. Write `largest_circle(n, friendships)` that returns the number of students in the largest circle. A student without friends is a circle of one.\n\nThe hidden tests have 100,000 students, so share one visited list among all the searches: a fresh search from every student is O(n·(n + E)). And search with a stack of your own, since a circle can be a chain of thousands of students.\n\nFor example, this program:\n\n```python\nprint(largest_circle(7, [(0, 1), (2, 3), (1, 4), (5, 3), (4, 0)]))\nprint(largest_circle(3, []))\n```\n\nprints:\n\n```\n3\n1\n```\n","kind":"code","seed":"def largest_circle(n, friendships):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(largest_circle(7, [(0, 1), (2, 3), (1, 4), (5, 3), (4, 0)]))\n    print(largest_circle(3, []))\n","solution":"def largest_circle(n, friendships):\n    friends = [[] for _ in range(n)]\n    for a, b in friendships:\n        friends[a].append(b)\n        friends[b].append(a)\n    seen = [False] * n\n    largest = 0\n    for start in range(n):\n        if seen[start]:\n            continue\n        seen[start] = True\n        stack = [start]\n        size = 0\n        while stack:\n            student = stack.pop()\n            size += 1\n            for friend in friends[student]:\n                if not seen[friend]:\n                    seen[friend] = True\n                    stack.append(friend)\n        largest = max(largest, size)\n    return largest\n\n\nif __name__ == \"__main__\":\n    print(largest_circle(7, [(0, 1), (2, 3), (1, 4), (5, 3), (4, 0)]))\n    print(largest_circle(3, []))\n","hints":["Build the adjacency list first, with each friendship in both students' lists. Then do what the lesson's `groups` does, but count each group's students instead of listing them.","Loop over every student. One who has been seen belongs to a circle you've already counted: skip it. Any other starts a new search, with a stack, that counts the students it pops.","Keep one `seen` list for all the searches, so every student is counted once in the whole run, and keep the largest count."],"tests":[{"name":"Test 1","stdin":"","call":"print(largest_circle(7, [(0, 1), (2, 3), (1, 4), (5, 3), (4, 0)]))\nprint(largest_circle(3, []))\n","expect":"3\n1","hidden":false},{"name":"largest_circle(1, [])","stdin":"","call":"largest_circle(1, [])\n","expect":"1","hidden":true,"returns":true},{"name":"largest_circle(6, [(0, 1), (1, 2), (3, 4), (4, 5), (5, 3), (2, 0)])","stdin":"","call":"largest_circle(6, [(0, 1), (1, 2), (3, 4), (4, 5), (5, 3), (2, 0)])\n","expect":"3","hidden":true,"returns":true},{"name":"largest_circle(6, [(5, 4), (3, 4), (2, 3), (0, 1)])","stdin":"","call":"largest_circle(6, [(5, 4), (3, 4), (2, 3), (0, 1)])\n","expect":"4","hidden":true,"returns":true},{"name":"largest_circle(2000, [(i, i + 1) for i in range(1999)])","stdin":"","call":"largest_circle(2000, [(i, i + 1) for i in range(1999)])\n","expect":"2000","hidden":true,"returns":true},{"name":"largest_circle(100_000, [(i, i + 3) for i in range(99_997)])","stdin":"","call":"largest_circle(100_000, [(i, i + 3) for i in range(99_997)])\n","expect":"33334","hidden":true,"big":true,"returns":true},{"name":"largest_circle(100_000, [(i, i // 2) for i in range(1, 60_000)] + [(i, i + 1) for i in range(60_000, 99_999, 2)])","stdin":"","call":"largest_circle(100_000, [(i, i // 2) for i in range(1, 60_000)] + [(i, i + 1) for i in range(60_000, 99_999, 2)])\n","expect":"60000","hidden":true,"big":true,"returns":true}],"require":[],"forbid":[{"pattern":"setrecursionlimit","message":"Don't raise the recursion limit: the browser's Python can crash. Search with a stack of your own instead."}]}]}