二叉排序树详解(Java实现)

一、二叉排序树定义

特点:一颗空树,或者具有以下性质的二叉树

  • 如果左子树不为空,则左子树上所有结点的值均小于根结点的值
  • 如果右子树不为空,则右子树上所有结点的值均大于根结点的值
  • 它的左、右子树也分别为二叉排序树。
  • 对二叉排序树进行中序遍历,会得到一个递增序列。

二、二叉树的表现形式(Java)

public class BinarySearchTree {
    private class Node {
        int data; // 数据域
        Node left; // 左子树
        Node right; // 右子树
    }
    private Node root; // 二叉排序树根节点
}

三、创建二叉树

这里通过借助parent,curretn来记录新结点插入的位置。

 /**
     * 创建二叉排序树
     * @param key
     */
    public void insert(int key) {
        Node p = new Node(); // 要插入的结点
        p.data = key;

        if(root == null) {
            root = p;
        } else {
            Node parent = new Node();
            Node current = root; // 当前结点
            while (true) {
                parent = current;
                if(key > current.data) {
                    current = current.right;
                    if (current == null) {
                        parent.right = p;
                        return;
                    }
                } else { // 这里不考虑key出现和二叉排序树结点中的值相等的情况
                    current = current.left;
                    if (current == null) {
                        parent.left = p;
                        return;
                    }
                }

            }
        }
    }

四、遍历二叉排序树

1、前序遍历

根、左、右

/**
     * 前序遍历     根、左、有
     * @param root
     */
    public void preOrder(Node root) {
        if (root != null) {
            System.out.print(root.data+" | ");
            preOrder(root.left);
            preOrder(root.right);
        }
    }

2、中序遍历

左、根、右

 /**
     * 中序遍历     左、根、右
     * @param root
     */
    public void inOrder(Node root) {
        if (root != null) {
            inOrder(root.left);
            System.out.print(root.data+" | ");
            inOrder(root.right);
        }
    }

3、后序遍历左、右、根

/**
     * 后续遍历     左、右、根
     * @param root
     */
    public void nextOrder(Node root) {
        if (root != null) {
            nextOrder(root.left);
            nextOrder(root.right);
            System.out.print(root.data+" | ");
        }
    }

五、查找某个结点

 /**
     * 按照关键值,查找结点。
     * @param key
     * @return
     */
    public Node find(int key) {
        Node current = root;
        while (current.data != key) {
            if (key > current.data) {
                current = current.right;
            } else {
                current = current.left;
            }
            if (current == null) {
                return null;
            }
        }
        return current;
    }

六、删除结点

删除结点比较复杂。我们要分多种情况来讨论

  • 眼删除的结点没有孩子,即叶子结点

在这里插入图片描述

如上图所示,只需要将parent的左孩子设置为null,就可以了。

  • 删除结点有一个孩子
    在这里插入图片描述
    在这里插入图片描述

    有一个结点,相对来说,也是比较简单的。只需要将parent结点的左孩子设置成current结点的右孩子即可。parent.left = current.rigth;

  • 要删除的结点有两个孩子

    这种情况比较负责,在讲这个之前,我们先引入一个有关后继结点的概念,如果一颗二叉树按照中序遍历,那么下一个结点就是该结点的后继结点。

    1、后继节点为待删除节点的右子,只需要将curren用successor替换即可,注意处理好current.left和successor.right.

    注意:这种情况下,successor一定没有左孩子,一但它有左孩子,那它必然不是current的后继节点。

在这里插入图片描述
在这里插入图片描述

2、**后继节点为待删除结点的右孩子的左子树,**这种情况稍微复杂点。

在这里插入图片描述
在这里插入图片描述

首先我们要弄明白的是successor为后继结点。successorParent为后继结点的父结点。要删除current结点。我们应该怎么做呢?

眼删除current结点我们需要重新排列二叉树。

1、删除current结点之后,我们需要重新排列current的左、右、子树。

2、我们知道二叉排序树的性质,在current的右子树中,successor结点为最小值。successor的右子树上的任何一个结点的值都比successor结点的值大。所以我们需要做一个操作

successorParent.left = successor.right;

将successor结点的右子树挂在到successorParent的左子树使其有序。

3、然后我们将有序的successorParent二叉树挂在到successor的右子树

successor.right = successorParent;

4、现在还剩下current的左子树了,将其挂在到successor的左子树

successor.left = current.left;

5、这样我们就构建好了有序的二叉树,最后将其连接到parent结点上

parent.left = successor;

  • 这里总结一下,总的来说,就是我们要删除current结点之后,我们还是要让current的左、右子树按照二叉排序树的性质,连接到parent结点上。我们这里为什么要找后继结点呢。其实后继结点是最后会作为左、右子树的根。因为我们是通过中序遍历来找的后继结点。

  • 算法步骤:

    1、successorParent.left = successor.right;

    2、successor.left = current.left;

    3、parent.left = successor;

七、总结二叉排序树

package com.gwz.datastructure.bstree;

/**
 * 二叉排序树
 */
public class BinarySearchTree {
    private class Node {
        int data; // 数据域
        Node left; // 左子树
        Node right; // 右子树
    }
    private Node root; // 二叉排序树根节点

