算法五 二叉树与图(Java实现)

411 阅读5分钟

刷题时间:2021.7.28-2021.7.31

图的深度优先遍历

从图中某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通且未被访问的顶点都被访问到。若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

图的广/宽度优先遍历

从图中某个顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问”,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

1、LeetCode113路径总和 II

思路:1.从根节点深度遍历二叉树,先序遍历时,将该节点值存储至path栈中(vector实现),使用path_ value累加节点值。

2.当遍历至叶结点时,检查path_ value值 是否为sum,若为sum,则将path push进入result结果中。

3.在后序遍历时,将该节点值从path栈中弹出,path_value减去节点值。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> result = new ArrayList<>();//存储满足条件路径的数组
        List<Integer> path = new ArrayList<>();//路径栈
        int path_value = 0;//数组值
        preorder(root,path_value,targetSum,path,result);
        return result;
    }
    public void preorder(TreeNode node,int path_value,int sum,List<Integer> path,List<List<Integer>> result)	{
        if(node==null){
            return;
        }
        path_value += node.val;//遍历一个节点即更新一次路径值
        path.add(node.val);
        if(node.left==null && node.right==null && path_value==sum){
            result.add(new ArrayList<>(path));
        }
        preorder(node.left,path_value,sum,path,result);
        preorder(node.right,path_value,sum,path,result);
        path_value -= node.val;
        path.remove(path.size()-1);//遍历完成后,将该节点送路径栈中弹出
    }
}

2、LeetCode236二叉树的最近公共祖先

思路:1两个节点的公共祖先一定在从根节点至这两个节点的路径上。

2.由于求公共祖先中的最近公共祖先,那么即同时出现在这两条路径上的离根节点最远的节点(或离两个最近)。

3.最终算法即:求p节点路径, q节点路径,两路径上最后一个相同的节点。

求根节点至某节点路径(深度搜索):1.从根节点遍历(搜索)至该节点,找到该节点后就结束搜索。

2.将遍历过程中遇到的节点按照顺序存储起来,这些节点即路径节点。

求两路径上最后一个相同的节点:1.求出较短路径的长度n。

2.同时遍历p节点的路径与q节点的路径,遍历n个节点,最后一个发现的相同节点,即最近公共祖先。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        List<TreeNode> path = new ArrayList<>();
        List<TreeNode> node_p_path = new ArrayList<>();//存储p节点路径
        List<TreeNode> node_q_path = new ArrayList<>();//存储q节点路径
        int finish = 0;//记录是否完成搜索的变量
        preorder(root,p,new Stack<>(),node_p_path,finish);
        path.clear();//清空path、finish,计算q节点路径
        finish = 0;
        preorder(root,q,new Stack<>(),node_q_path,finish);
        int path_len = 0;//较短路径的长度
        if(node_p_path.size()<node_q_path.size()){
            path_len = node_p_path.size();
        }else{
            path_len = node_q_path.size();
        }
        TreeNode result = null;//同时遍历根到p、q两个节点的路径上的节点
        for(int i=0;i<path_len;i++){
            if(node_p_path.get(i)==node_q_path.get(i)){
                result = node_p_path.get(i);//找到了最近公共祖先
            }
        }
        return result;
    }
    //正在遍历的节点,待搜索节点,遍历时的节点路径栈,最终搜索到节点search的路径结果,记录是否找到节点search变量
    void preorder(TreeNode node,TreeNode search,Stack<TreeNode> path,List<TreeNode> result,int finish){
        if(node==null || finish!=0){
            return;
        }
        path.add(node);//先序遍历时,将节点压入path栈
        if(node==search){
            finish = 1;//当找到search节点后,标记finish变量
            result.addAll(path);//将当前的path存储到result中
        }
        preorder(node.left,search,path,result,finish);//深度遍历node左孩子
        preorder(node.right,search,path,result,finish);//深度遍历node右孩子
        path.remove(path.size()-1);//结束遍历node时,将node节点弹出path栈
    }
}

LeetCode题解代码

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left == null) return right;
        if(right == null) return left;
        return root;
    }
}

3、LeetCode114二叉树展开为链表

思路:拆解并解决子问题。

