树 (二叉树)--- (内含树(二叉树)的概念、二叉树性质、遍历(非递归)、习题)永不过时的数据结构

树的定义:

树是一种非线性的数据结构,它是由有限个(n>=0)节点组成一个具有层次关系的集合。

树最顶端的顶点称为根节点,根节点没有前期节点。(如下A点就被称为根节点)

子树就是整一颗树的其中一个枝头。如下:

树形结构中,子树之间不能有交集,否则就不是树形结构。比如:

树的相关概念:

结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为3 。 树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为3 。 叶子结点或终端结点:度为0的结点称为叶结点; 如上图:F、I、J、K...等节点为叶结点 。 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是C的父结点 。 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:C是A的孩子结点 。 根结点:一棵树中,没有双亲结点的结点;如上图:A 点。 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推 。 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4 。 非终端结点或分支结点:度不为0的结点; 如上图:C、E、G、H等节点为分支结点 。 兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:C,D是兄弟结点 。 堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:I,K互为堂兄弟结点 。 结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙 。 森林:由m(m>=0)棵互不相交的树组成的集合称为森林 。

树的表现形式:

如果树的度太大,那么我们一般用孩子兄弟表示法。

public class Tree {
    private int value;
    private Tree firstChild;//第一个孩子
    private Tree nextBrother;//下一个兄弟的引用
}

二叉树:

二叉树是所有节点最大的度为2的树。

任意两个二叉树都是由以下几种情况复合而成的:

两种特殊的二叉树:

1.满二叉树:

一颗二叉树,每层节点的个数都达到最大值。如下:

它每一层的节点个数为2^(h-1),h为第h层的意思。总节点个数为2^k-1,k为层数。

2.完全二叉树:

简单来讲就是在h<k的位置是满二叉树 ,在h==k的那层叶子节点是从左往右没有空缺的。如下图:

二叉树的性质:

1. 若规定根结点的层数为1,则一棵非空二叉树第i层上最多有 (i>0)个结点 。 2. 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (k>=0) 。 3. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1 。 4. 具有n个结点的完全二叉树的深度k为 log2(n + 1)上取整 。 5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i 的结点有: 若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点 。 若2i+1<n,左孩子序号:2i+1,否则无左孩子 。 若2i+2<n,右孩子序号:2i+2,否则无右孩子 。

二叉树的存储结构:

1.顺序存储

顺序存储就是用数组来存储,比如:

这颗树的顺序存储形式就是:

一般顺序存储比较适合完全二叉树,因为不会有空间浪费:

二叉树顺序存储在物理上是一个数组,在逻辑上是一颗树。

2.链式存储

链式存储是用链表来表示一颗二叉树,通常的方法是每个节点包含3个域,左指针域、右指针域、数据域:

    static class TreeNode {
        public char val;
        public TreeNode left;//左孩子的引用
        public TreeNode right;//右孩子的引用

        public TreeNode(char val) {
            this.val = val;
        }
    }

左指针域来表示左孩子,右指针域来表示右孩子。

等到后面高阶数据结构还会出现三叉链,就是在原来二叉链的基础上多出一个父指针域,在红黑树中会使用到。

二叉树的遍历:

遍历就是沿着某种路线对树进行访问,是二叉树十分重要的功能。

二叉树的前序遍历:

    // 前序遍历
    public void preOrder(TreeNode root) {
        if(root == null) {//如果该节点为null,返回
            return;
        }
        System.out.print(root.val + "  ");//先访问父节点
        preOrder(root.left);//再访问左节点
        preOrder(root.right);//最后访问右节点
    }
非递归:
public void PreOrderNoRecursion(TreeNode root) {
        if (root == null) {//如果根节点为空就不运行了
            return;
        }

        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);//先把头节点放入栈中
        while (!stack.empty()) {
            TreeNode cur = stack.pop();
            System.out.print(cur.val + "  ");//先打印栈顶元素
            if (cur.right != null) {
                stack.push(cur.right);//注意这里要先放右节点!!!
            }
            if (cur.left != null) {
                stack.push(cur.left);//再放左节点
            }
        }
        System.out.println();
    }

二叉树的中序遍历:

