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;
}

参考:

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


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

posted on 2018-12-13 19:14  binarylei  阅读(1169)  评论(0编辑  收藏  举报

导航