Leetcode -树

本文详细介绍了二叉树的概念,包括树的结构、二叉树的存储方式,重点讲解了二叉树的遍历方法,如深度优先遍历(前序、中序、后序)和广度优先遍历。此外,还探讨了二叉搜索树(BST)相对于哈希表的优势,并提供了相关题目进行实战练习。

【待更新】

6. 树

6.1 树的结构

Terminology of Tree
在这里插入图片描述

Complete binary search tree完全二叉树

Binary Search Tree

在这里插入图片描述

满二叉树、完全二叉树无数值,从BST开始,internal nodes(非leaf node)包含数值。这里注意,node和左右节点的值可能相同。

AVL Tree

在这里插入图片描述

6.2 二叉树的储存方式

  1. 顺序储存 -> 按照parent -> left child -> right child -> left grandchild of left child -> right gradchild of left child… 的顺序储存在连续内存中 -> 以数组作为容器。
  2. 链式储存,每个node同时包含两指针,分别指向left child和right child,更常用。

6.3 二叉树的遍历方法

  • 深度优先遍历
    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)
  • 广度优先遍历
    • 层次遍历(迭代法)

前中后序遍历的区别在于:中间节点的位置。

前序遍历(中左右):{10, 5, 1, 7, 40, 50}

中序遍历(左中右):{1, 5, 7, 10, 40, 50}

后序遍历(左右中):{1, 7, 5, 50, 40, 10}

     10
   /   \
  5     40
 /  \      \
1    7      50

6.4 Advantages of BST over HashTable

HashTable 在search, insert, delete操作中都能达到O(1),然而自平衡树BST(avl tree, res black tree等)需要O(logn). 除了Time Complexity之外,BST与HashTable的优越性体现在哪些情况下呢?

  1. BST中,中序遍历可以得到sorted结果in O(logn),在HashTable中不是natural operation需要extra effort.
  2. Doing order statistics, finding closest lower and greater elements, doing range queries are easy to do with BSTs
  3. BSTs are easy to implement compared to hashing. Libraries are needed when implementing hasing.
  4. With Self-Balancing BSTs, all operations are guaranteed to work in O(Logn) time. But with Hashing, Θ(1) is average time and some particular operations may be costly i.e, O(n2 ), especially when table resizing happens.

reference

6.5 二叉树相关题目

6.5.1 Define a TreeNode 树节点的定义
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.rihgt = right;
    }
}
6.5.2 BST Traversal 二叉树遍历

虽然以下分类是按照递归、迭代和层序遍历,但层序遍历是和前中后序遍历同级别,对遍历输出结果的要求;而递归和迭代是代码思路和书写方式,可分别应用于前中后及层序遍历。

6.5.2.1 Recursive Traserval 递归遍历

递归法 recursive, O(n), 节点个数

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        preOrder(res, root);
        return res;
    }

    void preOrder(List<Integer> res, TreeNode root){
        if (root == null) return;

        res.add(root.val);
        preOrder(res, root.left);
        preOrder(res, root.right);
    }
}

后序遍历类似,中序遍历在6.5.3,不多赘述。

6.5.2.2 Iterative Traserval 用栈迭代

递归那么简单,为什么要学其他方法?在实际项目开发的过程中我们是要尽量避免递归!因为项目代码参数、调用关系都比较复杂,不容易控制递归深度,甚至会栈溢出。

前序遍历

用栈实现:出栈顺序:中左右,入栈顺序中右左。

具体地,中 -> 出栈 -> 右入栈 -> 左入栈 -> 左出栈 -> 右出栈

class Solution{
    public List<Integer> preorderTraversal(TreeNode root) {
		List<Integer> res = new ArrayList<>();
        if (root == null) return res;
        
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!.stack.isEmpty()){
            TreeNode node = stack.pop();
            res.append(node.val);
            if (node.right != null)
                stack.push(node.right);
            if (node.left != null)
                stack.push(node.left);
        }
        return res;
    }
}

中序遍历

preorder先处理的是根节点,但inorder要先处理最左边,直到左边为null,访问和处理位置不一致,因此使用cur代表访问位置。

