关于二叉树的几个必须掌握的实现

The best way to end your fear is to face it yourself. 
结束恐惧的最佳办法就是自己面对。

本分分享了二叉搜索树的几种实现,由简入繁。说句题外话,马上又是金三银四的季节了,无论跳不跳槽,增加自己的知识储备总是没错的。从代码里读原理,读思想。

所有源码均已上传至github:链接

日志

2019年3月28日 AVL树

准备

public class Node {
    /**
     * data
     */
    public int data;
    /**
     * 左节点
     */
    public Node left;
    /**
     * 右节点
     */
    public Node right;

    public Node(int data) {
        this.data = data;
    }

}复制代码

实现一个支持插入、删除、查找操作的二叉查找树,并且找二叉树的最大值和最小值操作

插入操作

传入一个data值,当tree为null,初始化tree,否则遍历tree,比较data与node.data的值,大于的话插入右节点,否则插入左节点。

    private void insert(int data) {
        if (null == tree) {
            tree = new Node(data);
            return;
        }
        Node node = tree;
        while (null != node) {
            if (data > node.data) {
                if (null == node.right) {
                    node.right = new Node(data);
                    return;
                }
                node = node.right;
            } else {
                if (null == node.left) {
                    node.left = new Node(data);
                    return;
                }
                node = node.left;
            }
        }
    }复制代码

查找

查找的实现也比较简单,遍历tree的左右节点,直到找到data == node.data

    private Node find(int data) {
        Node node = tree;
        while (null != node) {
            if (data > node.data) {
                node = node.right;
            } else if (data < node.data) {
                node = node.left;
            } else {
                return node;
            }
        }
        return null;
    }复制代码

找二叉树的最大值

找二叉树的最大值最小值比较简单,按照树的结构遍历即可

    private Node findMaxNode() {
        if (null == tree) return null;
        Node node = tree;
        while (null != node.right) {
            node = node.right;
        }
        return node;
    }复制代码

找二叉树的最小值

    private Node findMinNode() {
        if (null == tree) return null;
        Node node = tree;
        while (null != node.left) {
            node = node.left;
        }
        return node;
    }复制代码

删除操作

删除是这里面最复杂的操作了,这里详细讲一下。

针对删除的节点的位置不同需要考虑以下三种情况:

  1. 如果要删除的节点没有子节点,只需要直接将父节点中,指向要删除节点的指针置为 null。
  2. 如果要删除的节点只有一个子节点(左子节点或者右子节点),只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。
  3. 如果要删除的节点有两个子节点,这就比较复杂了。需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了)。

    private void delete(int data) {
        Node node = tree;
        //node的父节点
        Node pNode = null;
        while (null != node && node.data != data) {
            pNode = node;
            if (data > node.data) {
                node = node.right;
            } else {
                node = node.left;
            }
        }
        if (null == node) return;
        if (null != node.left && null != node.right) {
            Node p = node.right;
            //p的父节点
            Node pp = node;
            while (null != p.left) {
                pp = p.left;
                p = p.left;
            }
            //删除操作
            node.data = p.data;
            node = p;
            pNode = pp;
        }
        Node child;
        if (null != node.left) {
            child = node.left;
        } else if (null != node.right) {
            child = node.right;
        } else {
            child = null;
        }
        if (null == pNode) {
            tree = child;
        } else if (pNode.left == null) {
            pNode.left = child;
        } else {
            pNode.right = child;
        }
    }复制代码

实现查找二叉查找树中某个节点的后继、前驱节点

ps:因为这里测试的节点都是根节点,因为比较简单,不需要遍历该节点的父节点。

后续如果需要,欢迎留言,我补充一下。

查找前驱节点

    private Node findPreNode(Node node) {
        if (null == node) return null;
        if (null != node.left){
            Node left = node.left;
            while (null != left.right){
                left = left.right;
            }
            return left;
        }
        return null;
    }复制代码

查找后继节点

    private Node findNextNode(Node node) {
        if (null == node) return null;
        if (null != node.right) {
            Node right = node.right;
            while (null != right.left) {
                right = right.left;
            }
            return right;
        }
        return null;
    }复制代码

实现二叉查找树前、中、后序以及层级遍历

前序遍历

    public void preOrderTraversal(Node tree) {
        if (null == tree) return;
        System.out.print(tree.data + " ");
        preOrderTraversal(tree.left);
        preOrderTraversal(tree.right);
    }复制代码

中序遍历

    public void inOrderTraversal(Node tree) {
        if (null == tree) return;
        inOrderTraversal(tree.left);
        System.out.print(tree.data + " ");
        inOrderTraversal(tree.right);
    }复制代码

后序遍历

    public void postOrderTraversal(Node tree) {
        if (null == tree) return;
        postOrderTraversal(tree.left);
        postOrderTraversal(tree.right);
        System.out.print(tree.data + " ");
    }复制代码

层级遍历

层级遍历的思想是维护一个队里,每一次遍历tree,将tree的节点打印,然后将tree的left和right节点存入队列里(每一次打印时从队列里取即可)。

    public void levelTraversal(Node tree) {
        if (null == tree) return;
        LinkedList linkedList = new LinkedList<>();
        Node node;
        linkedList.offer(tree);
        while (!linkedList.isEmpty()) {
            node = linkedList.poll();
            System.out.print(node.data + " ");
            if (null != node.left) {
                linkedList.offer(node.left);
            }
            if (null != node.right) {
                linkedList.offer(node.right);
            }
        }
    }复制代码

