1. 程式人生 > >九章演算法筆記 4.寬度優先搜尋 Breadth First Search

九章演算法筆記 4.寬度優先搜尋 Breadth First Search

演算法與題型 cs3k.com

  1. DFS: 用於搜尋, 題目中有ALL字樣
  2. 二分法: 用於時間複雜度小於O(n)的情況
  3. 分治法: 二叉樹問題, 子問題和父問題有關係
  4. BFS:- 二叉樹上的寬搜- 圖上的寬搜: 拓撲排序- 棋盤上的寬搜

 

什麼時候應該用BFS?

  1. 圖的遍歷 Traversal in Graph:
    – 層級遍歷 Level Order Traversal: 有先碰到後碰到的問題, 分距離遠近enter image description here
    – 由點及面 Connected Component: 聯通問題, 比如Smallest Rectangle Enclosing Black Pixels這道題就可以用灌水法做: 二分法O(Row * logCol + Col * LogRow), 灌水法O(R * C)- 拓撲排序 Topological Sorting: 有向圖********pic4.2
  2. 最短路徑 Shortest Path in Simple Graph
    – 僅限簡單圖求最短路徑(圖中每條邊長度都是1,且沒有方向)
    – ps: 如果問最長的路徑呢? 用DP或者dfs所有路徑找一遍

Binary Tree Level Order Traversal

 cs3k.com

VECTOR用ARRAY來實現:

開一個n的區間, 指標指向開頭; 不夠的時候, 再開一個2n的區間, 把指標挪過來.

用迴圈陣列實現先進先出的佇列:

FIFO佇列包含兩個基本操作:插入(put)一個新的項,刪除(get)一個最早插入的項。

迴圈陣列實現方式:

對於每一個佇列資料結構,我們保留一個數組S,其最多存放的Nmax個元素,定義兩個位置front和rear分別指向佇列的頭尾兩端,此外我們用N來記錄佇列中實際存在的元素 的個數。

  對於一個元素item入隊,我們讓N和rear增1,然後置s[rear]=item;
  對於一個元素item入隊,我們讓N減1,讓front增1.

注意:這種實現有一個潛在的問題。假設陣列的Nmax=10,經過10次入隊後,佇列似乎已經滿了,因為此時rear=9.然而,佇列中可能只存在小於9個的元素,因為 之前入隊的操作可能伴隨著出隊的操作。例如我們將1,2,3,4,5,6,7,8,9,10一次入隊,然後又進行2次出隊操作,此時front指向陣列元素S[2]處的位置,此時佇列中只 有8個元素,這時rear仍然指向陣列的最後一個元素S[Nmax-1]的位置,此時我們有沒有辦法繼續進行入隊操作呢?

簡單的解決方法是:只要front和rear到達陣列的末尾,它就又繞回到開頭,這種實現方式就是迴圈陣列的實現。這樣佇列的操作就侷限在所定義的陣列這塊儲存空間內。

STACK SYNTAX:

push, pop, top

snipaste_20170320_095618

QUEUE SYNTAX

push, pop,front,back

這道題的實現:

Given a binary tree, return the level order traversal of its nodes’ values. (ie, from left to right, level by level).

enter image description here

  1. 模板一般是一個while大迴圈之後一個for小迴圈.
  2. 31行為什麼要先取出queue的size呢?因為for迴圈裡面的檢查條件, 每次都執行一遍, 不是開始多大就多大.
  3. 一般不能用stack, 因為同一層的節點會反過來.
    stack: DFS
    queue: BFS
  4. 分層遍歷比不分層遍歷多一個for迴圈
  5. 時空複雜度都是O(n), ps:一般二叉樹問題的時空複雜度都是O(n).
    一般時間複雜度都是看最裡面的迴圈體進出了多少次,這道題的while和for迴圈的最壞情況都是n次,但是時間複雜度不是n*n,是因為最壞的情況不能同時發生.

Binary Tree Serialization

 cs3k.com

什麼是序列化?

將“記憶體”中結構化的資料變成“字串”的過程

序列化:object to string

反序列化:string to object

什麼時候需要序列化?

  1. 將記憶體中的資料持久化儲存時記憶體中重要的資料不能只是呆在記憶體裡,這樣斷電就沒有了,所需需要用一種方式寫入硬碟,在需要的時候,能否再從硬碟中讀出來在記憶體中重新建立
  2. 網路傳輸時機器與機器之間交換資料的時候,不可能互相去讀對方的記憶體。只能講資料變成字元流資料(字串)後常用的一些序列化手段:XMLJson是個hash map,key是string, value是list/int或另一個hash map
    Thrift (by Facebook)
    ProtoBuf (by Google)

一些序列化的例子:

  1. 比如一個數組,裡面都是整數,我們可以簡單的序列化為”[1,2,3]”
  2. 一個整數連結串列,我們可以序列化為,”1->2->3”
  3. 一個雜湊表(HashMap),我們可以序列化為,”{\”key\”: \”value\”}”

