1、概述

通过前面的学习,我们知道,有序数组可以利用二分查找法快速的查找特定的值,时间复杂度为O(log2N),但是插入数据时很慢,时间复杂度为O(N);链表的插入和删除速度都很快,时间复杂度为O(1),但是查找特定值很慢,时间复杂度为O(N)。

树能解决上述问题
普通的非二叉树:
二叉树 - 图1

二叉树

如果树的每个节点最多有两个子节点,则称为二叉树。如果节点的子节点可以多余两个,称为多路树

二叉树 - 图2

满二叉树

如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。

二叉树 - 图3

完全二叉树

如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。
二叉树 - 图4

二叉树的存储结构

顺序存储

二叉树的顺序存储,指的是使用顺序表(数组)存储二叉树。需要注意的是,顺序存储只适用于完全二叉树。换句话说,只有完全二叉树才可以使用顺序表存储。因此,如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树。
如下所示左边的非二叉树转换为完全二叉树,对应下面的数组
二叉树 - 图5
二叉树 - 图6
我们知道,完全二叉树具有这样的性质,将树中节点按照层次并从左到右依次标号(1,2,3,…),若节点 i 有左右孩子,则其左孩子节点为 2i,右孩子节点为 2i+1。
缺点:浪费空间

链式存储

二叉树 - 图7

  • 指向左孩子节点的指针(Lchild);
  • 节点存储的数据(data);
  • 指向右孩子节点的指针(Rchild);

二叉树的遍历算法

中序

递归

  1. public void inorder(Node currentRoot){
  2. if(currentRoot != null){
  3. inorder(currentRoot.leftChild); //先对当前节点的左子树对进行中序遍历
  4. System.out.print(currentRoot.age+"\t"); //然后访问当前节点
  5. inorder(currentRoot.rightChild); //最后对当前节点的右子树对进行中序遍历
  6. }
  7. }

非递归

ArrayList<Integer> arr = new ArrayList<Integer>();
    public void middleOrder(TreeNode root) {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        while(root!=null||!stack.isEmpty()) {
            while(root!=null) {
                stack.push(root);
                root = root.left;
            }
            if(!stack.isEmpty()) {
                root = stack.pop();
                arr.add(root.val);
                root = root.right;
            }
        }
    }

先序

后序

层次

二叉搜索树

二叉搜索树的特点是,一个节点的左子节点的关键字值小于这个节点,右子节点的关键字值大于或等于这个父节点
平衡树非平衡树,非平衡就是说树的大部分节点在根的一边,如下图所示:

二叉树 - 图8

查找

我们已经知道,二叉搜索树的特点是左子节点小于父节点,右子节点大于或等于父节点。查找某个节点时,先从根节点入手,如果该元素值小于根节点,则转向左子节点,否则转向右子节点,以此类推,直到找到该节点,或者到最后一个叶子节点依然没有找到,则证明树中没有该节点。
比如我们要在树中查找57,执行的搜索路线如下图所示:
二叉树 - 图9
代码

public Node find(int key) 
    {
        Node cur = root;
        if(cur==null) {
            return null;
        }
        while(cur.age!=key) {
            if(cur.age<key) {
                cur = cur.rightChild;
            }else {
                cur = cur.leftChild;
            }
            if(cur==null) {
                return null;
            }
        }
        return cur;        
    }

插入

二叉树 - 图10
插入的节点作为叶子节点
代码

//插入新节点
    public void insert(Node node) {
        if(root==null) {
            root = node;
        }else {
            Node cur = this.root;
            while(true) {
                if(node.age<cur.age) {
                    if(cur.leftChild==null) {
                        cur.leftChild = node;
                        return;
                    }
                    cur = cur.leftChild;
                }else {
                    if(cur.rightChild==null) {
                        cur.rightChild = node;
                        return;
                    }
                    cur = cur.rightChild;
                }
            }

        }        
    }

遍历

二叉树 - 图11
代码

//遍历
    public static final int PREORDER = 1;   //前序遍历
    public static final int INORDER = 2;    //中序遍历
    public static final int POSTORDER = 3;  //中序遍历

    //遍历
    public void traverse(int type){
        switch(type){
        case 1:
            System.out.print("前序遍历:\t");
            preorder(root);
            System.out.println();
            break;
        case 2:
            System.out.print("中序遍历:\t");
            inorder(root);
            System.out.println();
            break;
        case 3:
            System.out.print("后序遍历:\t");
            postorder(root);
            System.out.println();
            break;
        }
    }
    //前序遍历
    public void preorder(Node currentRoot) {
        if(currentRoot!=null) {
            System.out.println(currentRoot.age+"\t");
            preorder(currentRoot.leftChild);
            preorder(currentRoot.rightChild);
        }        
    }
    //中序遍历,这三种遍历都用了迭代的思想
    public void inorder(Node currentRoot){
        if(currentRoot != null){
            inorder(currentRoot.leftChild);  //先对当前节点的左子树对进行中序遍历
            System.out.print(currentRoot.age+"\t"); //然后访问当前节点
            inorder(currentRoot.rightChild);  //最后对当前节点的右子树对进行中序遍历
        }
    }

    //后序遍历
    public void postorder(Node currentRoot){
        if(currentRoot != null){
            postorder(currentRoot.leftChild);
            postorder(currentRoot.rightChild);
            System.out.print(currentRoot.age+"\t");
        }
    }