(不知道为什么,改写成Java版本的这个代码一直有错,这里就放c++版本的了)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {
        if(!root){
            return;
        }
        TreeNode* last = NULL;
        preorde(root,last);
    }
    void preorde(TreeNode* node,TreeNode* &last){
        if(!node->left&&!node->right){
            last = node;
            return ;
        }
        TreeNode* left = node->left;
        TreeNode* right = node->right;
        TreeNode* left_last = NULL;
        TreeNode* right_last = NULL;
        if(left){
            preorde(left,left_last);
            node->left = NULL;
            node->right = left;
            last = left_last;
        }
        if(right){
            preorde(right,right_last);
            if(left_last){
                left_last->right = right;
            }
            last = right_last;
        }       
    }
};

LeetCode评论代码

class Solution {
    public void flatten(TreeNode root){
        if (root == null)
            return;
        flatten(root.left);
        flatten(root.right);
        // 左子节点不为null的时候才有必要调整
        if (root.left != null){
            TreeNode temp = root.right;
            root.right = root.left;
            root.left = null;
            while (root.right != null){
                root = root.right;
            }
            root.right = temp;
        }
    }
}

4、LeetCode199二叉树的右视图

思路:层次遍历时,将节点与层数绑定为pair,压入队列时,将节点与层数同时压入队列,并记录每一层中出现的最后一个节点。在层次遍历中,每一层中的最后一个节点最后遍历到,随时更新对每层的最后一个节点即可。

LeetCode评论代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        dfs(root, 0, result);
        return result;
    }
    //逆序遍历:根结点-->右节点-->左节点。这样保证每层最先访问的是最右侧的节点,将每层最先访问的节点保存即可
    void dfs(TreeNode root, int depth, List<Integer> result) {//root当前节点,depth节点在哪层,result结果集
        if (root == null) {
            return;
        }
        //当深度在n层,result放入第n该层元素后,则result中元素个数大于该层深度,即深度从0开始。如放完了第二层,result中有2个元素,而第二层的深度索引时1。只有到第3层时,且result未放入第三层的元素,此时深度索引是2,result中的元素个数也是2。也就是首次出现相等的时候加入元素就是该层最右侧的元素
        if (depth == result.size()) {
            result.add(root.val);
        }
        depth++;
        dfs(root.right, depth, result);
        dfs(root.left, depth, result);
    }
}

5、LeetCode207课程表

思路:若有向图无坏,则可以完成全部课程,否则不能。问题转换成,构建图,并判断图是否有环。

方法一:深度优先搜索

在深度优先搜索时,如果正在搜索某一顶点(还未退出该顶点的递归深度搜索),又回到了该顶点,即证明图有环。

LeetCode官方题解:

class Solution {
    List<List<Integer>> edges;
    int[] visited;
    boolean valid = true;
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<List<Integer>>();
        for (int i = 0; i < numCourses; ++i) {
            edges.add(new ArrayList<Integer>());
        }
        visited = new int[numCourses];
        for (int[] info : prerequisites) {
            edges.get(info[1]).add(info[0]);
        }
        for (int i = 0; i < numCourses && valid; ++i) {
            if (visited[i] == 0) {
                dfs(i);
            }
        }
        return valid;
    }
    //节点访问状态,1代表正在访问,0代表没有访问过,2代表已完成访问
    public void dfs(int u) {
        visited[u] = 1;
        for (int v: edges.get(u)) {
            if (visited[v] == 0) {
                dfs(v);
                if (!valid) {
                    return;
                }
            } else if (visited[v] == 1) {
                valid = false;
                return;
            }
        }
        visited[u] = 2;
    }
}

方法二:拓扑排序(宽度优先搜索)

在宽度优先搜索时,只将入度为0的点添加至队列。当完成一个顶点的搜索(从队列取出),它指向的所有顶点入度都减1,若此时某顶点入度为0则添加至队列,若完成宽度搜索后,所有的点入度都为0,则图无环,否则有环。

LeetCode官方题解:

class Solution {
    List<List<Integer>> edges;
    int[] indeg;
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<List<Integer>>();
        for (int i = 0; i < numCourses; ++i) {
            edges.add(new ArrayList<Integer>());
        }
        indeg = new int[numCourses];
        for (int[] info : prerequisites) {
            edges.get(info[1]).add(info[0]);
            ++indeg[info[0]];
        }
        Queue<Integer> queue = new LinkedList<Integer>();
        for (int i = 0; i < numCourses; ++i) {
            if (indeg[i] == 0) {
                queue.offer(i);
            }
        }
        int visited = 0;
        while (!queue.isEmpty()) {
            ++visited;
            int u = queue.poll();
            for (int v: edges.get(u)) {
                --indeg[v];
                if (indeg[v] == 0) {
                    queue.offer(v);
                }
            }
        }
        return visited == numCourses;
    }
}