二叉搜索树

235 阅读5分钟

1. 简介

二叉树(Binary Tree)是一种非线性的数据结构(线性数据结构:数组,链表,栈,队列)。 首先什么样的结构算是树,树是一个无连通的图。其中二叉树是树中的特例,其一个顶点的度不大于3。二叉搜索树是二叉树的特例,要求一个节点的左子节点的值小于其值,右子节点的值大于其值。

可以看出节点之间有相互关系,一个节点的上一层节点叫父亲节点,属于同一个父亲节点的另一个节点叫兄弟节点,其下一层节点叫子节点。把没有父亲节点的节点叫根节点,把没有任何子节点的节点叫叶子节点

2. 二叉搜索树的特性

虽然二叉搜索树在极端环境下会退化成一个近似链表的结构,时间复杂度为O(N),但左右平均情况下的时间复杂度为O(logN)。所以为了维持这个平衡关系,还衍生出了一个叫红黑树的数据结构。

如果我们要实现这样的一个二叉搜索树,具体过程是什么样子呢。我来详细讲述一下。

2.1 搜索

根据搜索的值进行左右递归,如果正好遇到要搜索的值返回true,搜索到叶子节点也没有遇到则返回false。

public boolean search(int value) {
    return search(root, value);
}

public boolean search(Node node, int value) {
    if (node == null) return false;

    if (node.value > value) return search(node.leftNode, value);
    else if (node.value < value) return search(node.rightNode, value);

    return true; // 相同的情况返回true
}
2.2 插入

插入操作是根据插入值的大小在二叉搜索树中遍历,当遍历到叶子节点时停止,并根据大小插入到左或者右子节点中。

public boolean insert(int value) {
    return insert(root, value);
}

public boolean insert(Node node, int value) {
    if (value == node.value) return false;

    if (node.value > value) {
       if (node.leftNode != null) {
            insert(node.leftNode, value);
        } else {
            Node newNode = new Node(value);
            node.leftNode = newNode;
        }
    } else {
        if (node.rightNode != null) {
            insert(node.rightNode, value);
        } else {
            Node newNode = new Node(value);
            node.rightNode = newNode;
        }
     }
     return true;
}
2.3 查找最小值,最大值

二叉搜索树中的最小值即是二叉搜索树中的最左节点。所以一直递归左节点直到遇到叶子节点,最大值也是同理,递归到最右边的叶子节点即可。

    public Node minValue() {
        return minValue(root);
    }

    public Node minValue(Node node) {
        // 查找二叉查找树中的最小值,即最左边的节点
        if (node == null) return null;

        if (node.leftNode == null) {
            return node;
        }
        return minValue(node.leftNode);
    }

    public Node maxValue() {
        return maxValue(root);
    }

    public Node maxValue(Node node) {
        // 查找二叉查找树中的最大值,即最右边的节点
        if (node == null) return null;

        if (node.rightNode == null) {
            return node;
        }
        return maxValue(node.rightNode);
    }

2.4 遍历

二叉搜索树的遍历根据遍历顺序的不同分为前序遍历,中序遍历,后序遍历。 前序遍历: 根节点, 左节点, 右节点 中序遍历: 左节点, 根节点, 右节点 后序遍历: 左节点, 右节点, 根节点

    public void preOrderSearch() {
        preOrderSearch(root);
    }

    public void preOrderSearch(Node node) {
        // 前序遍历, 遍历顺序:根节点,左节点,右节点
        System.out.println(node.value);
        if (node.leftNode != null) {
            preOrderSearch(node.leftNode);
        }
        if (node.rightNode != null) {
            preOrderSearch(node.rightNode);
        }
    }

    public void inOrderSearch() {
        inOrderSearch(root);
    }

    public void inOrderSearch(Node node) {
        // 中序遍历, 遍历顺序:左节点,根节点,右节点
        if (node.leftNode != null) {
            inOrderSearch(node.leftNode);
        }
        System.out.println(node.value);
        if (node.rightNode != null) {
            inOrderSearch(node.rightNode);
        }
    }

    public void postOrderSearch() {
        postOrderSearch(root);
    }

    public void postOrderSearch(Node node) {
        // 后序遍历, 遍历顺序:左节点,右节点,根节点
        if (node.leftNode != null) {
            inOrderSearch(node.leftNode);
        }
        if (node.rightNode != null) {
            inOrderSearch(node.rightNode);
        }
        System.out.println(node.value);
    }

2.5 删除

为什么最后说删除呢。因为删除最难最麻烦,需要分情况讨论。一共有三种情况。

  1. 如果要删除的节点没有没有左右子节点,也就是要删除的节点是叶子节点 -> 则直接把要删除的节点设为null。
  2. 如果要删除的节点只有一个左节点或者右节点 -> 把子节点替换到要删除的节点上面。
  3. 如果要删除的节点左右子节点都存在 -> 把要删除的节点的右子树的最小值,即右子树的最左节点替换到要删除节点上,并删除那个最小节点。 说的有点复杂,直接看代码:
    public boolean delete(int value) {
        return delete(null, root, value);
    }

    public boolean delete(Node parent, Node node, int value) {
        // 删除节点的操作根据要删除节点的位置的不同分为三种情况
        // 要删除节点没有任何子节点: 直接删除该节点
        // 要删除节点有一个子节点: 把子节点顶替到要删除节点的位置
        // 要删除节点有两个子节点: 把右子树中最左节点替换到要删除节点的位置

        if (node == null) return false;

        if (node.value != value) {
            if (node.value > value) {
                return delete(node, node.leftNode, value);
            } else {
                return delete(node, node.rightNode, value);
            }
        } else {
            if (node.leftNode == null && node.rightNode == null) {
                if(node==root){
                    // 如果删除节点是根节点,且没有左右子节点,则直接把root设为null
                    root = null;
                    return true;
                }
                boolean isParentLeftNode = parent.value > node.value;
                // 没有左右子节点
                if (isParentLeftNode) {
                    parent.leftNode = null;
                } else {
                    parent.rightNode = null;
                }
                node = null;
            } else if (node.leftNode != null && node.rightNode != null) {
                // 有左右两个节点
                // 查找右子树中的最左节点,进行替换
                Node minNode = node.rightNode;
                Node minNodeParent = null;
                if(minNode.leftNode==null){
                    // 如果要删除节点的右节点没有左子树,则把右节点直接替换到要删除的节点上
                    node.value = node.rightNode.value;
                    node.rightNode = null;
                }else{
                    // 如果要删除节点的右节点有左子树,则找其最小值
                    while(minNode.leftNode !=null){
                        minNodeParent = minNode;
                        minNode = minNode.leftNode;
                    }
                    // 把刚才查找到的值赋值到要删除的节点上
                    node.value = minNode.value;
                    // 删除刚才查找的节点
                    minNode = null;
                    minNodeParent.leftNode = null;
                }
            } else {
                boolean isParentLeftNode = parent.value > node.value;
                // 有左右节点中的其中一个
                if (node.leftNode != null) {
                    // 存在左节点
                    if (isParentLeftNode) {
                        parent.leftNode = node.leftNode;
                    } else {
                        parent.rightNode = node.leftNode;
                    }
                } else {
                    // 存在右节点
                    if (isParentLeftNode) {
                        parent.leftNode = node.rightNode;
                    } else {
                        parent.rightNode = node.rightNode;
                    }
                }
                node = null;
            }
        }
        return true;
    }

github: github.com/HyejeanMOON…