查找最值

在二叉搜索树中,查找最大值、最小是是很容易实现的,从根循环访问左子节点,直到该节点没有左子节点为止,该节点就是最小值;从根循环访问右子节点,直到该节点没有右子节点为止,该节点就是最大值。

 //返回关键值最大的节点,也就对右节点不断地遍历
    public Node getMax(){
        if(isEmpty()){
            return null;
        }
        Node cur = root;
        while(cur.rightChild != null){
            cur = cur.rightChild;
        }
        return cur;
    }

    //返回关键值最小的节点,对左节点不断的遍历
    public Node getMin(){
        if(isEmpty()){
            return null;
        }
        Node cur = root;
        while(cur.leftChild != null){
            cur = cur.leftChild;
        }
        return cur;
    }

删除节点

思路大致分三种

  • 该节点没有子节点
  • 该节点有一个子节点
    • 无论要删除的节点下面有多复杂的子树,只需要将它的子树上移
    • 还有一种特殊情况需要考虑,就是要删除的是根节点
  • 该节点有两个子节点
    • 首先找到后继节点,然后分析后继节点的情况
      • 一种是欲删除节点的右子节点没有左子节点,那么它本身就是后继节点
      • 另一种情况是欲删除节点的右子节点有左子节点

        完整代码

        ```java package my;

import java.util.ArrayList; import java.util.List;

import p.Node;

public class BinaryTree { private Node root;

public BinaryTree() {
    root = null;
}
//查找节点
public Node find(int key) 
{
    Node cur = root;
    if(cur==null) {
        return null;
    }
    while(cur.age!=key) {
        if(cur.age<key) {
            cur = cur.rightChild;
        }else {
            cur = cur.leftChild;
        }
        if(cur==null) {
            return null;
        }
    }
    return cur;        
}
//插入新节点
public void insert(Node node) {
    if(root==null) {
        root = node;
    }else {
        Node cur = this.root;
        while(true) {
            if(node.age<cur.age) {
                if(cur.leftChild==null) {
                    cur.leftChild = node;
                    return;
                }
                cur = cur.leftChild;
            }else {
                if(cur.rightChild==null) {
                    cur.rightChild = node;
                    return;
                }
                cur = cur.rightChild;
            }
        }

    }        
}
//删除指定节点
public boolean delete(Node node) {
    if(root==null) {
        return false;
    }
    boolean isLeftChild = true;
    Node parent = null;
    Node cur = root;

    while(cur.age!=node.age) {
        parent = cur;
        if(node.age<cur.age) {
            cur = cur.leftChild;
        }else {
            isLeftChild = false;//记录要删除节点是否是在父节点的左子节点
            cur = cur.rightChild;
        }
        if(cur == null) {
            return false;//没有找到要删除的节点
        }
    }
    if(cur.leftChild==null&&cur.rightChild==null) {//目标节点为叶子节点
        if(cur==root) {
            root = null;
        }else if(isLeftChild) {
            parent.leftChild = null;
        }else {
            parent.rightChild = null;
        }
    }else if(cur.leftChild==null) {//删除点只有一个右子节点
        if(cur==root) {
            root = cur.rightChild;
        }else if(isLeftChild) {//判断删除节点是父节点的左还是右节点
            parent.leftChild = cur.rightChild;
        }else {
            parent.rightChild = cur.rightChild;
        }
    }else if(cur.rightChild==null) {//删除点只有一个左子节点
        if(cur==root) {
            root = cur.leftChild;
        }else if(isLeftChild){//判断删除节点是父节点的左还是右节点
            parent.leftChild = cur.leftChild;
        }else {
            parent.rightChild = cur.leftChild;
        }
    }else {//有两个子节点,首先要找到要删除节点的后继节点
        Node successor = cur.rightChild;//一种是欲删除节点的右子节点没有左子节点,那么它本身就是后继节点
        Node successorParent = null;
        while(successor.leftChild!=null) {//另一种情况是欲删除节点的右子节点有左子节点
            successorParent = successor;
            successor = successor.leftChild;
        }
        if(successorParent==null) {//欲删除节点的右子节点没有左子节点,则将以后继节点为根的子树上移即可
            if(root==cur) {
                root = successor;
                root.leftChild = cur.leftChild;
            }else if(isLeftChild) {
                parent.leftChild = successor;
                successor.leftChild = cur.leftChild;
            }else {
                parent.rightChild = successor;
                successor.leftChild = cur.leftChild;
            }                
        }else {//另一种情况是欲删除节点的右子节点有左子节点
            successorParent.leftChild = successor.rightChild;
            successor.rightChild = cur.rightChild;
            if(cur==root) {
                root = successor;
                successor.leftChild = cur.leftChild;
            }else if(isLeftChild) {
                parent.leftChild = successor;
                successor.leftChild = cur.leftChild;
            }else {
                parent.rightChild = successor;
                successor.leftChild = cur.leftChild;
            }
        }
    }
    return true;
}
//遍历
public static final int PREORDER = 1;   //前序遍历
public static final int INORDER = 2;    //中序遍历
public static final int POSTORDER = 3;  //中序遍历

//遍历
public void traverse(int type){
    switch(type){
    case 1:
        System.out.print("前序遍历:\t");
        preorder(root);
        System.out.println();
        break;
    case 2:
        System.out.print("中序遍历:\t");
        inorder(root);
        System.out.println();
        break;
    case 3:
        System.out.print("后序遍历:\t");
        postorder(root);
        System.out.println();
        break;
    }
}
//前序遍历
public void preorder(Node currentRoot) {
    if(currentRoot!=null) {
        System.out.println(currentRoot.age+"\t");
        preorder(currentRoot.leftChild);
        preorder(currentRoot.rightChild);
    }        
}
//中序遍历,这三种遍历都用了迭代的思想
public void inorder(Node currentRoot){
    if(currentRoot != null){
        inorder(currentRoot.leftChild);  //先对当前节点的左子树对进行中序遍历
        System.out.print(currentRoot.age+"\t"); //然后访问当前节点
        inorder(currentRoot.rightChild);  //最后对当前节点的右子树对进行中序遍历
    }
}

//后序遍历
public void postorder(Node currentRoot){
    if(currentRoot != null){
        postorder(currentRoot.leftChild);
        postorder(currentRoot.rightChild);
        System.out.print(currentRoot.age+"\t");
    }
}
//私有方法,用迭代方法来获取左子树和右子树的最大深度,返回两者最大值
public int getDepth(Node currentRoot,int initdepth) {
    int depth = initdepth;
    int rightdepth = initdepth;
    int leftdepth = initdepth;
    if(currentRoot.leftChild!=null) {
        leftdepth = getDepth(currentRoot.leftChild, depth+1);
    }
    if(currentRoot.rightChild!=null) {
        rightdepth = getDepth(currentRoot.rightChild, depth+1);
    }
    return Math.max(leftdepth, rightdepth);
}

//获取树的深度 public int getTreeDepth(){ if(root == null){ return 0; } return getDepth(root,1); } //返回关键值最大的节点,也就对右节点不断地遍历 public Node getMax(){ if(isEmpty()){ return null; } Node cur = root; while(cur.rightChild != null){ cur = cur.rightChild; } return cur; }

//返回关键值最小的节点,对左节点不断的遍历
public Node getMin(){
    if(isEmpty()){
        return null;
    }
    Node cur = root;
    while(cur.leftChild != null){
        cur = cur.leftChild;
    }
    return cur;
}

//判空 public boolean isEmpty(){ return (root == null); } //以树的形式打印出该树 public void displayTree(){ int depth = getTreeDepth(); ArrayList currentLayerNodes = new ArrayList (); currentLayerNodes.add(root); //存储该层所有节点 int layerIndex = 1; while(layerIndex <= depth){ int NodeBlankNum = (int)Math.pow(2, depth-layerIndex)-1; //在节点之前和之后应该打印几个空位 for(int i = 0;i<currentLayerNodes.size();i++){ Node node = currentLayerNodes.get(i); printBlank(NodeBlankNum); //打印节点之前的空位

            if(node == null){
                System.out.print("*\t");  //如果该节点为null,用空位代替
            }else{
                System.out.print("*  "+node.age+"\t");  //打印该节点
            }

            printBlank(NodeBlankNum);  //打印节点之后的空位
            System.out.print("*\t");   //补齐空位
        }
        System.out.println();
        layerIndex++;
        currentLayerNodes = getAllNodeOfThisLayer(currentLayerNodes);  //获取下一层所有的节点
    }
}

//获取指定节点集合的所有子节点
private ArrayList getAllNodeOfThisLayer(List parentNodes){
    ArrayList list = new ArrayList<Node>();
    Node parentNode;
    for(int i=0;i<parentNodes.size();i++){
        parentNode = (Node)parentNodes.get(i);
        if(parentNode != null){  
            if(parentNode.leftChild != null){  //如果上层的父节点存在左子节点,加入集合
                list.add(parentNode.leftChild);
            }else{
                list.add(null);  //如果上层的父节点不存在左子节点,用null代替,一样加入集合
            }
            if(parentNode.rightChild != null){
                list.add(parentNode.rightChild);
            }else{
                list.add(null);
            }
        }else{  //如果上层父节点不存在,用两个null占位,代表左右子节点
            list.add(null);
            list.add(null);
        }
    }
    return list;
}

//打印指定个数的空位
private void printBlank(int num){
    for(int i=0;i<num;i++){
        System.out.print("*\t");
    }
}

//判断是否为叶子节点 public boolean isLeaf(Node node){ return (node.leftChild != null || node.rightChild != null); }

//获取根节点
public Node getRoot(){
    return root;
}

} ```