class Solution{
	public List<Integer> inorderTraversal(TreeNode){
		List<Integer> res = new ArrayList<>();
        if (root == null) return res;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur!= null || !stack.isEmpty()){
            if (cur != null){
                stack.push(cur);
                cur = cur.left;
            }else{
				cur = stack.pop();
                res.add(cur.val);
                cur = cur.right;
            }
        }
        return res;
    }
}

后序遍历

输出顺序 左右中, 如果颠倒过来的话就是中右左,和前序遍历一样。

把入栈顺序改成中左右,以中右左顺序出栈,再将结果翻转,得到后序遍历结果。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root ==null) return res;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode node = stack.pop();
            res.add(node.val);
            if (node.left != null) stack.push(node.left);
            if (node.right != null) stack.push(node.right);
        }
        Collections.reverse(res);
        return res;
    }
}
6.5.2.3 层序遍历 levelorder

需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。

方法一:recursion递归, DFS

方法二:iteration迭代,BFS

class Solution {
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        checkFun01(root, 0);
        return res;
    }
    /*	Method1, recursion, DFS
    */
    public void checkFun01(TreeNode root, Integer deep){
		if (root == null) return;
        deep++;
        
        if (res.size() < deep){
			// initialise item list in current level
            List<Integer> item = new ArrayList<>();
            res.add(item);
        }
        // 注意这里deep - 1 means 当前deep-1
        res.get(deep-1).add(root.val);
        checkFun01(root.left, deep);
        checkFun01(root.right, deep);
    }
    /*	Method2, using queue, BFS
    */
    public void checkFun02(TreeNode root){
		if (root == null)	return;
        Queue<TreeNode> q = new LinkedList<TreeNode>();
        q.offer(root);
        while (!q.isEmpty()){
            List<Integer> items = new ArrayList<>();
            int len = q.size();
            while (len > 0){
				TreeNode node = q.poll();
                items.add(node.val);
                if (node.left != null) q.offer(node.left); 
                if (node.right != null) q.offer(node.right);
                // 千万别忘记更新参数,不然add null报错
                len--;
            }
            res.add(items);
        }
    }
}

同类型的题目:

剑指 Offer 32 - I. 从上到下打印二叉树
剑指 Offer 32 - III. 从上到下打印二叉树 III
107. 二叉树的层序遍历 II
515. 在每个树行中找最大值

6.5.3 Search and Insertion 搜索和插入

在BST中插入节点之前,首先判断root node是否存在,若不存在则需要创建一棵树。
其次,需要找到合适的插入位置。

伪代码:

if root is null
    then create root node
return
   
if root exists then
    compare the data with node.data
    
    while until the insertion position is located
        
        if data is greater then node.data
            goto right subtree
        else
            goto left subtree
    endwhile
    insert data
end if

Code

class BST{
    // class that defines tree nodes
    class Node{
        int val;
        Node left;
        Node right;
        Node(){}
        Node(int val){this.val = val;}
        Node(int val, Node left, Node right){
            this.val = val;
            this.left = left;
            this.rihgt = right;
        }
    }
    Node root;
    
    // constructors
    // initialisation with an empty tree
    BST(){
		this.root = null;
    }
    BST(int val){
		this.root = new Node(val);
    }
    
    // operations
    // search a node in BST
    Node search(Node root, int value){
        if (root == null || root.val = value){
            return root;
        }
        // 如果val大于根节点的之,在右子树继续找
        if (value > root.val){
			return search(root.right, val);
        }
        // 反之,在左子树继续找
        return search(root.left, val);
    }
    
    /* 
       因为要进行recursive traversal的过程
       就像建立dummyhead和遍历移动的cur指针一样
       建立住function和utility function来递归遍历
       */
    // This method mainly calls insertRec()
	void insert(int val){
		root = insertRec(root, val);
        }
    /* A recursive function to
       insert a new key in BST */
    Node insertRec(Node root, int val){
        if (root == null){
            root = new Node(val);
            return root;
        }
        if (val > root.val){
            // 这里千万注意,是改变了子树
            // 不能call the utility function without assigning it 
            root.right = insertRec(root.right, val);
        }else{
            //相同值放在了做左边节点
			root.left = insertRec(root.left, val);
        }
        /*
        return the unchanged node pointer
        这里需要注意, 由于recursivly改变了左右节点直到insertion location
        最终node pointer返回的是根节点
        */
        return root;        
    }
    
