Блог пользователя tyristik

Автор tyristik, история, 18 месяцев назад, По-английски

Imagine doing bfs on a 2D $$$n \times n$$$ grid. Your code may look something like this:

import collections
NOT_VISITED, VISITED = 0, 1
n = int(input())
grid = [[NOT_VISITED] * n for i in range(n)]
queue = collections.deque([(0, 0)])
while len(queue):
    x, y = queue.popleft()
    for nx, ny in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]:
        if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == NOT_VISITED:
            queue.append((nx, ny))
            grid[nx][ny] = VISITED
print(grid)

It works in $$$O(n^2)$$$, nothing interesting. But if we write it like this:

import collections
NOT_VISITED, VISITED = 0, 1
n = int(input())
grid = [[NOT_VISITED] * n for i in range(n)]
queue = collections.deque([(0, 0)])
while len(queue):
    x, y = queue.popleft()
    grid[x][y] = VISITED
    for nx, ny in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]:
        if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == NOT_VISITED:
            queue.append((nx, ny))
print(grid)

The code now works in $$$O\left(\dfrac{4^n}{\sqrt{n}}\right)$$$ :skull: :skull: :skull:

Can you figure out why?

  • Проголосовать: нравится
  • +29
  • Проголосовать: не нравится

»
18 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

In the second code, the cell is marked as visited, but its neighbors are not. This allows the same cell to be visited multiple times, leading to an exponential blow-up.

»
18 месяцев назад, скрыть # |
 
Проголосовать: нравится +1 Проголосовать: не нравится

In second version, you mark the current cell as visited before you check the neighbours.

That would mean that you might re visit the same cell multiple times cause the cell is being put back into queue for checking later even though it is marked as visited.

Also, can you explain how you got the time complexity for second code? i am still working on my skills and would be happy to know

  • »
    »
    18 месяцев назад, скрыть # ^ |
     
    Проголосовать: нравится +14 Проголосовать: не нравится

    The number of operations spent by the algorithm is $$$c \cdot \text{number of things added to the queue}$$$. You can try to convince yourself that the number of times cell $$$(x, y)$$$ gets added to the queue is the number of shortest paths from $$$(0, 0)$$$ to $$$(x, y)$$$, which is $$$\binom{x + y}{x}$$$.

    Therefore, the number of things added to the queue is $$$\sum_{x=0}^{n-1} \sum_{y = 0}^{n-1} \binom{x + y}{x}$$$. Applying hockey-stick once yields $$$\sum_{x=1}^n \binom{x + n - 1}{x} = \sum_{x=1}^n \binom{x + n - 1}{n - 1}$$$. Applying hockey-stick again yields $$$\binom{2n}{n} - 1$$$.

    One can use Stirling's approximation, $$$n! \sim \sqrt{2\pi n} (\frac{n}{e})^n$$$, to show that $$$O(\binom{2n}{n}) = O(\frac{4^n}{\sqrt{n}})$$$. I learned the last part just now thanks to Google AI lmao.

»
18 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Interesting, is there a similar issue for DFS using stack?

»
18 месяцев назад, скрыть # |
 
Проголосовать: нравится +2 Проголосовать: не нравится

tyristik The user is disabled.

»
18 месяцев назад, скрыть # |
 
Проголосовать: нравится -6 Проголосовать: не нравится

fix the second code by checking if the cell is visited before

 import collections
 NOT_VISITED, VISITED = 0, 1
 n = int(input())
 grid = [[NOT_VISITED] * n for i in range(n)]
 queue = collections.deque([(0, 0)])
 while len(queue):
     x, y = queue.popleft()
+    if grid[x][y] == VISITED:
+        continue
     grid[x][y] = VISITED
     for nx, ny in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]:
         if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == NOT_VISITED:
             queue.append((nx, ny))
 print(grid)