    /**
     * 创建二叉排序树
     * @param key
     */
    public void insert(int key) {
        Node p = new Node(); // 要插入的结点
        p.data = key;

        if(root == null) {
            root = p;
        } else {
            Node parent = new Node();
            Node current = root; // 当前结点
            while (true) {
                parent = current;
                if(key > current.data) {
                    current = current.right;
                    if (current == null) {
                        parent.right = p;
                        return;
                    }
                } else { // 这里不考虑key出现和二叉排序树结点中的值相等的情况
                    current = current.left;
                    if (current == null) {
                        parent.left = p;
                        return;
                    }
                }

            }
        }
    }

    /**
     * 前序遍历     根、左、有
     * @param root
     */
    public void preOrder(Node root) {
        if (root != null) {
            System.out.print(root.data+" | ");
            preOrder(root.left);
            preOrder(root.right);
        }
    }

    /**
     * 中序遍历     左、根、右
     * @param root
     */
    public void inOrder(Node root) {
        if (root != null) {
            inOrder(root.left);
            System.out.print(root.data+" | ");
            inOrder(root.right);
        }
    }

    /**
     * 后续遍历     左、右、根
     * @param root
     */
    public void nextOrder(Node root) {
        if (root != null) {
            nextOrder(root.left);
            nextOrder(root.right);
            System.out.print(root.data+" | ");
        }
    }

    /**
     * 按照关键值,查找结点。
     * @param key
     * @return
     */
    public Node find(int key) {
        Node current = root;
        while (current.data != key) {
            if (key > current.data) {
                current = current.right;
            } else {
                current = current.left;
            }
            if (current == null) {
                return null;
            }
        }
        return current;
    }

    /**
     * 打印结点域中的值
     * @param node
     */
    public void show(Node node) {
        if (node != null) {
            System.out.println(node.data);
        } else {
            System.out.println("此结点为空!");
        }
    }


    /**
     * 删除结点
     * @param key
     * @return
     */
    public boolean delete(int key) {
        Node current = root;
        Node parent = new Node();
        boolean isRightChild = true;
        // 查找要删除的结点是否存在
        while (current.data != key) {
            parent = current;
            if (key > current.data) {
                current = current.right;
                isRightChild = true;
            } else {
                current = current.left;
                isRightChild = false;
            }
            if (current == null) {
                return false;
            }
        }
        // 找到的current就是要删除的结点。parent是删除结点的符结点,只有根结点。
        if (current.right == null && current.left == null) {
            if (current == root) {
                root = null; //删除根,变成空树
            } else if (isRightChild){
                parent.right = null;
            } else {
                parent.left = null;
            }
            return true;
        }
        // 要删除的结点,有一个子结点
        // 当前current是查找到的结点,不一定是根结点。
        else if (current.left == null) {
            if (current == root) {
                current = current.right;
            } else if (isRightChild) {
                parent.right = current.right;
            } else {
                parent.left = current.left;
            }
            return true;
        } else if (current.right == null) {
            if (current == root) {
                current = current.left;
            } else if (isRightChild) {
                parent.right = current.left;
            } else {
                parent.left = current.left;
            }
            return true;
        }
        // 要删除的结点有两个子结点
        else {
            // 找到要删除结点的后继结点
            Node successor = getSuccessor(current);

            if (current == root) {
                root = successor;
            } else if (isRightChild) {
                parent.right = successor;
            } else {
                parent.left = successor;
            }

            successor.left = current.left;
            return true;
        }
    }

    /**
     * 查找要删除结点的后继结点
     * 后继结点:按照中序遍历,下一个结点就是后继结点。
     * @param delNode
     * @return
     */
    private Node getSuccessor(Node delNode) {
        Node successorParent = delNode;
        Node successor = delNode;
        Node current = delNode.right; //左、根、右、右为后继。

        // 寻找后继结点
        while (current != null) {
            successorParent = successor;
            successor = current;
            // 右子树的遍历方式、左、根、右、
            // 所以左、结点为后继
            current = current.left;
        }
        // 如果后继结点为要删除结点的右子树
        // 需要调整一下删除结点的右子树
        if (successor != delNode.right) {
            successorParent.left = successor.right;
            successor.right = delNode.right;
        }
        return successor; // 转换好的有序的二叉树。
    }

    public void traverse(int traverseType)
    { // 选择以何种方式遍历
        switch (traverseType)
        {
            case 1:
                System.out.print("preOrder traversal ");
                preOrder(root);
                System.out.println();
                break;
            case 2:
                System.out.print("inOrder traversal ");
                inOrder(root);
                System.out.println();
                break;
            case 3:
                System.out.print("postOrder traversal ");
                nextOrder(root);
                System.out.println();
                break;
        }
    }

    public static void main(String[] args) {
        BinarySearchTree tree = new BinarySearchTree();

        tree.insert(39);
        tree.insert(24);
        tree.insert(64);
        tree.insert(23);
        tree.insert(30);
        tree.insert(53);
        tree.insert(60);

        tree.traverse(1);
        tree.traverse(2);
        tree.traverse(3);

        tree.show(tree.find(23));
        tree.show(tree.find(60));
        tree.show(tree.find(64));

        tree.delete(23);
        tree.delete(60);
        tree.delete(64);

        tree.show(tree.find(23));
        tree.show(tree.find(60));
        tree.show(tree.find(64));
    }
}

动态图片来源: https://visualgo.net/en/bst

猜你喜欢

转载自blog.csdn.net/qq_40749830/article/details/105877101