# Python wide search (BFS) and deep search (DFS)

Posted by corrupshun on Thu, 17 Feb 2022 12:45:46 +0100

# BFS

## What is BFS

BFS is called breadth first search, also called breadth first search. Its alias is BFS, which belongs to a blind search method.
Dijkstra single source shortest path algorithm and Prim minimum spanning tree algorithm adopt the idea similar to width first search.
The purpose is to systematically expand and check all nodes in the diagram to find the results.
In other words, it does not consider the possible location of the result and searches the whole graph thoroughly until it finds the result.
Its idea is to start from a vertex V0 and traverse the wider area around it radially, so it is named.
Breadth first search is realized by using queue, which has the characteristics of first in first out FIFO(First Input First Output).

## Working process and principle of BPS

BFS operation steps are as follows:
1. Put the starting point into the queue;
2. Repeat the following 2 steps until the queue is empty:
1) Take out the queue header from the queue;
2) Find the points adjacent to this point that have not been traversed, mark them, and then put them all into the queue.

(1) Put the starting node 1 into the queue and mark it as traversed:

(2) Take node 1 of the queue header from the queue, find nodes 2 and 3 adjacent to node 1, mark them as traversed, and then put them into the queue.

(3) Take node 2 of the queue header from the queue and find nodes 1, 4 and 5 adjacent to node 2. Since node 1 has been traversed, it is excluded; It is traversed and marked as 4,5.

(4) Take the node 3 of the queue header from the queue and find the nodes 1, 6 and 7 adjacent to node 3. Since node 1 has been traversed, it is excluded; Mark 6 and 7 as traversed and put them into the queue.

(5) Take out the node 4 of the queue header from the queue, find out the nodes 2, 8 and 2 adjacent to the node 4, which belong to the traversed points and are excluded; Therefore, mark node 8 as traversed and put it into the queue.

(6) Take out the node 5 of the queue header from the queue, and find out the nodes 2, 8, 2 and 8 adjacent to the node 5, which belong to the traversed points, and do not do the next operation.

(7) Take out the node 6 of the queue header from the queue, find out that the nodes 3, 8, 9, 3 and 8 adjacent to the node 6 belong to the traversed points, and exclude them; Therefore, mark node 9 as traversed and put it into the queue.

(8) Take out the node 7 of the queue header from the queue, and find out that the nodes 3, 9, 3 and 9 adjacent to the node 7 belong to the traversed points, so do not do the next operation.

(9) Take out the node 8 of the queue header from the queue, and find out that the nodes 4, 5, 6, 4, 5 and 6 adjacent to the node 8 belong to the traversed points, so do not do the next operation.

(10) Take out the node 9 of the queue header from the queue, and find out the nodes 6, 7 and 6, 7 adjacent to the node 9, which belong to the traversed points and do not do the next operation.

(11) If the queue is empty, BFS traversal ends.

## BFS application scenario

• Two scenarios of BFS: sequence traversal and shortest path
• Find the shortest path from a certain point, but the path is not unique. The shortest path is the first one to meet the conditions
• Extensive search
• Words like "shortest" and "least" can be given priority

## Implementing BFS in python (tree and graph)

Trees are special graphs

### BFS diagram of python implementation

```import os
import sys

'''
python realization BFS Graph of
2022-01-15
'''

# Create a dictionary for storing graphs. A dictionary is equivalent to a mapping relationship and is read through key value pairs. It is applicable to only a small number of points in the graph. If there is too much data, it is more appropriate to use python class
graph = {
"A": ["B", "C"],
"B": ["A", "C", "D"],
"C": ["A", "B", "D", "E"],
"D": ["B", "C", "E", "F"],
"E": ["C", "D"],
"F": ["D"]
}

# Start BFS traversal
# Graph is the graph data and s is the starting point of the graph
def BFS(graph, s):
# Create an array as a queue to store points that have not been accessed
queue = []
# Put in starting point
queue.append(s)
# Create a collection to hold the points that have been read in, such as the starting point s
seen = set()
while (len(queue) > 0):
# Via queue Pop (0) reads the queue head, that is, each point
vertex = queue.pop(0)
nodes = graph[vertex]
# Weight judgment: cycle to judge whether adjacent points have been read
for w in nodes:
if w not in seen:
queue.append(w)
# Output traversal node
print(vertex, end= ' ')

BFS(graph, "A")
'''
# Operation results
A B C D E F
'''
```

### Shortest path of python implementation diagram

Improved from the above code

