zoukankan      html  css  js  c++  java
  • Java 数据结构

    Java 数据结构 - 二叉树概念:什么样的二叉树可以使用数组存储

    数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html)

    前面讲到的链表、栈和队列都是一对一的线性结构,这节讲一对多的线性结构 - 树。「一对多」就是指一个元素只能有一个前驱,但可以有多个后继。

    关于二叉树的学习,分为以下几个部分:

    • 二叉树基本概念:树的高度、深度、层次等概念。完全二叉树有什么意义?
    • 二叉查找树:一种有序的二叉树。有了哈希表这种插入、删除、查找时间复杂度都是 O(1) 的数据结构,为什么还需要二叉查找树?
    • 红黑树:为什么即使红黑树不完全符合平衡二叉查找树的定义,但实际软件工程中使用的平衡二叉查找树都是红黑树?

    1. 树的概念

    • 度(Degree) :节点拥有的子树数。树的是树中各个节点度的最大值。
    • 节点 :度为 0 的节点称为叶节点(Leaf)或终端节点。度不为 0 的节点称为分支节点。除根节点外,分支节点也被称为内部节点。
    • 节点关系 :节点的子树的根称为该节点的孩子(Child)。该结点称为孩子的双亲或父结点。同一个双亲的孩子之间互称为兄弟
    • 节点的层次 :树的层次从根开始,根为第一层,根的孩子为第二层。双亲在同一层的节点互为堂兄弟。树的深度(Depth)也是从根结点开始计算,与层次不同的是从 0 开始计算。树的高度则相反,从叶子结点开始计算,也是从 0 开始计算。
    • 有序树 :如果将树中节点的各个子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。也称为查找树搜索树
    • 森林 :m(m>=0) 棵互不相交的树的集合。

    2. 二叉树

    2.1 二叉树分类

    二叉树(Binary Tree)是树的特殊一种,具有如下特点:

    1. 每个结点最多有两颗子树,结点的度最大为 2。
    2. (树自身特征)左子树和右子树是有顺序的,次序不能颠倒。
    3. (树自身特征)即使某结点只有一个子树,也要区分左右子树。

    二叉树可以分为以下几大类:

    • 斜树:完全退化为链表。

    • 满二叉树:所有的分支结点都存在左子树和右子树,并且所有的叶子结点都在同一层上,这样就是满二叉树。

    • 完全二叉树:之所以将完全二叉树单独拿出来讲,是因为完全二叉树的所有结点,刚好可以全部放到数组中而不浪费任何空间。对于完全二叉树的任意结点 arr[i],那么其左子树为 arr[2i] ,右子树为 arr[2i + 1]。所以完全二叉树很多时候是用数组进行存储的。

    • 平衡二叉树(Balanced Binary Tree):一棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的意义在于,维护树的高度在 logn,避免树退化为链表,导致查找的时间复杂度由 O(logn) 降为 O(n)。

      平衡二叉树的常用实现方法有 "AVL" 或 "红黑树" 两种,但实际工程中使用的都是红黑树。

    2.2 二叉树存储方式

    二叉树同样有链表存储数组存储二种方式。基于指针的二叉链式存储法非常直观,就不多说了。我们重点分析一下基于数组的顺序存储。

    使用数组存储时,数组 arr[0] 不存储任何结点。对于二叉树任意结点 arr[i],其左子树为 arr[2i] ,右子树为 arr[2i + 1]。如果是完全二叉树,那么其所有的结点都刚好可以存储在数组中,并且没有浪费任何存储空间。但如果不是完全二叉树,如上图所示,arr[5] 就浪费了。

    3. 二叉树基本操作

    3.1 树的遍历

    二叉树根据父结点的访问顺序,分为前序遍历、中序遍历、后序遍历三种情况。

    (1)前序遍历

    // 递归实现前序遍历
    public void preOrder() {
        System.out.printf("%s ", value);
        if (left != null) {
            left.preOrder1();
        }
        if (right != null) {
            right.preOrder1();
        }
    }
    
    // 非递归实现前序遍历
    public void preOrder1() {
        TreeNode<E> head = this;
        Stack<TreeNode<E>> stack = new Stack();
        stack.push(head);
        while (!stack.isEmpty()) {
            TreeNode<E> pop = stack.pop();
            System.out.printf("%s ", head.value);
            if (pop.right != null) {
                stack.push(pop.right);
            }
            if (pop.left != null) {
                stack.push(pop.left);
            }
        }
    }
    

    (2)中序遍历

    // 递归实现中序遍历
    public void midOrder() {
        if (left != null) {
            left.preOrder1();
        }
        System.out.printf("%s ", value);
        if (right != null) {
            right.preOrder1();
        }
    }
    
    // 非递归实现中序遍历
    public void midOrder1() {
        TreeNode<E> head = this;
        Stack<TreeNode<E>> stack = new Stack();
        while (!stack.isEmpty() || head != null) {
            if (head != null) {
                // 先将左结点全部入栈
                stack.push(head);
                head = head.left;
            } else {
                // 左结点全部入栈后就需要依次弹出,并处理右结点
                head = stack.pop();
                System.out.printf("%s ", head.value);
                head = head.right;
            }
        }
    }
    

    (3)后序遍历

    // 递归实现后序遍历
    public void postOrder() {
        if (left != null) {
            left.preOrder1();
        }
        if (right != null) {
            right.preOrder1();
        }
        System.out.printf("%s ", value);
    }
    
    // 非递归实现后序遍历
    public void postOrder2() {
        TreeNode<E> head = this;
        Stack<TreeNode<E>> stack1 = new Stack();
        Stack<TreeNode<E>> stack2 = new Stack();
        stack1.push(head);
        while (!stack1.isEmpty()) {
            TreeNode<E> tmp = stack1.pop();
            stack2.push(tmp);
            if (tmp.left != null) {
                stack1.push(tmp.left);
            }
            if (tmp.right != null) {
                stack1.push(tmp.right);
            }
        }
        while (!stack2.isEmpty()) {
            TreeNode<E> tmp = stack2.pop();
            System.out.printf("%s ", tmp.value);
        }
    }
    

    (4)层次遍历

    public void levelOrder() {
        TreeNode<E> head = this;
        Queue<TreeNode<E>> queue = new ArrayDeque<>();
        queue.offer(head);
        while (!queue.isEmpty()) {
            for (int i = 0; i < queue.size(); i++) {
                TreeNode<E> tmp = queue.poll();
                System.out.printf(String.valueOf(tmp.value) + " ");
                if (tmp.left != null) {
                    queue.offer(tmp.left);
                }
                if (tmp.right != null) {
                    queue.offer(tmp.right);
                }
            }
        }
    }
    

    3.2 树的深度

    // 非递归求树的最大和最小深度
    public int maxLevel() {
        int level = 0;
        TreeNode<E> head = this;
        Queue<TreeNode<E>> queue = new ArrayDeque<>();
        queue.offer(head);
        while (!queue.isEmpty()) {
            for (int i = 0; i < queue.size(); i++) {
                level++;
                TreeNode<E> tmp = queue.poll();
                if (tmp.left != null) {
                    queue.offer(tmp.left);
                }
                if (tmp.right != null) {
                    queue.offer(tmp.right);
                }
            }
        }
        return level;
    }
    
    public int minLevel() {
        int level = 0;
        TreeNode<E> head = this;
        Queue<TreeNode<E>> queue = new ArrayDeque<>();
        queue.offer(head);
        while (!queue.isEmpty()) {
            for (int i = 0; i < queue.size(); i++) {
                level++;
                TreeNode<E> tmp = queue.poll();
                if (tmp.left == null && tmp.right == null) {
                    return level;
                }
                if (tmp.left != null) {
                    queue.offer(tmp.left);
                }
                if (tmp.right != null) {
                    queue.offer(tmp.right);
                }
            }
        }
        return 0;
    }
    
    // 递归求树的最大和最小深度
    public int minLevel(TreeNode head) {
        if (head == null) {
            return 0;
        }
        if (head.left == null && head.right == null) {
            return 1;
        }
        if (head.left == null && head.right != null) {
            return minLevel(head.left) + 1;
        }
        if (head.left != null && head.right == null) {
            return minLevel(head.right) + 1;
        }
        return Math.min(minLevel(head.left), minLevel(head.right)) + 1;
    }
    

    3.3 求公共祖先结点

    // 递归求两个结点的公共祖先,一个结点可以是自己的祖先
    public TreeNode ancestor(TreeNode root, TreeNode node1, TreeNode node2) {
        if (root == node1 || root == node2) {
            return root;
        }
        TreeNode left = ancestor(root.left, node1, node2);
        TreeNode right = ancestor(root.right, node1, node2);
        if (left == null || right == null) {
            return root;
        }
        return left != null ? left : right;
    }
    

    参考:

    数据结构(八)--平衡二叉树


    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    APDU:APDU常用指令
    其他:IntelliJ IDEA设置运行内存
    Linux:获取Linux离线postgresql数据库安装包并部署
    资源:Postgresql数据库下载路径
    免费版:Xshell和Xftp下载路径
    Raspberry Pi:树莓派开发板配置USB启动系统
    Raspberry Pi:树莓派安装基础系统 Raspberry Pi(树莓派系统)
    Raspberry Pi:树莓派安装Kali2021新版本
    Docker:虚拟机挂起后,再启动docker容器连接失败
    Docker:docker搭建redis一主多从集群(配置哨兵模式)
  • 原文地址:https://www.cnblogs.com/binarylei/p/10115838.html
Copyright © 2011-2022 走看看