序列化演算法設計時需要考慮的因素:

  1. 壓縮率。對於網路傳輸和磁碟儲存而言,當然希望更節省如 Thrift, ProtoBuf 都是為了更快的傳輸資料和節省儲存空間而設計的
  2. 可讀性。我們希望開發人員,能夠通過序列化後的資料直接看懂原始資料是什麼如 Json,LintCode 的輸入資料

 

二叉樹如何序列化?

  你可以使用任何方式進行序列化, bfs,dfs, 前中後序都行,只要serialization之後unserialization回來的一樣就可以.  

Binary Tree Serialization

 cs3k.com

Design an algorithm and write code to serialize and deserialize a binary tree. Writing the tree to a file is called ‘serialization’ and reading back from the file to reconstruct the exact same binary tree is ‘deserialization’.  

Example

An example of testdata: Binary tree {3,9,20,#,#,15,7}, denote the following structure:

  3
 / \
9  20
  /  \
 15   7
class Solution {
    /**
     * This method will be invoked first, you should design your own algorithm 
     * to serialize a binary tree which denote by a root node to a string which
     * can be easily deserialized by your own "deserialize" method later.
     */
    public String serialize(TreeNode root) { if (root == null) { return "{}"; } ArrayList<TreeNode> queue = new ArrayList<TreeNode>(); queue.add(root); for (int i = 0; i < queue.size(); i++) { TreeNode node = queue.get(i); if (node == null) { continue; } queue.add(node.left); queue.add(node.right); } while (queue.get(queue.size() - 1) == null) { queue.remove(queue.size() - 1); } StringBuilder sb = new StringBuilder(); sb.append("{"); sb.append(queue.get(0).val); for (int i = 1; i < queue.size(); i++) { if (queue.get(i) == null) { sb.append(",#"); } else { sb.append(","); sb.append(queue.get(i).val); } } sb.append("}"); return sb.toString(); } /** * This method will be invoked second, the argument data is what exactly * you serialized at method "serialize", that means the data is not given by * system, it's given by your own serialize method. So the format of data is * designed by yourself, and deserialize it here as you serialize it in * "serialize" method. */ public TreeNode deserialize(String data) { if (data.equals("{}")) { return null; } String[] vals = data.substring(1, data.length() - 1).split(","); ArrayList<TreeNode> queue = new ArrayList<TreeNode>(); TreeNode root = new TreeNode(Integer.parseInt(vals[0])); queue.add(root); int index = 0; boolean isLeftChild = true; for (int i = 1; i < vals.length; i++) { if (!vals[i].equals("#")) { TreeNode node = new TreeNode(Integer.parseInt(vals[i])); if (isLeftChild) { queue.get(index).left = node; } else { queue.get(index).right = node; } queue.add(node); } if (!isLeftChild) { index++; } isLeftChild = !isLeftChild; } return root; } } 
 

圖上的寬度優先搜尋

 cs3k.com

  1. 和樹上的有什麼區別?
    – 圖上可能有環
  2. 所以圖上的需要個hash map或者一個hash set記錄走沒走過這個節點

Graph Valid Tree

Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), write a function to check whether these edges make up a valid tree.

 

一個graph是不是tree的決定條件有兩個:

  1. 點比邊個數多一個
  2. 所有點都要連通: 對於連通性問題, bfs是最好的方法之一.