```import os
import sys

'''
python realization BFS Graph of
2022-01-15
'''

# Create a dictionary for storing graphs. A dictionary is equivalent to a mapping relationship and is read through key value pairs. It is applicable to only a small number of points in the graph. If there is too much data, it is more appropriate to use python class
graph = {
"A": ["B", "C"],
"B": ["A", "C", "D"],
"C": ["A", "B", "D", "E"],
"D": ["B", "C", "E", "F"],
"E": ["C", "D"],
"F": ["D"]
}

# Start BFS traversal
# Graph is the graph data and s is the starting point of the graph
def BFS(graph, s):
# Create an array as a queue to store points that have not been accessed
queue = []
# Put in starting point
queue.append(s)
# Create a collection to hold the points that have been read in, such as the starting point s
seen = set()
# Path restore, which corresponds the accessed point to its previous point to form a key value pair, and uses it to complete the output of the shortest path
parent = {s: None}
while (len(queue) > 0):
# Via queue Pop (0) reads the queue head, that is, each point
vertex = queue.pop(0)
nodes = graph[vertex]
# Weight judgment: cycle to judge whether adjacent points have been read
for w in nodes:
if w not in seen:
queue.append(w)
parent[w] = vertex
# Output traversal node
# print(vertex, end= ' ')
return parent

parent = BFS(graph, "A")
# Output the shortest path you want
# Set v as the end point
v = 'F'
# Loop find v
while v != None:
print(v, end=' ')
v = parent[v]
'''
# Operation results
F D B A
'''
```

### Implementing BFS tree in python

```# Define the nodes of a tree
class TreeNode:
def __init__(self, x):
self.val = x  # value
self.left = None  # Left node
self.right = None  # Right node

def level_order_tree(root):
if not root:  # A tree has only the root node and returns a null value
return root
queue = []
result = []
queue.append(root)  # Queue root node
result.append(root.val)  # Add root node value
while len(queue) > 0:
node = queue.pop(0)  # Take the head of the line
result.append(node.val)
# Judge left and right subtrees
if node.left:  # Add children from the left subtree to the queue
queue.append(node.left)
if node.right:  # Add children from the right subtree to the queue
queue.append(node.right)
return result

if __name__ == "__main__":
tree = TreeNode(4)
tree.left = TreeNode(9)
tree.right = TreeNode(0)
tree.left.left = TreeNode(5)
tree.left.right = TreeNode(1)

print(level_order_tree(tree))
# [4, 9, 0, 5, 1, 3, 2, 10, 7]

```

# DFS

## What is DFS

1. Depth first search (DFS) is an algorithm used to traverse or search trees or graphs. In brief, the process is to go deep into each possible branch path to the point where it can no longer go deep, and each node can only access it once.
2. When the edge of node v has been explored or the node does not meet the conditions when searching, the search will be traced back to the starting node of the edge where node v is found. The whole process repeats until all nodes are accessed.
3. For the same tree or graph, the DFS order is not necessarily unique.
4. Depth first search is realized by stack, which has the characteristics of first in and last out.

## DFS working process and principle

DFS working process and principle

Please don't forget that DFS order is not necessarily unique.

1. Take A as the starting node and access A. the adjacent points connected with A are B and C.
2. At this point, you can access B and C. which one you access depends on the order in which you press the stack.
3. Visit B, and the adjacent points connected to B are C and D.
4. Access D, and the adjacent points connected to D are E and F.
5. Access F, f has no adjacency point, and the nearest adjacency point D when the original path returns.
6. The adjacency point e connected with D has not been accessed. Access e, and the adjacency points connected with E are C and D.
7. Access C. the adjacency points of C include A, B, D and E. all have been visited. The original path returns to D. at this time, the adjacency points of d have been visited. The original path returns to B and then to A.

## DFS application scenario

• Seeking subsequence and subset
• Find all possible solutions and assemble the results layer by layer (not much different from the first one)

## Implementing DFS with python

### DFS diagram implemented in python

```import os
import sys

'''
python realization DFS Graph of
2022-01-15
'''

# Create a dictionary for storing graphs. A dictionary is equivalent to a mapping relationship and is read through key value pairs. It is applicable to only a small number of points in the graph. If there is too much data, it is more appropriate to use python class
graph = {
"A": ["B", "C"],
"B": ["A", "C", "D"],
"C": ["A", "B", "D", "E"],
"D": ["B", "C", "E", "F"],
"E": ["C", "D"],
"F": ["D"]
}

# Start DFS traversal
# Graph is the graph data and s is the starting point of the graph
def BFS(graph, s):
# Create an array as a stack to store points that have not been accessed
stack = []
# Put in starting point
stack.append(s)
# Create a collection to hold the points that have been read in, such as the starting point s
seen = set()
while (len(stack) > 0):
# Via queue Pop() reads the last on the stack
vertex = stack.pop()
nodes = graph[vertex]
# Weight judgment: cycle to judge whether adjacent points have been read
for w in nodes:
if w not in seen:
stack.append(w)
# Output traversal node
print(vertex, end= ' ')

BFS(graph, "A")
'''
# Operation results
A C E D F B
'''
```

### Implementing DFS tree in python

