Chapter four Breadth First Search(宽度优先搜索)

Posted struggleforit

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Chapter four Breadth First Search(宽度优先搜索)相关的知识,希望对你有一定的参考价值。

BFS最主要的数据结构是Queue,由LinkedList实现。

二叉树上的BFS:

1.binary-tree-level-order-traversal(二叉树的层次遍历)

给出一棵二叉树,返回其节点值的层次遍历(逐层从左往右访问)

BFS解法一【基本模板】

public class Solution {
    /**
     * @param root: The root of binary tree.
     * @return: Level order a list of lists of integer
     */
    public ArrayList<ArrayList<Integer>> levelOrder(TreeNode root) {
        // write your code here
        ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>();
        if (root == null) {
            return results;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            ArrayList<Integer> level = new ArrayList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            results.add(level);
        }
        return results;
    }
}
View Code

BFS解法二(使用Q1和Q2两个ArrayList):

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */
public class Solution {
    /**
     * @param root: The root of binary tree.
     * @return: Level order a list of lists of integer
     */
    public ArrayList<ArrayList<Integer>> levelOrder(TreeNode root) {
        // write your code here
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if (root == null) {
            return result;
        }
        ArrayList<TreeNode> Q1 = new ArrayList<TreeNode>();
        ArrayList<TreeNode> Q2 = new ArrayList<TreeNode>();
        Q1.add(root);
        while (Q1.size() != 0) {
            ArrayList<Integer> level = new ArrayList<Integer>();
            Q2.clear();
            for (int i = 0; i < Q1.size(); i++) {
                TreeNode node = Q1.get(i);
                level.add(node.val);
                if (node.left != null) {
                    Q2.add(node.left);
                }
                if (node.right != null) {
                    Q2.add(node.right);
                }
            }
            // swap q1 and q2
            ArrayList<TreeNode> temp = Q1;
            Q1 = Q2;
            Q2 = temp;
            // add to result
            result.add(level);
        }
        return result;

    }
}
View Code

2.binary-tree-level-order-traversal-ii(二叉树的层次遍历II)

给出一棵二叉树,返回其节点值从底向上的层次遍历(按从叶节点所在层到根节点所在的层遍历,然后逐层从左往右遍历)

public class Solution {
    /**
     * @param root: The root of binary tree.
     * @return: buttom-up level order a list of lists of integer
     */
    public ArrayList<ArrayList<Integer>> levelOrderBottom(TreeNode root) {
        // write your code here
        ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>();
        if (root == null) {
            return results;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            ArrayList<Integer> level = new ArrayList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            results.add(level);
        }
        Collections.reverse(results);
        return results;
    }
}
View Code

注意:在第1题的基础上在返回results之前利用Colllections.reverse(results)翻转一下即可。

3.binary-tree-zigzag-level-order-traversal(二叉树的锯齿形层次遍历)

给出一棵二叉树,返回其节点值的锯齿形层次遍历(先从左往右,下一层再从右往左,层与层之间交替进行) 

public class Solution {
    /**
     * @param root: The root of binary tree.
     * @return: A list of lists of integer include 
     *          the zigzag level order traversal of its nodes\' values 
     */
    public ArrayList<ArrayList<Integer>> zigzagLevelOrder(TreeNode root) {
        // write your code here
        ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>();
        if (root == null) {
            return results;
        }
        Stack<TreeNode> curtLevel = new Stack<TreeNode>();
        Stack<TreeNode> nextLevel = new Stack<TreeNode>();
        Stack<TreeNode> temp;
        curtLevel.push(root);
        boolean normalOrder = true;
        while (!curtLevel.empty()) {
            ArrayList<Integer> curtLevelResult = new ArrayList<Integer>();
            while (!curtLevel.empty()) {
                TreeNode node = curtLevel.pop();
                curtLevelResult.add(node.val);
                if (normalOrder) {
                    if (node.left != null) {
                        nextLevel.push(node.left);
                    }
                    if (node.right != null) {
                        nextLevel.push(node.right);
                    }
                } else {
                    if (node.right != null) {
                        nextLevel.push(node.right);
                    }
                    if (node.left != null) {
                        nextLevel.push(node.left);
                    }
                }
            }
            results.add(curtLevelResult);
            temp = curtLevel;
            curtLevel = nextLevel;
            nextLevel = temp;
            normalOrder = !normalOrder;
        }
        return results;
    }
}
View Code

