能够用 BFS 解决的问题,一定不要用 DFS 去做
因为用 Recursion 实现的 DFS 可能造成 StackOverflow
(NonRecursion 的 DFS 一来你不会写,二来面试官也看不懂)
1. Queue
Java常用的队列包括如下几种:
ArrayDeque:数组存储。实现Deque接口,而Deque是Queue接口的子接口,代表双端队列(double-ended queue)。
LinkedList:链表存储。实现List接口和Duque接口,不仅可做队列,还可以作为双端队列,或栈(stack)来使用。
C++中,使用<queue>中的queue模板类,模板需两个参数,元素类型和容器类型,元素类型必要,而容器类型可选,默认deque,可改用list(链表)类型。
Python中,使用collections.deque,双端队列
1 class MyQueue: 2 # 队列初始化 3 def __init__(self): 4 self.elements = [] # 用list存储队列元素 5 self.pointer = 0 # 队头位置 6 7 # 获取队列中元素个数 8 def size(self): 9 return len(self.elements)-pointer 10 11 # 判断队列是否为空 12 def empty(self): 13 return self.size() == 0 14 15 # 在队尾添加一个元素 16 def add(self, e): 17 self.elements.append(e) 18 19 # 弹出队首元素,如果为空则返回None 20 def poll(self): 21 if self.empty(): 22 return None 23 pointer += 1 24 return self.elements[pointer-1]
2. 图的BFS
BFS中可能用到的HashSet(C++: unordered_map, Python: dict)
常用邻接表存储。邻接矩阵太大了...
邻接表定义:
1. 自定义的方法,更加工程化。所以在面试中如果时间不紧张题目不难的情况下,推荐使用自定义邻接表的方式。
python:
1 def DirectedGraphNode: 2 def __init__(self, label): 3 self.label = label 4 self.neighbors = [] # a list of DirectedGraphNode's 5 ... 6 其中 neighbors 表示和该点连通的点有哪些
Java:
1 class DirectedGraphNode { 2 int label; 3 List<DirectedGraphNode> neighbors; 4 ... 5 }
2. 使用 Map 和 Set
这种方式虽然没有上面的方式更加直观和容易理解,但是在面试中比较节约代码量。
python:
1 # 假设nodes为节点标签的列表: 2 3 # 使用了Python中的dictionary comprehension语法 4 adjacency_list = {x:set() for x in nodes} 5 6 # 另一种写法 7 adjacency_list = {} 8 for x in nodes: 9 adjacency_list[x] = set() 10 其中 T 代表节点类型。通常可能是整数(Integer)。
Java:
1 Map<T, Set<T>> = new HashMap<T, HashSet<T>>(); // node -> a set of its neighbor nodes
3. 拓扑排序
定义
在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。
- 每个顶点出现且只出现一次;
- 若A在序列中排在B的前面,则在图中不存在从B到A的路径。
也可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。
(来自 Wiki)
实际运用
拓扑排序 Topological Sorting 是一个经典的图论问题。他实际的运用中,拓扑排序可以做如下的一些事情:
- 检测编译时的循环依赖
- 制定有依赖关系的任务的执行顺序
拓扑排序不是一种排序算法
虽然名字里有 Sorting,但是相比起我们熟知的 Bubble Sort, Quick Sort 等算法,Topological Sorting 并不是一种严格意义上的 Sorting Algorithm。
确切的说,一张图的拓扑序列可以有很多个,也可能没有
。拓扑排序只需要找到其中一个
序列,无需找到所有
序列。
入度与出度
在介绍算法之前,我们先介绍图论中的一个基本概念,入度
和出度
,英文为 in-degree & out-degree。
在有向图中,如果存在一条有向边 A-->B,那么我们认为这条边为 A 增加了一个出度,为 B 增加了一个入度。
算法流程
拓扑排序的算法是典型的宽度优先搜索算法,其大致流程如下:
- 统计所有点的入度,并初始化拓扑序列为空。
- 将所有入度为 0 的点,也就是那些没有任何
依赖
的点,放到宽度优先搜索的队列中 - 将队列中的点一个一个的释放出来,放到拓扑序列中,每次释放出某个点 A 的时候,就访问 A 的相邻点(所有A指向的点),并把这些点的入度减去 1。
- 如果发现某个点的入度被减去 1 之后变成了 0,则放入队列中。
- 直到队列为空时,算法结束,
深度优先搜索的拓扑排序
深度优先搜索也可以做拓扑排序,不过因为不容易理解,也并不推荐作为拓扑排序的主流算法。
eg: http://www.lintcode.com/problem/topological-sorting/ http://www.jiuzhang.com/solutions/topological-sorting/
1 public class Solution { 2 /** 3 * @param graph: A list of Directed graph node 4 * @return: Any topological order for the given graph. 5 */ 6 public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) { 7 // map 用来存储所有节点的入度,这里主要统计各个点的入度 8 HashMap<DirectedGraphNode, Integer> map = new HashMap(); 9 for (DirectedGraphNode node : graph) { 10 for (DirectedGraphNode neighbor : node.neighbors) { 11 if (map.containsKey(neighbor)) { 12 map.put(neighbor, map.get(neighbor) + 1); 13 } else { 14 map.put(neighbor, 1); 15 } 16 } 17 } 18 19 // 初始化拓扑序列为空 20 ArrayList<DirectedGraphNode> result = new ArrayList<DirectedGraphNode>(); 21 22 // 把所有入度为0的点,放到BFS专用的队列中 23 Queue<DirectedGraphNode> q = new LinkedList<DirectedGraphNode>(); 24 for (DirectedGraphNode node : graph) { 25 if (!map.containsKey(node)) { 26 q.offer(node); 27 result.add(node); 28 } 29 } 30 31 // 每次从队列中拿出一个点放到拓扑序列里,并将该点指向的所有点的入度减1 32 while (!q.isEmpty()) { 33 DirectedGraphNode node = q.poll(); 34 for (DirectedGraphNode n : node.neighbors) { 35 map.put(n, map.get(n) - 1); 36 // 减去1之后入度变为0的点,也放入队列 37 if (map.get(n) == 0) { 38 result.add(n); 39 q.offer(n); 40 } 41 } 42 } 43 44 return result; 45 } 46 }
入度(In-degree):
有向图(Directed Graph)中指向当前节点的点的个数(或指向当前节点的边的条数)
算法描述:
1. 统计每个点的入度
2. 将每个入度为 0 的点放入队列(Queue)中作为起始节点
3. 不断从队列中拿出一个点,去掉这个点的所有连边(指向其他点的边),其他点的相应的入度 - 1
4. 一旦发现新的入度为 0 的点,丢回队列中
拓扑排序并不是传统的排序算法
一个图可能存在多个拓扑序(Topological Order),也可能不存在任何拓扑序
拓扑排序的四种不同问法:
求任意1个拓扑序(Topological Order)
问是否存在拓扑序(是否可以被拓扑排序)
求所有的拓扑序 DFS
求是否存在且仅存在一个拓扑序 Queue中最多同时只有1个节点
http://www.lintcode.com/problem/course-schedule/
1 class DirectedGraphNode{ 2 int id; 3 ArrayList<DirectedGraphNode> neighbors; //node->neighbor 4 public DirectedGraphNode(int x){ 5 id=x; 6 neighbors=new ArrayList<>(); 7 } 8 } 9 10 class Solution { 11 public ArrayList<DirectedGraphNode> topSort(DirectedGraphNode[] G){ 12 HashMap<Integer, Integer> map = new HashMap<>(); //nodeid and their indegree 13 ArrayList<DirectedGraphNode> res = new ArrayList<>(); //top sequence 14 for(DirectedGraphNode node : G) 15 map.put(node.id, 0); 16 17 for(DirectedGraphNode nodex : G) 18 for(DirectedGraphNode nodey : nodex.neighbors) 19 map.put(nodey.id, map.get(nodey.id)+1); 20 21 Queue<DirectedGraphNode> Q = new LinkedList<>(); 22 for(DirectedGraphNode node : G){ 23 if(map.get(node.id)==0){ 24 Q.offer(node); 25 res.add(node); 26 } 27 } 28 29 while(!Q.isEmpty()){ 30 DirectedGraphNode head = Q.poll(); 31 for(DirectedGraphNode neig : head.neighbors){ 32 map.put(neig.id, map.get(neig.id)-1); 33 if(map.get(neig.id)==0){ 34 Q.offer(neig); 35 res.add(neig); 36 } 37 } 38 } 39 40 return(res); 41 } 42 43 public boolean canFinish(int numCourses, int[][] prerequisites) { 44 DirectedGraphNode[] G = new DirectedGraphNode[numCourses]; 45 for(int i=0;i<numCourses;i++) 46 G[i] = new DirectedGraphNode(i); 47 48 for(int[] pairs : prerequisites){ 49 int x=pairs[0]; 50 int y=pairs[1]; 51 G[x].neighbors.add(G[y]); 52 } 53 54 ArrayList<DirectedGraphNode> topseq = topSort(G); 55 if(topseq.size()<numCourses) 56 return false; 57 else 58 return true; 59 } 60 }
http://www.lintcode.com/problem/course-schedule-ii/
1 class DirectedGraphNode{ 2 int id; 3 ArrayList<DirectedGraphNode> neighbors; //node->neighbor 4 public DirectedGraphNode(int x){ 5 id=x; 6 neighbors=new ArrayList<>(); 7 } 8 } 9 10 class Solution { 11 public ArrayList<DirectedGraphNode> topSort(DirectedGraphNode[] G){ 12 HashMap<Integer, Integer> map = new HashMap<>(); //nodeid and their indegree 13 ArrayList<DirectedGraphNode> res = new ArrayList<>(); //top sequence 14 for(DirectedGraphNode node : G) 15 map.put(node.id, 0); 16 17 for(DirectedGraphNode nodex : G) 18 for(DirectedGraphNode nodey : nodex.neighbors) 19 map.put(nodey.id, map.get(nodey.id)+1); 20 21 Queue<DirectedGraphNode> Q = new LinkedList<>(); 22 for(DirectedGraphNode node : G){ 23 if(map.get(node.id)==0){ 24 Q.offer(node); 25 res.add(node); 26 } 27 } 28 29 while(!Q.isEmpty()){ 30 DirectedGraphNode head = Q.poll(); 31 for(DirectedGraphNode neig : head.neighbors){ 32 map.put(neig.id, map.get(neig.id)-1); 33 if(map.get(neig.id)==0){ 34 Q.offer(neig); 35 res.add(neig); 36 } 37 } 38 } 39 40 return(res); 41 } 42 43 public int[] findOrder(int numCourses, int[][] prerequisites) { 44 DirectedGraphNode[] G = new DirectedGraphNode[numCourses]; 45 for(int i=0;i<numCourses;i++) 46 G[i] = new DirectedGraphNode(i); 47 48 for(int[] pairs : prerequisites){ 49 int y=pairs[0]; 50 int x=pairs[1]; 51 G[x].neighbors.add(G[y]); 52 } 53 54 ArrayList<DirectedGraphNode> topseq = topSort(G); 55 int topsize=topseq.size(); 56 if(topsize<numCourses){ 57 topsize=0; 58 int[] res = new int[topsize]; 59 return(res); 60 } 61 62 int[] res = new int[topsize]; 63 int cnt=0; 64 for(DirectedGraphNode node : topseq){ 65 res[cnt]=node.id; 66 cnt+=1; 67 } 68 return(res); 69 } 70 }
http://www.lintcode.com/problem/alien-dictionary/
http://www.lintcode.com/problem/sequence-reconstruction/
4. BFS模板
4.1 无需分层遍历
Java:
1 // T 指代任何你希望存储的类型 2 Queue<T> queue = new LinkedList<>(); 3 Set<T> set = new HashSet<>(); 4 5 set.add(start); 6 queue.offer(start); 7 while (!queue.isEmpty()) { 8 T head = queue.poll(); 9 for (T neighbor : head.neighbors) { 10 if (!set.contains(neighbor)) { 11 set.add(neighbor); 12 queue.offer(neighbor); 13 } 14 } 15 }
Python:
1 from collections import deque 2 3 queue = deque() 4 seen = set() #等价于Java版本中的set 5 6 seen.add(start) 7 queue.append(start) 8 while len(queue): 9 head = queue.popleft() 10 for neighbor in head.neighbors: 11 if neighbor not in seen: 12 seen.add(neighbor) 13 queue.append(neighbor)
c++:
1 queue<T> que; 2 unordered_set<T, Hasher> seen; 3 4 seen.insert(start); 5 que.push(start); 6 while (!que.empty()) { 7 const T &head = que.front(); 8 que.pop(); 9 for (const T &neighbor : head.neighbors) { 10 if (!seen.count(neighbor)) { 11 seen.insert(neighbor); 12 que.push(neighbor); 13 } 14 } 15 }
- neighbor 表示从某个点 head 出发,可以走到的下一层的节点。
- set/seen 存储已经访问过的节点(已经丢到 queue 里去过的节点)
- queue 存储等待被拓展到下一层的节点
- set/seen 与 queue 是一对好基友,无时无刻都一起出现,往 queue 里新增一个节点,就要同时丢到 set 里
4.2 需要分层遍历(始终保证queue中的所有元素都来自同一层,比如binary-tree-level-order-traversal)
Java:
1 // T 指代任何你希望存储的类型 2 Queue<T> queue = new LinkedList<>(); 3 Set<T> set = new HashSet<>(); 4 5 set.add(start); 6 queue.offer(start); 7 while (!queue.isEmpty()) { 8 int size = queue.size(); 9 for (int i = 0; i < size; i++) { 10 T head = queue.poll(); 11 for (T neighbor : head.neighbors) { 12 if (!set.contains(neighbor)) { 13 set.add(neighbor); 14 queue.offer(neighbor); 15 } 16 } 17 } 18 }
Python:
1 from collections import deque 2 3 queue = deque() 4 seen = set() 5 6 seen.add(start) 7 queue.append(start) 8 while len(queue): 9 size = len(queue) 10 for _ in range(size): 11 head = queue.popleft() 12 for neighbor in head.neighbors: 13 if neighbor not in seen: 14 seen.add(neighbor) 15 queue.append(neighbor)
C++:
1 queue<T> que; 2 unordered_set<T, Hasher> seen; 3 4 seen.insert(start); 5 que.push(start); 6 while (!que.empty()) { 7 int size = que.size(); 8 for (int i = 0; i < size; ++i) { 9 const T &head = que.front(); 10 que.pop(); 11 for (const T &neighbor : head.neighbors) { 12 if (!seen.count(neighbor)) { 13 seen.insert(neighbor); 14 que.push(neighbor); 15 } 16 } 17 } 18 }
- size = queue.size() 是一个必须的步骤。如果在 for 循环中使用
for (int i = 0; i < queue.size(); i++)
会出错,因为 queue.size() 是一个动态变化的值。所以必须先把当前层一共有多少个节点存在局部变量 size 中,才不会把下一层的节点也在当前层进行扩展。
1. 二叉树上的BFS
http://www.lintcode.com/problem/binary-tree-level-order-traversal/ Video 11:00
https://leetcode.com/problems/binary-tree-level-order-traversal/
水题...
1 /** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode(int x) { val = x; } 8 * } 9 */ 10 class Solution { 11 public List<List<Integer>> levelOrder(TreeNode root) { 12 Queue<TreeNode> Q = new LinkedList<>(); 13 Set<TreeNode> S = new HashSet<>(); 14 List<List<Integer>> ans = new ArrayList<>(); 15 if(root==null) 16 return(ans); 17 18 S.add(root); 19 Q.offer(root); 20 while(!Q.isEmpty()){ 21 List<Integer> CurrentLevel=new ArrayList<>(); 22 int qsize=Q.size(); 23 for(int i=0;i<qsize;i++){ 24 TreeNode qhead=Q.poll(); 25 CurrentLevel.add(qhead.val); 26 if(qhead.left!=null){ 27 S.add(qhead.left); 28 Q.offer(qhead.left); 29 } 30 if(qhead.right!=null){ 31 S.add(qhead.right); 32 Q.offer(qhead.right); 33 } 34 } 35 ans.add(CurrentLevel); 36 } 37 return(ans); 38 } 39 }
2. Binary Tree的序列化/反序列化
http://www.lintcode.com/en/help/binary-tree-representation/
http://www.lintcode.com/en/problem/binary-tree-serialization/
Binary Tree Level Order Traversal II
http://www.lintcode.com/en/problem/binary-tree-level-order-traversal-ii/
http://www.jiuzhang.com/solutions/binary-tree-level-order-traversal-ii/
1 /** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode(int x) { val = x; } 8 * } 9 */ 10 class Solution { 11 public List<List<Integer>> levelOrderBottom(TreeNode root) { 12 List<List<Integer>> ans=new ArrayList<>(); 13 Queue<TreeNode> Q=new LinkedList<>(); 14 if(root==null) 15 return(ans); 16 Q.offer(root); 17 while(!Q.isEmpty()){ 18 int qsize=Q.size(); 19 List<Integer> CurrentLevel=new ArrayList<>(); 20 for(int i=0;i<qsize;i++){ 21 TreeNode qhead=Q.poll(); 22 CurrentLevel.add(qhead.val); 23 if(qhead.left!=null) 24 Q.add(qhead.left); 25 if(qhead.right!=null) 26 Q.add(qhead.right); 27 } 28 ans.add(CurrentLevel); 29 } 30 for (int i = 0, j = ans.size() - 1; i < j; i++) 31 ans.add(i, ans.remove(j)); 32 return(ans); 33 } 34 }
把binary-tree-level-order-traversal的结果反过来就行了...
注意一个in-place的reverse list的小技巧
Binary Tree Zigzag Order Traversal
http://www.lintcode.com/en/problem/binary-tree-zigzag-level-order-traversal/
http://www.jiuzhang.com/solutions/binary-tree-zigzag-level-order-traversal/
把binary-tree-level-order-traversal的结果胡搞一下就行了...
1 /** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode(int x) { val = x; } 8 * } 9 */ 10 class Solution { 11 public List<List<Integer>> zigzagLevelOrder(TreeNode root) { 12 Queue<TreeNode> Q=new LinkedList<>(); 13 List<List<Integer>> ans=new ArrayList<>(); 14 15 if(root==null) 16 return(ans); 17 18 boolean seq=true; //true: l->r false: r->l 19 Q.offer(root); 20 while(!Q.isEmpty()){ 21 int qsize=Q.size(); 22 List<Integer> CurrentLevel=new ArrayList<>(); 23 for(int i=0;i<qsize;i++){ 24 TreeNode qhead=Q.poll(); 25 CurrentLevel.add(qhead.val); 26 if(qhead.left!=null) 27 Q.add(qhead.left); 28 if(qhead.right!=null) 29 Q.add(qhead.right); 30 } 31 if(seq) 32 seq=false; 33 else{ 34 for(int i=0;i<CurrentLevel.size()/2;i++){ 35 int j=CurrentLevel.size()-i-1; 36 int tmp=CurrentLevel.get(i); 37 CurrentLevel.set(i, CurrentLevel.get(j)); 38 CurrentLevel.set(j,tmp); 39 } 40 seq=true; 41 } 42 ans.add(CurrentLevel); 43 } 44 45 return(ans); 46 } 47 }
Convert Binary Tree to Linked Lists by Depth
http://www.lintcode.com/en/problem/convert-binary-tree-to-linked-lists-by-depth/
http://www.jiuzhang.com/solutions/convert-binary-tree-to-linked-lists-by-depth/
把binary-tree-level-order-traversal的结果建个链表就行了
1 /** 2 * Definition of TreeNode: 3 * public class TreeNode { 4 * public int val; 5 * public TreeNode left, right; 6 * public TreeNode(int val) { 7 * this.val = val; 8 * this.left = this.right = null; 9 * } 10 * } 11 * Definition for singly-linked list. 12 * public class ListNode { 13 * int val; 14 * ListNode next; 15 * ListNode(int x) { val = x; } 16 * } 17 */ 18 public class Solution { 19 /** 20 * @param root the root of binary tree 21 * @return a lists of linked list 22 */ 23 public List<ListNode> binaryTreeToLists(TreeNode root) { 24 // Write your code here 25 List<ListNode> ans=new ArrayList<>(); 26 Queue<TreeNode> Q=new LinkedList<>(); 27 if(root==null) 28 return(ans); 29 30 Q.offer(root); 31 while(!Q.isEmpty()){ 32 int qsize=Q.size(); 33 TreeNode qhead=Q.poll(); 34 ListNode lhead=new ListNode(qhead.val); 35 lhead.next=null; 36 ListNode lpnt=lhead; 37 if(qhead.left!=null) 38 Q.offer(qhead.left); 39 if(qhead.right!=null) 40 Q.offer(qhead.right); 41 for(int i=0;i<qsize-1;i++){ 42 qhead=Q.poll(); 43 ListNode ltmp=new ListNode(qhead.val); 44 lpnt.next=ltmp; 45 lpnt=lpnt.next; 46 if(qhead.left!=null) 47 Q.offer(qhead.left); 48 if(qhead.right!=null) 49 Q.offer(qhead.right); 50 } 51 ans.add(lhead); 52 } 53 54 return(ans); 55 } 56 }
3. 图的BFS
http://www.lintcode.com/problem/clone-graph/ Video 43:00
http://www.lintcode.com/problem/word-ladder/ Video 55:00
1 class Solution { 2 public int distance(String A, String B){ 3 int sl=A.length(); 4 int ans=0; 5 for(int i=0;i<sl;i++){ 6 if(A.charAt(i)!=B.charAt(i)) 7 ans++; 8 } 9 return(ans); 10 } 11 12 public int ladderLength(String beginWord, String endWord, List<String> wordList) { 13 Queue<String> Q=new LinkedList<>(); 14 Map<String, Integer> S=new HashMap<>(); 15 16 Q.offer(beginWord); 17 S.put(beginWord, 1); 18 19 while(!Q.isEmpty()){ 20 String qhead=Q.poll(); 21 int dep=S.get(qhead); 22 if(qhead.equals(endWord)) 23 return(dep); 24 for(String dictword : wordList){ 25 if(distance(qhead, dictword)==1 && !S.containsKey(dictword)){ 26 S.put(dictword, dep+1); 27 Q.offer(dictword); 28 } 29 } 30 } 31 32 return(0); 33 } 34 }
图上的BFS
判断一个图是否是一棵树
http://www.lintcode.com/problem/graph-valid-tree/
搜索图中最近值为target的点
http://www.lintcode.com/problem/search-graph-nodes/
无向图联通块
http://www.lintcode.com/problem/connected-component-in-undirected-graph
序列重构(判断是否只有一个拓扑排序)
http://www.lintcode.com/problem/sequence-reconstruction/
4. 矩阵的bfs
图 Graph N个点,M条边 M最大是 O(N^2) 的级别 图上BFS时间复杂度 = O(N + M) • 说是O(M)问题也不大,因为M一般都比N大 所以最坏情况可能是 O(N^2) 矩阵 Matrix R行C列 R*C个点,R*C*2 条边(每个点上下左右4条边,每条边被2个点共享)。 矩阵中BFS时间复杂度 = O(R * C)
http://www.lintcode.com/problem/number-of-islands/ Video 1:30:00
偷懒用dfs做了....
1 class Solution { 2 public boolean[][] visited; 3 4 public void dfs(int x,int y,char[][] grid){ 5 int lx=grid.length; 6 int ly=grid[0].length; 7 int[] dx={1,0,-1,0}; 8 int[] dy={0,1,0,-1}; 9 10 for(int i=0;i<4;i++){ 11 int tx=x+dx[i]; 12 int ty=y+dy[i]; 13 if(tx>=0 && tx<lx && ty>=0 && ty<ly) 14 if(!visited[tx][ty] && grid[tx][ty]=='1'){ 15 visited[tx][ty]=true; 16 dfs(tx,ty,grid); 17 } 18 } 19 } 20 21 public int numIslands(char[][] grid) { 22 int lx=grid.length; 23 if(lx==0) 24 return(0); 25 int ly=grid[0].length; 26 int ans=0; 27 visited=new boolean[lx][ly]; 28 29 for(int i=0;i<lx;i++){ 30 for(int j=0;j<ly;j++){ 31 if(grid[i][j]=='1' && !visited[i][j]){ 32 ans+=1; 33 visited[i][j]=true; 34 dfs(i,j,grid); 35 } 36 } 37 } 38 39 return(ans); 40 } 41 }
http://www.lintcode.com/problem/knight-shortest-path/ Video 1:35:00
矩阵上的BFS
僵尸多少天吃掉所有人
http://www.lintcode.com/problem/zombie-in-matrix/
建邮局问题 Build Post Office II
http://www.lintcode.com/problem/build-post-office-ii/
双向BFS
双向宽度优先搜索 (Bidirectional BFS) 算法适用于如下的场景:
- 无向图
- 所有边的长度都为 1 或者长度都一样
- 同时给出了起点和终点
以上 3 个条件都满足的时候,可以使用双向宽度优先搜索来求出起点和终点的最短距离。
双向宽度优先搜索本质上还是BFS,只不过变成了起点向终点和终点向起点同时进行扩展,直至两个方向上出现同一个子节点,搜索结束。
我们还是可以利用队列来实现:一个队列保存从起点开始搜索的状态,另一个保存从终点开始的状态,两边如果相交了,那么搜索结束。起点到终点的最短距离即为起点到相交节点的距离与终点到相交节点的距离之和。
Q.双向BFS是否真的能提高效率?
假设单向BFS需要搜索 N 层才能到达终点,每层的判断量为 X,那么总的运算量为 X ^ N. 如果换成是双向BFS,前后各自需要搜索 N / 2 层,总运算量为 2∗X^(N/2)。
如果 N 比较大且X 不为 1,则运算量相较于单向BFS可以大大减少,差不多可以减少到原来规模的根号的量级。
Bidirectional BFS 掌握起来并不是很难,算法实现上稍微复杂了一点(代码量相对单向 BFS 翻倍),掌握这个算法一方面加深对普通 BFS 的熟练程度,另外一方面,基本上写一次就能记住,如果在面试中被问到了如何优化 BFS 的问题,Bidirectional BFS 几乎就是标准答案了。
https://www.jiuzhang.com/tutorial/algorithm/371
1 /** 2 * Definition for graph node. 3 * class UndirectedGraphNode { 4 * int label; 5 * ArrayList<UndirectedGraphNode> neighbors; 6 * UndirectedGraphNode(int x) { 7 * label = x; neighbors = new ArrayList<UndirectedGraphNode>(); 8 * } 9 * }; 10 */ 11 public int doubleBFS(UndirectedGraphNode start, UndirectedGraphNode end) { 12 if (start.equals(end)) { 13 return 1; 14 } 15 // 起点开始的BFS队列 16 Queue<UndirectedGraphNode> startQueue = new LinkedList<>(); 17 // 终点开始的BFS队列 18 Queue<UndirectedGraphNode> endQueue = new LinkedList<>(); 19 startQueue.add(start); 20 endQueue.add(end); 21 int step = 0; 22 // 记录从起点开始访问到的节点 23 Set<UndirectedGraphNode> startVisited = new HashSet<>(); 24 // 记录从终点开始访问到的节点 25 Set<UndirectedGraphNode> endVisited = new HashSet<>(); 26 startVisited.add(start); 27 endVisited.add(end); 28 while (!startQueue.isEmpty() || !endQueue.isEmpty()) { 29 int startSize = startQueue.size(); 30 int endSize = endQueue.size(); 31 // 按层遍历 32 step ++; 33 for (int i = 0; i < startSize; i ++) { 34 UndirectedGraphNode cur = startQueue.poll(); 35 for (UndirectedGraphNode neighbor : cur.neighbors) { 36 if (startVisited.contains(neighbor)) {//重复节点 37 continue; 38 } else if (endVisited.contains(neighbor)) {//相交 39 return step; 40 } else { 41 startVisited.add(neighbor); 42 startQueue.add(neighbor); 43 } 44 } 45 } 46 step ++; 47 for (int i = 0; i < endSize; i ++) { 48 UndirectedGraphNode cur = endQueue.poll(); 49 for (UndirectedGraphNode neighbor : cur.neighbors) { 50 if (endVisited.contains(neighbor)) { 51 continue; 52 } else if (startVisited.contains(neighbor)) { 53 return step; 54 } else { 55 endVisited.add(neighbor); 56 endQueue.add(neighbor); 57 } 58 } 59 } 60 } 61 return -1; // 不连通 62 }
http://www.lintcode.com/zh-cn/problem/shortest-path-in-undirected-graph/
http://www.lintcode.com/zh-cn/problem/knight-shortest-path/
http://www.lintcode.com/en/problem/knight-shortest-path-ii/
Appendix:
使用两个队列的BFS实现
我们可以将当前层的所有节点存在第一个队列中,然后拓展(Extend)出的下一层节点存在另外一个队列中。来回迭代,逐层展开。
1 // T 表示任意你想存储的类型 2 Queue<T> queue1 = new LinkedList<>(); 3 Queue<T> queue2 = new LinkedList<>(); 4 queue1.offer(startNode); 5 int currentLevel = 0; 6 7 while (!queue1.isEmpty()) { 8 int size = queue1.size(); 9 for (int i = 0; i < size; i++) { 10 T head = queue1.poll(); 11 for (all neighbors of head) { 12 queue2.offer(neighbor); 13 } 14 } 15 Queue<T> temp = queue1; 16 queue1 = queue2; 17 queue2 = temp; 18 19 queue2.clear(); 20 currentLevel++; 21 }
使用 Dummy Node 进行 BFS
什么是 Dummy Node
Dummy Node,翻译为哨兵节点。Dummy Node 一般本身不存储任何实际有意义的值,通常用作"占位",或者链表的“虚拟头”。
如很多的链表问题中,我们会在原来的头head的前面新增一个节点,这个节点没有任何值,但是 next 指向 head。这样就会方便对 head 进行删除或者在前面插入等操作。
head->node->node->node ...
=>
dummy->head->node->node->node...
Dummy Node 在 BFS 中如何使用
在 BFS 中,我们主要用 dummy node 来做占位符。即,在队列中每一层节点的结尾,都放一个 null
(or None in Python,nil in Ruby),来表示这一层的遍历结束了。这里 dummy node 就是一个 null。
1 // T 可以是任何你想存储的节点的类型 2 Queue<T> queue = new LinkedList<>(); 3 queue.offer(startNode); 4 queue.offer(null); 5 currentLevel = 0; 6 // 因为有 dummy node 的存在,不能再用 isEmpty 了来判断是否还有没有拓展的节点了 7 while (queue.size() > 1) { 8 T head = queue.poll(); 9 if (head == null) { 10 currentLevel++; 11 queue.offer(null); 12 continue; 13 } 14 for (all neighbors of head) { 15 queue.offer(neighbor); 16 } 17 }