算法-03 | 深度优先DFS| 广度优先BFS | 剪枝优化
1. 搜索算法
在树(图/状态集)中寻找特定节点
深度优先搜索算法和广度优先搜索算法都是基于“图”这种数据结构。
图上的搜索算法就是,在图中找出从一个顶点出发,到另一个顶点的路径。图上的搜索算法有深度优先、广度优先搜索算法,和A*A∗、IDA*IDA∗ 等启发式搜索算法。
广度优先搜索和深度优先搜索是图上的两种最常用、最基本的搜索算法,仅适用于状态空间不大的搜索。它们比A*A∗、IDA*IDA∗ 等启发式搜索算法要简单粗暴,没有什么优化,所以也叫作暴力搜索算法。
广度优先搜索,采用地毯式层层推进,从起始顶点开始,依次往外遍历。广度优先搜索需要借助队列来实现,遍历得到的路径就是起始顶点到终止顶点的最短路径。
深度优先搜索,采用回溯思想,适合用递归或栈来实现。遍历得到的路径并不是最短路径。
深度优先和广度优先搜索的时间复杂度都是 O(E),空间复杂度都是 O(V)。其中E代表边,O代表顶点。
搜索 -- 遍历
- 每个节点都要访问一次
- 每个节点仅仅要访问一次
- 对于节点的访问顺序不限
- 深度优先:depth first search
- 广度优先:breadth first search
还有是按照优先级优先进行搜索,(可按现实场景进行定义,更适合于现实中很多的业务场景。这种算法称为 启发式搜索)
2. 深度优先搜索
“走迷宫”。假设你站在迷宫的某个岔路口,然后想找到出口。你随意选择一个岔路口来走,走着走着发现走不通的时候,你就回退到上一个岔路口,重新选择一条路继续走,直到最终找到出口。这种走法就是一种深度优先搜索策略。实线箭头表示遍历,虚线箭头表示回退。但深度优先搜索最先找出来的路径,并不是顶点 s 到顶点 t 的最短路径。深度优先搜索用的是回溯思想,回溯思想非常适合用递归来实现。
黑色表起始点,红色表终点。按照左下右上的方向顺序走,即,如果左边可以走,我们先走左边。然后递归下去,没达到终点或者走不通了,回溯上一个位置......
首先往左走,走不通了就回溯到上一步(左右都走过了再回溯)、上一步到起点,按左下右上的顺序走。
深度优先搜索 Depth-First-Search
遍历顺序 : 用栈
DFS python代码模板
DFS 代码 - 递归写法 visited = set() def dfs(node, visited): if node in visited: # terminator # already visited return visited.add(node) # process current node here. ... for next_node in node.children(): if not next_node in visited: dfs(next_node, visited) DFS 代码 - 非递归写法 def DFS(self, tree): if tree.root is None: return [] visited, stack = [], [tree.root] while stack: node = stack.pop() visited.add(node) process (node) nodes = generate_related_nodes(node) stack.push(nodes) # other processing work ...
3. 广度优先搜索 Breadth-First-Search - BFS
广度优先搜索(Breadth-First-Search),简称 BFS。它是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索:
广度优先搜索较之深度优先搜索之不同在于,深度优先搜索旨在不管有多少条岔路,先一条路走到底,不成功就返回上一个路口然后就选择下一条岔路,而广度优先搜索旨在面临一个路口时,把所有的岔路口都记下来,然后选择其中一个进入,然后将它的分路情况记录下来,然后再返回来进入另外一个岔路,并重复这样的操作。
顺序遍历: 用队列
BFS的python代码模板:
BFS 代码 def BFS(graph, start, end): queue = [] queue.append([start]) visited.add(start) while queue: node = queue.pop() visited.add(node) process(node) nodes = generate_related_nodes(node) queue.push(nodes) # other processing work ... DFS 代码 - 递归写法 visited = set() def dfs(node, visited): visited.add(node) # process current node here. ... for next_node in node.children(): if not next_node in visited: dfs(next node, visited)
https://www.geeksforgeeks.org/difference-between-bfs-and-dfs/
4. DFS| BFS 与 Tree的遍历的关系
A Tree is typically traversed in two ways:
- Breadth First Traversal (Or Level Order Traversal)
- Depth First Traversals
- Inorder Traversal (Left-Root-Right)
- Preorder Traversal (Root-Left-Right)
- Postorder Traversal (Left-Right-Root)
BFS and DFSs of above Tree
Breadth First Traversal : 1 2 3 4 5
Depth First Traversals:
Preorder Traversal : 1 2 4 5 3
Inorder Traversal : 4 2 5 1 3
Postorder Traversal : 4 5 2 3 1
All four traversals require O(n) time as they visit every node exactly once.
here is difference in terms of extra space required.
- Extra Space required for Level Order Traversal is O(w) where w is maximum width of Binary Tree. In level order traversal, queue one by one stores nodes of different level.
- Extra Space required for Depth First Traversals is O(h) where h is maximum height of Binary Tree. In Depth First Traversals, stack (or function call stack) stores all ancestors of a node.
https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/
5. 高级搜索
初级搜索(傻搜,暴力搜索):
1. 朴素搜索
2. 优化方式:不重复(fibonacci)、剪枝(生成括号问题)
如斐波拉契问题;
所谓剪枝就是整个状态树这个分支没有必要的时候就把它剪掉,不进行搜索。
3. 搜索方向:
DFS: depth first search 深度优先搜索 (傻子搜索, 一路不回头直到撞了南墙)
BFS: breadth first search 广度优先搜索 (距离最近的)
双向搜索(从起点和终点分别做一个广度优先,然后在中间相遇,它的时间更快)、
启发式搜索也叫A*算法或优先级搜索(不是用栈或队列,而是用一个优先队列放里边,优先队列是按照这个结的优先级,有些结点更可能会达到我们需要的结果,先就把它从队列中拿出来进行搜索)
之所以分DFS和BFS是因为计算机数据结构里有一个先入后出的栈和 先入先出的队列;
双向BFS
Breadth First Search(BFS)
Breadth First Search Levels
Two-ended BFS 双向BFS
A和L各从两边扩散,直到两者相遇,A、L各自所走的路径和。
启发式搜索
启发式搜索 Heuristic Search(A*) 也叫智能搜索 或者 根据某项条件来不断地优化搜索的方向。
一边搜索一边思考 <--> 也叫思考型搜索 ;
通过优先级不断地去找要找的点,先用优先级高的拿出来搜索;
启发式搜索它也是基于BFS的。
A* 也是基于 BFS 代码 def BFS(graph, start, end): queue = [] queue.append([start]) visited.add(start) while queue: node = queue.pop() #queue每次都pop一个最近的元素, can we add more intelligence here ? 在pop node时多加一些智能的元素在里面,不再使用queue的傻子似的先入先出。可以使用优先队列。 visited.add(node)
process(node) nodes = generate_related_nodes(node) queue.push(nodes)
A* search的模板如下:
def AstarSearch(graph, start, end): pq = collections.priority_queue() # 优先级 —> 估价函数 pq.append([start]) visited.add(start) while pq: node = pq.pop() # can we add more intelligence here ? visited.add(node)
process(node) nodes = generate_related_nodes(node) unvisited = [node for node in nodes if node not in visited] pq.push(unvisited)
使用了PQ即priority queue;