注意:使用Stack(栈)先进后出的特征,curtLevel和nextLevel交替使用,normalOrder变量用于标记是否是正常顺序(从左到右)。初始时为从左至右的顺序,下一层就应该先放入左儿子,再放入右儿子。由于栈的先进后出特性,右儿子先出来。

4.convert-binary-tree-to-linked-lists-by-depth(将二叉树按照层级转化为链表)

给一棵二叉树,设计一个算法为每一层的节点建立一个链表。也就是说,如果一棵二叉树有D层,那么你需要创建D条链表。 

DFS解法:

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    /**
     * @param root the root of binary tree
     * @return a lists of linked list
     */
    public List<ListNode> binaryTreeToLists(TreeNode root) {
        // Write your code here
        List<ListNode> result = new ArrayList<ListNode>();
        if (root == null) {
            return result;
        }
        dfs(root, 1, result);
        return result;
    }
    private void dfs(TreeNode root, int depth, List<ListNode> result) {
        if (root == null) {
            return;
        }
        ListNode node = new ListNode(root.val);
        if (result.size() < depth) {
            result.add(node);
        } else {
            node.next = result.get(depth - 1);
            result.set(depth - 1, node);
        }
        dfs(root.right, depth + 1, result);
        dfs(root.left, depth + 1, result);
    }
}
View Code

注意:例如下图的二叉树,运行步骤为:执行dfs(1,1,result),结果{1};执行dfs(3,2,{1}),结果{1,3};执行(2,2,{1,3}),结果{1,2->3};执行(4,3,{1,2->3})结果{1,2->3, 4}。注意到要先dfs根节点的右儿子,再dfs左儿子,这样才能实现左儿子->右儿子。

5.binary-tree-serialization(二叉树的序列化和反序列化)【理解有难度】

设计一个算法,并编写代码来序列化和反序列化二叉树。将树写入一个文件被称为“序列化”,读取文件后重建同样的二叉树被称为“反序列化”。如何反序列化或序列化二叉树是没有限制的,你只需要确保可以将二叉树序列化为一个字符串,并且可以将字符串反序列化为原来的树结构。

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */
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) {
        // write your code here
        if (root == null) {
            return "{}";
        }
        ArrayList<TreeNode> list = new ArrayList<TreeNode>();
        list.add(root);
        //将每个节点及其左右儿子放入list中
        for (int i = 0; i < list.size(); i++) {
            TreeNode node = list.get(i);
            if (node == null) {
                continue;
            }
            list.add(node.left);
            list.add(node.right);
        }
        //删除list最后的空节点
        while (list.get(list.size() - 1) == null) {
            list.remove(list.size() - 1);
        }
        //将list转换为string字符串
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append(list.get(0).val);
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i) == null) {
                sb.append(",#");
            } else {
                sb.append(",");
                sb.append(list.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) {
        // write your code here
        if (data.equals("{}")) {
            return null;
        }
        //将字符串进行分割
        String[] vals = data.substring(1, data.length() - 1).split(",");
        ArrayList<TreeNode> list = new ArrayList<TreeNode>();
        TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
        list.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) {
                    list.get(index).left = node;
                } else {
                    list.get(index).right = node;
                }
                list.add(node);
            }
            //index标记当前根节点,当右儿子已添加时,就换下一个根节点。
            if (!isLeftChild) {
                index++;
            }
            isLeftChild = !isLeftChild;
        }
        return root;
    }
}
View Code

图上的BFS:(与树上的区别在于:图中有环,意味着同一个节点可能重复进入队列)

6.graph-valid-tree(图是否是树)

给出 n 个节点,标号分别从 0 到 n - 1 并且给出一个 无向 边的列表 (给出每条边的两个顶点), 写一个函数去判断这张\'无向\'图是否是一棵树。你可以假设我们不会给出重复的边在边的列表当中。 无向边 [0, 1] 和 [1, 0] 是同一条边, 因此他们不会同时出现在我们给你的边的列表当中。