AVL树

定义

AVL树是一种平衡二叉树。貌似就windows对进程地址空间的管理用到了AVL树。

  1. 左右子树的高度差小于等于 1。
  2. 其每一个子树均为平衡二叉树。

核心

AVL树核心在于左旋和右旋上,当出现不平衡的情况时,它会自动调整。

声明一个AVL树的Node类

在普通二叉树的基础上新增了几个属性

  1. balance 是平衡标识,它的区间是[-1,1]
  2. height 是该节点的高度,自上而下计算,从0开始

    public class Node {
        /**
         * 数据
         */
        int data;
        /**
         * 高度
         */
        int height;
        /**
         * 平衡标识
         */
        int balance;
        /**
         * 左子树
         */
        Node left;
        /**
         * 右子树
         */
        Node right;
        /**
         * 根
         */
        Node parent;

        /**
         * 有参构造方法
         *
         * @param data   数据
         * @param parent 父节点
         */
        Node(int data, Node parent) {
            this.data = data;
            this.parent = parent;
        }
    }复制代码

插入

插入方法和常规插入的区别是需要将当前节点作为根节点插入,并且执行一下自平衡方法。

 private void insert(int data) {
        if (null == root) {
            root = new Node(data, null);
        } else {
            Node node = root;
            Node parent;
            while (node.data != data) {
                parent = node;
                boolean goleft = node.data > data;
                node = goleft ? node.left : node.right;
                if (null == node) {
                    if (goleft) {
                        parent.left = new Node(data, parent);
                    } else {
                        parent.right = new Node(data, parent);
                    }
                    rebalance(parent);
                    break;
                }
            }
        }
    }复制代码

删除

这里是根据节点删除。和常规的删除相比,也是多了一步自平衡。

    private void delete(Node node) {
        if (null == node.left && null == node.right) {
            if (null == node.parent) {
                root = null;
            } else {
                Node parent = node.parent;
                if (parent.left == node) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
                rebalance(parent);
            }
        }
        if (null != node.left) {
            Node child = node.left;
            while (null != child.right) {
                child = child.right;
            }
            node.data = child.data;
            delete(child);
        } else {
            Node child = node.right;
            if (null != child) {
                while (null != child.left) {
                    child = child.left;
                }
                node.data = child.data;
                delete(child);
            }
        }
    }复制代码

自平衡

有这么四种情况。


这里是否需要自平衡,是根据balance来判断的,当balance为-2的时候,(如果该节点的左子树的左子树的高度小于右子树高度,左旋,)右旋;当balance为2的时候,(如果该节点的右子树的右子树的高度小于左子树高度,右旋,)左旋;最后判断根节点是否为null,是则递归,否则赋值,停止递归;平衡完毕。

    private void rebalance(Node node) {
        setBalance(node);
        if (node.balance == -2) {
            if (getHeight(node.left.left) < getHeight(node.left.right)) {
                node.left = rotateLeft(node.left);
            }
            node = rotateRight(node);
        } else if (node.balance == 2) {
            if (getHeight(node.right.right) < getHeight(node.right.left)) {
                node.right = rotateRight(node.right);
            }
            node = rotateLeft(node);
        }
        if (null != node.parent) {
            rebalance(node.parent);
        } else {
            root = node;
        }
    }复制代码

左右旋

左旋和右旋的原理是一样的,只要了解一种即可。

    private Node rotateLeft(Node node) {
        Node right = node.right;
        right.parent = node.parent;
        node.right = right.left;
        if (null != node.right)
            node.right.parent = node;
        right.left = node;
        node.parent = right;
        if (null != right.parent) {
            if (right.parent.right == node) {
                right.parent.right = right;
            } else {
                right.parent.left = right;
            }
        }
        setBalance(node, right);
        return right;
    }复制代码

    private Node rotateRight(Node node) {
        Node left = node.left;
        left.parent = node.parent;
        node.left = left.right;
        if (null != node.left)
            node.left.parent = node;
        left.right = node;
        node.parent = left;
        if (null != left.parent) {
            if (left.parent.right == node) {
                left.parent.right = left;
            } else {
                left.parent.left = left;
            }
        }
        setBalance(node, left);
        return left;
    }复制代码

设置平衡点

首先是重置高度,取左右子树高度的最大值加一作为该节点的高度。

其次是设置平衡点,用来判断该节点是否平衡

    private void setBalance(Node... nodes) {
        for (Node node : nodes) {
            if (null != node) {
                //重置高度
                node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
                node.balance = getHeight(node.right) - getHeight(node.left);
            }
        }
    }复制代码

获取树的高度

    private int getHeight(Node node) {
        if (null != node)
            return node.height;
        return -1;
    }复制代码

测试代码

这里打印用的中序遍历(中序遍历可以从小到大打印出来)

    private void print(Node node) {
        if (null != node) {
            print(node.left);
            System.out.println(node.data + " " + node.balance);
            print(node.right);
        }
    }

    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();
        for (int i = 1; i < 10; i++) {
            avlTree.insert(i);
        }
        avlTree.print();
    }复制代码

测试结果

               4

        2              6

1          3    5           8

                         7           9


data - balance


end


您的点赞和关注是对我最大的支持,谢谢!



你可能感兴趣的:(关于二叉树的几个必须掌握的实现)