```# Create tree node class val: value, left: left subtree, right: right subtree
class TreeNode:
def __init__(self, value):
self.val = value
self.left = None
self.right = None

# Deep search traversal binary tree
def DFS(root):
# Create a storage result array
res = []
# Create a stack. The stack has the characteristics of last in first out. First put it into the root node
stack = [root]
# Loop stack
while stack:
# Take out the last element of the stack
currentNode = stack.pop()
# Get the value of the element
res.append(currentNode.val)
# Include right subtree
if currentNode.right:
# Add right subtree to stack
stack.append(currentNode.right)
# Whether to include left subtree
if currentNode.left:
# Add left subtree to stack
stack.append(currentNode.left)

# Return results
return res

if __name__ == "__main__":
tree = TreeNode(4)  # Add root node
tree.left = TreeNode(9)  # Add root left subtree
tree.right = TreeNode(0)  # Add root right subtree, and so on
tree.left.left = TreeNode(5)
tree.left.right = TreeNode(1)
tree.right.left = TreeNode(3)
tree.right.right = TreeNode(2)
tree.left.left.left = TreeNode(10)
tree.right.left.right = TreeNode(7)

print(DFS(tree))
# [4, 9, 5, 10, 1, 0, 3, 7, 2]
'''
Operation results:
[4, 9, 5, 10, 1, 0, 3, 7, 2]
'''
```

## When to use deep search and wide search

1. BFS is used to search the solution of the shortest path, which is more appropriate, such as the solution with the least steps and the solution with the least exchange times. Because the solution encountered in the process of BFS search must be the closest to the root, the solution encountered must be the optimal solution. At this time, the search algorithm can be terminated. It is not appropriate to use DFS at this time, because the solution searched by DFS is not necessarily the closest to the root. Only after the global search is completed can we find the nearest solution to the root from all solutions. (of course, the deficiency of DFS can be made up by using iterative deepening search ID-DFS).
2. In terms of space, DFS has advantages. DFS does not need to save the state in the search process, while BFS needs to save the searched state in the search process, and generally a queue is needed to record.
3. DFS is suitable for searching all solutions, because to search all solutions, it is useless to encounter the solution closest to the root in the BFS search process. You must traverse the entire search tree, and DFS search will also search all solutions. However, compared with DFS, DFS does not need to record too much information, so DFS is obviously more suitable for searching all solutions.

## python implements the traversal of the tree before, during and after the sequence

### Preorder traversal

```# Define the nodes of a tree
class TreeNode:
def __init__(self, x):
self.val = x  # value
self.left = None  # Left node
self.right = None  # Right node

# Preorder traversal. lis is the passed in list type parameter. In order to get a traversal result of list recursively
def preorder(root, result):
result.append(root.val)  # Add root node value
if root.left:
preorder(root.left, result)  # Recursive search for left subtree
if root.right:
preorder(root.right, result)  # Recursive search for right subtree
return result

if __name__ == "__main__":
tree = TreeNode(4)  # Add root node
tree.left = TreeNode(9)  # Add root left subtree
tree.right = TreeNode(0)  # Add root right subtree, and so on
tree.left.left = TreeNode(5)
tree.left.right = TreeNode(1)
tree.right.left = TreeNode(3)
tree.right.right = TreeNode(2)
tree.left.left.left = TreeNode(10)
tree.right.left.right = TreeNode(7)

print(preorder(tree, []))
# [4, 9, 5, 10, 1, 0, 3, 7, 2]
```

### Middle order traversal

```# Define the nodes of a tree
class TreeNode:
def __init__(self, x):
self.val = x  # value
self.left = None  # Left node
self.right = None  # Right node

# Medium order traversal. lis is the passed in list parameter. In order to get a traversal result of list recursively
def infix_order(root, result):
if root.left:
infix_order(root.left, result)
result.append(root.val)
if root.right:
infix_order(root.right, result)
return result

if __name__ == "__main__":
tree = TreeNode(4)  # Add root node
tree.left = TreeNode(9)  # Add root left subtree
tree.right = TreeNode(0)  # Add root right subtree, and so on
tree.left.left = TreeNode(5)
tree.left.right = TreeNode(1)
tree.right.left = TreeNode(3)
tree.right.right = TreeNode(2)
tree.left.left.left = TreeNode(10)
tree.right.left.right = TreeNode(7)

print(infix_order(tree, []))
# [10, 5, 9, 1, 4, 3, 7, 0, 2]
```

### Postorder traversal

```# Define the nodes of a tree
class TreeNode:
def __init__(self, x):
self.val = x  # value
self.left = None  # Left node
self.right = None  # Right node

# Post order traversal. Result is the passed in list type parameter. In order to get a traversal list result recursively
def epilogue(root, result):
if root.left:
epilogue(root.left, result)
if root.right:
epilogue(root.right, result)
result.append(root.val)
return result

if __name__ == "__main__":
tree = TreeNode(4)  # Add root node
tree.left = TreeNode(9)  # Add root left subtree
tree.right = TreeNode(0)  # Add root right subtree, and so on
tree.left.left = TreeNode(5)
tree.left.right = TreeNode(1)
tree.right.left = TreeNode(3)
tree.right.right = TreeNode(2)
tree.left.left.left = TreeNode(10)
tree.right.left.right = TreeNode(7)

print(epilogue(tree, []))
# [10, 5, 1, 9, 7, 3, 2, 0, 4]
```

Topics: Python