图是树的标准:1.图有n-1条边(n个点);2.n-1条边把图连通。

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) {
        // Write your code here
        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 LinkedeList<Integer>();
        Set<Integer> hash = new HashSet<Integer>();
        queue.offer(0);
        hash.add(0);
        while (!queue.isEmpty()) {
            int node = queue.poll();
            for (Integer neighbor : graph.get(node)) {
                if (hash.contains(neighbor)) {
                    continue;
                }
                queue.offer(neighbor);
                hash.add(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++) {
        }
        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;
    }
}
View Code

注意:使用Map<Integer, Set<Integer>>来将图进行初始化,Integer存储节点,Set<Integer>存储节点所连接的边。hash表用来判断一个点是否可以进入队列,不要进入两次。最后判断hash表中的节点数等于n就返回true,否则返回false。hash跟queue同时出现。

7.clone-graph(克隆图)

克隆一张无向图,图中的每个节点包含一个 label 和一个列表 neighbors。你的程序需要返回一个经过深度拷贝的新图。这个新图和原图具有同样的结构,并且对新图的任何改动不会对原图造成任何影响。数据中如何表示一个无向图?比如,序列化图 {0,1,2#1,2#2,2} 共有三个节点, 因此包含两个个分隔符#。1.第一个节点label为0,存在边从节点0链接到节点1和节点2。2.第二个节点label为1,存在边从节点1连接到节点2。3.第三个节点label为2,存在边从节点2连接到节点2(本身),从而形成自环。

三步解法:

/**
 * Definition for undirected graph.
 * class UndirectedGraphNode {
 *     int label;
 *     ArrayList<UndirectedGraphNode> neighbors;
 *     UndirectedGraphNode(int x) { label = x; neighbors = new ArrayList<UndirectedGraphNode>(); }
 * };
 */
public class Solution {
    /**
     * @param node: A undirected graph node
     * @return: A undirected graph node
     */
    public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
        // write your code here
        if (node == null) {
            return node;
        }
        //step one:use bfs algorithm to traverse the graph and get all nodes.
        ArrayList<UndirectedGraphNode> nodes = getNodes(node);
        //step two: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));
        }
        //step three: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> hash = new HashSet<>();
        queue.offer(node);
        hash.add(node);
        while (!queue.isEmpty()) {
            UndirectedGraphNode head = queue.poll();
            for (UndirectedGraphNode neighbor : head.neighbors) {
                if (!hash.contains(neighbor)){
                    hash.add(neighbor);
                    queue.offer(neighbor);
                }
            }
        }
        return new ArrayList<UndirectedGraphNode>(hash);
    }
}
View Code

注意:1.使用bfs算法遍历图并获取所有节点;2.复制节点,将old-new映射信息存储在hash映射中;3.复制邻居(边)。

8.search-graph-nodes(搜索图中的节点)

给定一个无向图,一个节点和一个目标值,返回距离给定的节点最近值是target的节点,如果找不到,返回NULL。有一个mapping存储给定参数中的节点值。

例如:{1,2,3,4#2,1,3#3,1#4,1,5#5,4}, [3,4,5,50,50], 1, 50(无向图,mapping,节点,目标值)

/**
 * Definition for graph node.
 * class UndirectedGraphNode {
 *     int label;
 *     ArrayList<UndirectedGraphNode> neighbors;
 *     UndirectedGraphNode(int x) {
 *         label = x; neighbors = new ArrayList<UndirectedGraphNode>();
 *     }
 * };
 */
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 neighbor : head.neighbors) {
                if (!hash.contains(neighbor)) {
                    queue.offer(neighbor);
                    hash.add(neighbor);
                }
            }
        }
        return null;
    }
}
View Code

注意:在BFS模板的基础上增加判断是否等于目标节点的步骤。如果要查找所有最近的value=target的点?使用分层遍历。

拓扑排序:(有向图)

9.topological-sorting(拓扑排序)

给定一个有向图,图节点的拓扑排序被定义为:对于每条有向边A--> B,则A必须排在B之前;拓扑排序的第一个节点可以是任何在图中没有其他节点指向它的节点。找到给定图的任一拓扑排序。

三步解法:

/**
 * Definition for Directed graph.
 * class DirectedGraphNode {
 *     int label;
 *     ArrayList<DirectedGraphNode> neighbors;
 *     DirectedGraphNode(int x) { label = x; neighbors = new ArrayList<DirectedGraphNode>(); }
 * };
 */