// 中序遍历
void inOrder(TreeNode root) {
        if(root == null) {//如果该节点为null,返回
            return;
        }
        inOrder(root.left);//先访问左节点
        System.out.print(root.val + "  ");//再访问父节点
        inOrder(root.right);//最后访问右节点
    }
非递归:
//中序变量无递归版
    public void inOrderNoRecursion(TreeNode root) {
        if (root == null ) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !stack.isEmpty()) {
            while(cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            cur = stack.pop();
            System.out.print(cur.val + "  ");
            cur = cur.right;
        }
        System.out.println();
    }

二叉树的后序遍历:

// 后序遍历
    void postOrder(TreeNode root) {
        if(root == null) {//如果该节点为null,返回
            return;
        }
        postOrder(root.left);//先访问左节点
        postOrder(root.right);//再访问右节点
        System.out.print(root.val + "  ");//最后访问父节点
    }
非递归:
public void postOrderNoRecursion(TreeNode root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode prev = null;//记录着前一个被打印的节点

        while (cur != null || !stack.empty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }

            TreeNode top = stack.peek();
            if (top.right == null || top.right == prev) {
                //打印的条件是1.这个节点的左儿子为空或者被打印过且
                //2.右儿子为空或者有被打印过
                //第一个条件可以不显式的写出来,因为当程序执行到TreeNode top = stack.peek();时
                //会被自动筛掉
                System.out.print(top.val + "  ");
                prev = top;//更新prev
                stack.pop();//别忘记打印了的要出栈
            } else {
                //这种情况时这个节点还有右孩子且右孩子没有被打印
                cur = top.right;
            }
        }
        System.out.println();
    }

二叉树的层序遍历:

//层序遍历
    void levelOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int size = 1;
        while(!queue.isEmpty()) {
            while (size -- > 0) {
                TreeNode cur = queue.poll();
                System.out.print(cur.val + "  ");
                if(cur.left != null) {
                    queue.offer(cur.left);
                }
                if(cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            size = queue.size();
            System.out.println();
        }
    }

获取二叉树的一些属性:

获取树中节点的个数:

    //方法1
    public static int nodeSize;//利用静态成员变量
    void size(TreeNode root) {
        if(root == null) {
            return;
        }
        nodeSize++;
        size(root.left);
        size(root.right);
    }


    //方法2
    int size2(TreeNode root) {
        if (root == null) {
            return 0;
        }

        int sizeleft = size2(root.left);
        int sizeright  = size2(root.right);
        
        return  sizeleft + sizeright + 1;
    }

获取叶子节点的个数:


     //方法1
    public static int leafSize = 0;//利用静态成员变量

    void getLeafNodeCount1(TreeNode root) {
        if(root == null) {
            return;
        }
        if(root.left == null && root.right == null) {
            leafSize++;
        }
        getLeafNodeCount1(root.left);
        getLeafNodeCount1(root.right);
    }

    //方法2
    int getLeafNodeCount2(TreeNode root) {
        if (root == null) {
            return 0;
        }

        if (root.left == null && root.right == null) {
            return 1;
        }
        return getLeafNodeCount2(root.left) + getLeafNodeCount2(root.right);
    }

获取第K层节点的个数:

    int getKLevelNodeCount(TreeNode root, int k) {
        if(root == null) {
            return 0;
        }
        if(k == 1) {//只有第k层的节点才加上
            return 1;//注意这里是1,表示已经到达了第k层了。
        }
        int sizeLeft = getKLevelNodeCount(root.left, k - 1);
        int sizeRight = getKLevelNodeCount(root.right, k - 1);
        return sizeLeft + sizeRight;
    }

获取二叉树的高度:

/*
     获取二叉树的高度
     时间复杂度:O(N)
     */
    int getHeight(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int sizeLeft = getHeight(root.left);
        int sizeRight = getHeight(root.right);
        return sizeLeft > sizeRight ? (sizeLeft + 1) : (sizeRight + 1);
    }

检测值为value的元素是否存在:

// 检测值为value的元素是否存在
TreeNode find(TreeNode root, char val) {
        if (root == null) {
            return null;
        }
        
        if (root.val == val) {
            return root;//如果找到了,返回这个节点
        }
        
        TreeNode cur1 = find(root.left, val);//创建一个节点来存储左子树之前返回的结果
        if (cur1 != null) {//如果结果不为空则表示找到了
            return cur1;
        }
        
        TreeNode cur2 = find(root.left, val);//创建一个节点来存储右子树之前返回的结果
        return cur2;//执行到这一步表示左子树没有找到,那么无论有没有找到都返回右子树的解
    }

判断一棵树是不是完全二叉树:

// 判断一棵树是不是完全二叉树
    boolean isCompleteTree(TreeNode root) {
        if(root == null) {
            return true;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty())
        {
            TreeNode cur = queue.poll();
            if(cur != null) {
                queue.offer(cur.left);
                queue.offer(cur.right);
            }
            else {
                break;
            }
        }
        while (!queue.isEmpty()) {
            if (queue.poll() != null) {
                return false;
            }
        }
        return true;
    }

练习:

选择:

  1. 某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为()A: ABDHECFG B: ABCDEFGH C: HDBEAFCG D: HDEBFGCA

2.二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为()A: E B: F C: G D: H

3.设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为()A: adbce B: decab C: debac D: abcde

4.某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为()A: FEDCBA B: CBAFED C: DEFCBA D: ABCDEF

答案:1.A 2.A 3.D 4.A

编程题:

1. 检查两颗树是否相同。100. 相同的树 - 力扣(LeetCode)

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q != null || p != null && q == null) {
            return false;
        }

        if (p == null && q == null) {
            return true;
        }

        if (p.val != q.val) {
            return false;
        }

        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