public class Solution {
    /** * @param n an integer * @param edges a list of undirected edges * @return true if it's a valid tree, or false */ public boolean validTree(int n, int[][] edges) { if (n == 0) { return false; } if (edges.length != n - 1) { return false; } Map<Integer, Set<Integer>> graph = initializeGraph(n, edges); // bfs Queue<Integer> queue = new LinkedList<>(); Set<Integer> hash = new HashSet<>(); queue.offer(0); hash.add(0); while (!queue.isEmpty()) { int node = queue.poll(); for (Integer neighbor : graph.get(node)) { if (hash.contains(neighbor)) { continue; } hash.add(neighbor); queue.offer(neighbor); } } return (hash.size() == n); } private Map<Integer, Set<Integer>> initializeGraph(int n, int[][] edges) { Map<Integer, Set<Integer>> graph = new HashMap<>(); for (int i = 0; i < n; i++) { graph.put(i, new HashSet<Integer>()); } for (int i = 0; i < edges.length; i++) { int u = edges[i][0]; int v = edges[i][1]; graph.get(u).add(v); graph.get(v).add(u); } return graph; } } 
    1. 我們需要一個鄰接表adjacent list, 即是一個Map<Integer, Set<Integer>> 2. 實現方式為queque + hash 3. 兩個迴圈: while + for  

Clone Graph

 cs3k.com

Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors.

How we serialize an undirected graph:

Nodes are labeled uniquely.

We use # as a separator for each node, and , as a separator for node label and each neighbor of the node.

As an example, consider the serialized graph {0,1,2#1,2#2,2}.

The graph has a total of three nodes, and therefore contains three parts as separated by #.

  1. First node is labeled as 0. Connect node 0 to both nodes 1 and 2.
  2. Second node is labeled as 1. Connect node 1 to node 2.
  3. Third node is labeled as 2. Connect node 2 to node 2 (itself), thus forming a self-cycle.

Visually, the graph looks like the following:

   1
  / \
 /   \
0 --- 2
     / \
     \_/
 
public class Solution {
    /** * @param node: A undirected graph node * @return: A undirected graph node */ public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) { if (node == null) { return node; } // use bfs algorithm to traverse the graph and get all nodes. ArrayList<UndirectedGraphNode> nodes = getNodes(node); // copy nodes, store the old->new mapping information in a hash map HashMap<UndirectedGraphNode, UndirectedGraphNode> mapping = new HashMap<>(); for (UndirectedGraphNode n : nodes) { mapping.put(n, new UndirectedGraphNode(n.label)); } // copy neighbors(edges) for (UndirectedGraphNode n : nodes) { UndirectedGraphNode newNode = mapping.get(n); for (UndirectedGraphNode neighbor : n.neighbors) { UndirectedGraphNode newNeighbor = mapping.get(neighbor); newNode.neighbors.add(newNeighbor); } } return mapping.get(node); } private ArrayList<UndirectedGraphNode> getNodes(UndirectedGraphNode node) { Queue<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>(); HashSet<UndirectedGraphNode> set = new HashSet<>(); queue.offer(node); set.add(node); while (!queue.isEmpty()) { UndirectedGraphNode head = queue.poll(); for (UndirectedGraphNode neighbor : head.neighbors) { if(!set.contains(neighbor)){ set.add(neighbor); queue.offer(neighbor); } } } return new ArrayList<UndirectedGraphNode>(set); } } 

 

三步走:

  1. 通過一個點找到所有的點
  2. 克隆點, 即點變新的點
  3. 克隆邊, 即邊變新的邊

勸分不勸合, 程式設計能分開的儘量分開寫。

能用BFS, 一定不用DFS, 因為dfs有recursion, 容易stack over flow

Search Graph Nodes

 cs3k.com

由點及面

不用做分層遍歷

Given a undirected graph, a node and a target, return the nearest node to given node which value of it is target, return NULL if you can’t find.

There is a mapping store the nodes’ values in the given parameters.

It’s guaranteed there is only one available solution

 

Example

 

2------3  5
 \     |  | 
  \    |  |
   \   |  |
    \  |  |
      1 --4
Give a node 1, target is 50

there a hash named values which is [3,4,10,50,50], represent:
Value of node 1 is 3
Value of node 2 is 4
Value of node 3 is 10
Value of node 4 is 50
Value of node 5 is 50

Return node 4
public class Solution {
    /** * @param graph a list of Undirected graph node * @param values a hash mapping, <UndirectedGraphNode, (int)value> * @param node an Undirected graph node * @param target an integer * @return the a node */ public UndirectedGraphNode searchNode(ArrayList<UndirectedGraphNode> graph, Map<UndirectedGraphNode, Integer> values, UndirectedGraphNode node, int target) { // Write your code here Queue<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>(); Set<UndirectedGraphNode> hash = new HashSet<UndirectedGraphNode>(); queue.offer(node); hash.add(node); while (!queue.isEmpty()) { UndirectedGraphNode head = queue.poll(); if (values.get(head) == target) { return head; } for (UndirectedGraphNode nei : head.neighbors) { if (!hash.contains(nei)){ queue.offer(nei); hash.add(nei); } } } return null; } }

 

Topological Sorting

 cs3k.com

-可以用DFS?

可以,但是不推薦

-應用場景

選課

編譯:編譯的時候用link的東西,所以要看看有沒有迴圈依賴。

-關心能否進行拓撲排序, 即是關心有沒有環

-入度: 指向一個節點的邊的個數

 

Given an directed graph, a topological order of the graph nodes is defined as follow:

  • For each directed edge A -> B in graph, A must before B in the order list.
  • The first node in the order can be any node in the graph with no nodes direct to it.

Find any topological order for the given graph.

 

  Example

 

For graph as follow:

picture

The topological order can be:

[0, 1, 2, 3, 4, 5]
[0, 2, 3, 1, 5, 4]
...

 

Screenshot from 2017-10-02 23-21-19

Course Schedule

There are a total of n courses you have to take, labeled from 0 to n - 1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

 

Example

 

Given n = 2, prerequisites = [[1,0]]
Return true

Given n = 2, prerequisites = [[1,0],[0,1]]
Return false

 

Screenshot from 2017-10-02 23-25-31.png