    // This function mainly calls the InorderRec
    void inorder(Node root){
        return inorderRec(root);
    }
    // The utility function traversing revursivly
    void inorderRec(Node root){
        if (root != null){
			inorderRec(root.left);
            System.out.println(root.val);
            inorderRec(root.right);
        }
    }
}

对于Search和Insertion,Time Complexity: O(log(n)) where log(n) is the height of the tree. 对于skewed tree来说,它的高度是n因此in the worst case it takes O(n).

6.5.4 Delete 删除
class BST{
    // Node defination
    Node root;
    // constructors
    
    // This method mainly calls the deleteRec function
    void delete(int val){
		deleteRec(root, val);
    }
    // utility function
    Node deleteRec(Node root, int val){
        // base case: if the tree is empty
        if (root == null){
            return root;
        }
        // otherwise, recur down the tree
        if (val > root.val){
			root.right = deleteRec(root.right, val);
        }else if(val < root.val){
            root.left = deleteRec(root.left, val);
        }
        // val == root.val, delete root 
        else{
        	// root with only one child or no child
            if (root.left == null)
                return root.right;
            else if (root.right == null)
                return root.left;
            
            // node with teo children
            // get the inorder successor
            // find the smallest one in the right subtree
            // 只是更改了root.val
            root.val = minValue(root.right);
            
            // delete the successor
            root.right = deleteRec(root.right, root.val);
        }
        return root;
    }
    
    Node minValue(Node root){
        int minval = root.val;
        while (root.left != null){
            minval = root.left.val;
            root = root.left;
        }
        return minval;
    }
}
6.5.6 Conversion 二叉树的翻转

226. 翻转二叉树

// DFS 
class Solution {
    public TreeNode invertTree(TreeNode root) {
		if (root == null) return root;
        invertTree(root.left);
        invertTree(root.right);
        swapChildren(root);
        return root;
    }
    void swapChildren(TreeNode root){
		TreeNode node = root.left;
        root.left = root.right;
        root.right = node;
    }
}

现在感觉到,DFS就是要考虑最底层节点的情况。

//BFS
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return root;
        ArrayDeque<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        while (!q.isEmpty()){
            int size = q.size();
            while (size-- > 0){
                TreeNode node = q.poll();
                swap(node);
                if (node.left != null) q.offer(node.left);
                if (node.right != null) q.offer(node.right);
            }
        }
        return root;
    }
    void swap(TreeNode node){
        TreeNode tmp = node.left;
        node.left = node.right;
        node.right = tmp;
    }
}
// ArrayDeque<TreeNode> q = new ArrayDeque<>();
// 比Queue<TreeNode> q = new LinkedList<>();
// 占用内存少

BFS时,首先从上到下逐层对节点进行操作。BFS首先考虑清楚辅助数据类型用栈还是队列,用队列的话每层记得用size作为判断循环的条件。

6.4.6 对称二叉树

想到的第一方法是,层序遍历,每层检查是否对称。

但下图这种情况无法解决,不是镜像对称。

在这里插入图片描述

思路:左边前序遍历,中左右。

右边镜像,应为:中右左 ->左右中的翻转。

因此,右边后序遍历,再翻转结果。

然后判断左右两棵树遍历得到的结果是否相同即可。

6.4.7 [Construct BST from preorder]

Reference:
https://www.geeksforgeeks.org/binary-search-tree-set-1-search-and-insertion/
https://www.softwaretestinghelp.com/binary-search-tree-in-java/#Binary_Search_Tree_BST_Implementation_In_Java
https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%BF%AD%E4%BB%A3%E9%81%8D%E5%8E%86.html#%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86-%E8%BF%AD%E4%BB%A3%E6%B3%95

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值