public class Solution {
    /**
     * @param graph: A list of Directed graph node
     * @return: Any topological order for the given graph.
     */    
    public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
        // write your code here
        ArrayList<DirectedGraphNode> result = new ArrayList<DirectedGraphNode>();
        HashMap<DirectedGraphNode, Integer> map = new HashMap<DirectedGraphNode, Integer>();
        //step one: collect in-degree
        for (DirectedGraphNode node : graph) {
            for (DirectedGraphNode neighbor : node.neighbors) {
                if (map.containsKey(neighbor)) {
                    map.put(neighbor, map.get(neighbor) + 1);
                } else {
                    map.put(neighbor, 1);
                }
            }
        }
        //step two: put all nodes that indegree = 0 into queue
        Queue<DirectedGraphNode> queue = new LinkedList<DirectedGraphNode>();
        for (DirectedGraphNode node : graph) {
            if (!map.containsKey(node)) {
                queue.offer(node);
                result.add(node);
            }
        }
        //step three: bfs
        while (!queue.isEmpty()) {
            DirectedGraphNode node = queue.poll();
            for (DirectedGraphNode neighbor : node.neighbors) {
                map.put(neighbor, map.get(neighbor) - 1);
                if (map.get(neighbor) == 0) {
                    result.add(neighbor);
                    queue.offer(neighbor);
                }
            }
        }
        return result;
    }
}
View Code

注意:1.收集所有节点的入度;2.将入度为0的节点放入队列;3.bfs。入度(indegree)是终点次数之和,出度(outdegree)是起点次数之和。入度为0才进入队列,不会出现两次入度等于0,所以不需要hash表。使用Map<DirectedGraphNode,Integer>来存储(节点,入度值)。

10.course-schedule(课程安排)

总共有n门课程,从0到n - 1。一些课程可能有先决条件,例如,要学习课程0,你必须先学习1,这表示为一对:[0,1]。鉴于课程总数和先决条件列表,您可以完成所有课程吗?返回结果为true或false。

public class Solution {
    /**
     * @param numCourses a total of n courses
     * @param prerequisites a list of prerequisite pairs
     * @return true if can finish all courses or false
     */
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // Write your code here
        //initialize
        int[] degree = new int[numCourses];
        List[] edges = new ArrayList[numCourses];
        for (int i = 0; i < numCourses; i++) {
            edges[i] = new ArrayList<Integer>();
        }
        //step one: collect indegree 
        for (int i = 0; i < prerequisites.length; i++) {
            degree[prerequisites[i][0]]++;
            edges[prerequisites[i][1]].add(prerequisites[i][0]);
        }
        //step two: put all nodes that indegrees = 0 into queue
        Queue<Integer> queue = new LinkedList<Integer>();
        for (int i = 0; i < numCourses; i++) {
            if (degree[i] == 0) {
                queue.offer(i);
            }
        }
        //step three: bfs
        int count = 0;
        while (!queue.isEmpty()) {
            int course = queue.poll();
            count++;
            int n = edges[course].size();
            for (int i = 0; i < n; i++) {
                int num = (int) edges[course].get(i);
                degree[num]--;
                if (degree[num] == 0) {
                    queue.offer(num);
                }
            }
        }
        return count == numCourses;
    }
}
View Code

注意:依然是三步法的主体思路,具体实现细节有变化。使用int类型数组degree记录每个课程的入度,使用List类型数组edges来记录每个先决条件,在bfs阶段使用count记录已学习的课程数,最后判断是否跟课程总数相同。

11.course-schedule-ii(课程安排II)

你需要去上n门九章的课才能获得offer,这些课被标号为 0 到 n-1 。有一些课程需要“前置课程”,比如如果你要上课程0,你需要先学课程1,我们用一个匹配来表示他们: [0,1]

给你课程的总数量和一些前置课程的需求,返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

public class Solution {
    /**
     * @param numCourses a total of n courses
     * @param prerequisites a list of prerequisite pairs
     * @return the course order
     */
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        // Write your code here
        int[] degree = new int[numCourses];
        List[] edges = new ArrayList[numCourses];
        for (int i = 0; i < numCourses; i++) {
            edges[i] = new ArrayList<Integer>();
        }
        for (int i = 0; i < prerequisites.length; i++) {
            degree[prerequisites[i][0]]++;
            edges[prerequisites[i][1]].add(prerequisites[i][0]);
        }
        Queue<Integer> queue = new LinkedList<Integer>();
        for (int i = 0; i < degree.length; i++){
            if (degree[i] == 0) {
                queue.add(i);
            }
        }
        int count = 0;
        int[] order = new int[numCourses];
        while (!queue.isEmpty()) {
            int course = queue.poll();
            order[count] =菜鸟笔记 -- Chapter 5.4  Dead Code

Breadth-first search

[Leetcode] Breadth-first Search

Breadth First Search

使用 Boost 的图 breadth_first_search() 在未加权、无向图中查找路径

(总结)宽度优先搜索(Breadth First Search)