2. 另一颗树的子树。572. 另一棵树的子树 - 力扣(LeetCode)

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q != null || p != null && q == null) {
            return false;
        }

        if (p == null && q == null) {
            return true;
        }

        if (p.val != q.val) {
            return false;
        }

        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }

    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if (root == null) { //如果走到空了都没找到返回false
            return false;
        }

        if (isSameTree(root, subRoot)) {
            return true;//看看这颗子树是不是相同的
        }

        if (isSubtree(root.left, subRoot)) {
            return true;//不相同的话去左子树找
        }

        if (isSubtree(root.right, subRoot)) {
            return true;//还是不相同的话去右子树找
        }

        return false;//都找过了,都找不到返回false
    }
}

3. 翻转二叉树。226. 翻转二叉树 - 力扣(LeetCode)

class Solution {
    public void invert(TreeNode root) {
        if (root == null) {
            return;
        }

        TreeNode l = root.left;
        TreeNode r = root.right;
        root.left = r;
        root.right = l;
        invert(root.left);
        invert(root.right);
    }

    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }

        invert(root);

        return root;
    }
}

4. 判断一颗二叉树是否是平衡二叉树。110. 平衡二叉树 - 力扣(LeetCode)

class Solution {
    public int getHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }

        int leftHeight = getHeight(root.left);
        int rightHeight = getHeight(root.right);

        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

    public boolean isBalanced(TreeNode root) {
        if (root == null) {
            return true;
        }

        if (Math.abs(getHeight(root.left) - getHeight(root.right)) > 1) {
            return false;
        }

        return isBalanced(root.left) && isBalanced(root.right);
    }
}

5. 对称二叉树。101. 对称二叉树 - 力扣(LeetCode)

class Solution {
    public boolean isSym(TreeNode l, TreeNode r) {
        if (l == null && r == null) {
            return true;
        } else if (l == null && r != null || l != null && r == null) {
            return false;
        }

        if (l.val != r.val) {
            return false;
        }

        if (l.left == null && r.right != null || l.left != null && r.right == null) {
            return false;
        }

        if (l.right == null && r.left != null || l.right != null && r.left == null) {
            return false;
        }

        if (l.left != null && r.right != null && l.left.val != r.right.val) {
            return false;
        } 

        if (l.right != null && r.left != null && l.right.val != r.left.val) {
            return false;
        }

        return isSym(l.left, r.right) && isSym(l.right, r.left);

    }

    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }
        return isSym(root.left, root.right);
    }
}

6. 二叉树的构建及遍历。二叉树遍历_牛客题霸_牛客网 (nowcoder.com)

import java.util.Scanner;

    class TreeNode {
        char val;
        TreeNode left;
        TreeNode right;
        public TreeNode (char value) {
            val = value;
        }
    }
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    static int i;
    public static TreeNode CreateTree(String str) {
        TreeNode cur = null;
        if (str.charAt(i) != '#') {
            cur = new TreeNode(str.charAt(i));
            i++;
            cur.left = CreateTree(str);
            cur.right = CreateTree(str);
        }
        else {
            i++;
        }
        return cur;
    }

    static public void inOder(TreeNode root) {
        if (root == null) {
            return;
        }

        inOder(root.left);
        System.out.print(root.val + " ");
        inOder(root.right);
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.next();
        TreeNode root = CreateTree(str);
        inOder(root);
    }
}

7. 二叉树的分层遍历 。102. 二叉树的层序遍历 - 力扣(LeetCode)

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list = new LinkedList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        if (root == null) {
            return list;
        } 

        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            LinkedList<Integer> newlist = new LinkedList<>();
            while (size-- > 0) {
                TreeNode top = queue.poll();
                if (top.left != null) {
                    queue.offer(top.left);
                }

                if (top.right != null) {
                    queue.offer(top.right);
                }
                newlist.add(top.val);
            }
            list.add(newlist);
        }

        return list;
    }
}

8. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。236. 二叉树的最近公共祖先 - 力扣(LeetCode)

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

        root.left = lowestCommonAncestor(root.left, p, q);
        root.right = lowestCommonAncestor(root.right, p, q);

        if (root.left != null && root.right != null) {
            return root;
        } else if (root.left != null) {
            return root.left;
        } else if (root.right != null) {
            return root.right;
        }

        return null;
    }
}

9. 根据一棵树的前序遍历与中序遍历构造二叉树。 105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

class Solution {
    public int i = 0;
    public TreeNode buildTree(int[] preorder, int[] inorder) {       
        return PreIn(preorder, inorder, 0, preorder.length - 1);
    }

    public TreeNode createByPreIn(int[] preorder, int[] inorder, int begin, int end) {
        if (begin > end) {
            return null;
        }

        TreeNode root = new TreeNode(preorder[i]);
        int index = findIndex(inorder, preorder[i]);
        i ++;

        root.left = PreIn(preorder, inorder, begin, index - 1);
        root.right = PreIn(preorder, inorder, index + 1, end);

        return root;

    }

    public static int findIndex(int[] inorder, int key) {
        for (int j = 0; j < inorder.length; j++) {
            if (inorder[j] == key) {
                return j;
            }
        }

        return -1;
    }
}

10. 根据一棵树的中序遍历与后序遍历构造二叉树。106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

class Solution {
    public int i = 0;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        i = inorder.length - 1;
        return createByInPost(inorder, postorder, 0, inorder.length - 1);
    }

    public TreeNode createByInPost(int[] inorder, int[] postorder, int begin, int end) {
        if(begin > end) {
            return null;
        }
        TreeNode root = new TreeNode(postorder[i]);
        int index = FindIndex(inorder, begin, end, postorder[i]);
        i--;
        
        root.right = createByInPost(inorder, postorder, index + 1, end);
        //一定要先创建右子树再创建左子树,因为postoder从后往前遍历是根->右->左
        root.left = createByInPost(inorder, postorder, begin, index - 1);
        return root;
    }

    public int FindIndex(int[] inorder, int ibegin, int iend, int key) {
        for(int j = ibegin; j <= iend; j++) {
            if(inorder[j] == key) {
                return j;
            }
        }
        return -1;
    }
}

11. 二叉树创建字符串。606. 根据二叉树创建字符串 - 力扣(LeetCode)

class Solution {
    public String tree2str(TreeNode root) {
        StringBuffer str = new StringBuffer();
        dfs(root, str);

        return str.toString();
    }

    public void dfs(TreeNode root, StringBuffer str) {
        if (root == null) {
            return;
        }

        str.append(root.val);

        if (root.left == null && root.right == null) {
            return;
        }

        str.append("(");
        dfs(root.left, str);
        str.append(")");

        if (root.right != null) {
            str.append("(");
            dfs(root.right, str);
            str.append(")");
        }
    }

}

小总结:二叉树的问题普遍都很难,大家要好好消化,这部分内容非常重要!!!

不怕溺于尘埃人海,只愿灵魂永站高台_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值