数据结构——二叉搜索树

二叉搜索树实现(基于链表)

除叶节点外,其余节点中左节点小于父节点,右节点大于父节点,其通常用于查找(二分查找)

代码实现

  • add()方法:当根节点为空时,则新元素即为根节点,否则循环判断新元素放左边还是右边
  • getParent():从根节点开始寻找指定节点的父节点
  • MaxNode()和Max():获取最大的节点及其对应的值
  • MinNode()和Min():获取最小的节点及其对应的值
  • innerSearch()和search():递归找到对应的值返回boolean
  • visitNode()、midOrderTraversal()和innerMidOrderTraversal():中序遍历,用于打印验证树结果是否正确
  • remove():删除,在下面测试代码中介绍

getPre():获取指定节点的前驱节点(如寻找10的前驱节点pre),分以下情况

  • 情况1:若该节点有左子树,其前驱节点是其左子树中值最大的节点(添加11-10-8-7-9,pre=9)
  • 情况2:若该节点无左子树,那么判断该节点和其父节点的关系
  • 情况2-1:若该节点是无左子树的根节点,无前驱节点(添加10-11-12,pre=null)
  • 情况2-2:若该节点是其父节点的右节点,其前驱结点即为其父节点(添加9-10-11,pre=9)
  • 情况2-3:若该节点是其父节点的左节点,其前驱节点为第一个小于该节点的父节点(添加8-9-11-10,pre=9),无则为null(添加13-10-11,pre=null)

getSuc():获取指定节点的后继节点(如寻找10的后继节点suc),分以下情况

  • 情况1:若该节点有右子树,其后继节点是其右子树中值最小的节点(添加9-10-12-11-13,suc=11)
  • 情况2:若该节点无右子树,那么判断该节点和其父节点的关系
  • 情况2-1:若该节点是无右子树的根节点,无后继节点(添加10-9-8,suc=null)
  • 情况2-2:若该节点是其父节点的左节点,其后继结点即为其父节点(添加11-10-9,suc=11)
  • 情况2-3:若该节点是其父节点的右节点,其后继结点为第一个大于该节点的父节点(添加11-9-8-10,suc=11),无则为null(添加8-10-9,suc=null)
class BinarySearchTree {

    private class TreeNode {

        int value;
        TreeNode leftChild;
        TreeNode rightChild;

        TreeNode(int value) {
            this.value = value;
        }
    }

    private TreeNode root;

    public void add(int value) {
        if (root == null) {
            root = new TreeNode(value);
        } else {
            TreeNode parent;
            TreeNode current = root;
            while (true) {
                parent = current;
                if (value < current.value) {
                    current = current.leftChild;
                    if (current == null) {
                        parent.leftChild = new TreeNode(value);
                        return;
                    }
                } else {
                    current = current.rightChild;
                    if (current == null) {
                        parent.rightChild = new TreeNode(value);
                        return;
                    }
                }
            }
        }
    }

    private TreeNode getPre(TreeNode node) {
        if (node.leftChild != null) {
            return MaxNode(node.leftChild);
        } else {
            TreeNode parent = getParent(node);
            if (parent == null) {
                return null;
            }
            if (node == parent.rightChild) {
                return parent;
            } else {    //node == parent.leftChild
                while (parent != null) {
                    if (parent.value < node.value) {
                        break;
                    }
                    parent = getParent(node);
                }
                return parent;
            }
        }
    }

    private TreeNode getSuc(TreeNode node) {
        if (node.rightChild != null) {
            return MinNode(node.rightChild);
        } else {
            TreeNode parent = getParent(node);
            if (parent == null) {
                return null;
            }
            if (node == parent.leftChild) {
                return parent;
            } else {    //node == parent.rightChild
                while (parent != null) {
                    if (parent.value > node.value) {
                        break;
                    }
                    parent = getParent(node);
                }
                return parent;
            }
        }
    }

    private TreeNode getParent(TreeNode node) {
        if (root == node) {
            return null;
        }
        if (node == null) {
            return null;
        }
        TreeNode current = root;
        TreeNode parent = null;
        while (current != null) {
            if (node.value < current.value) {
                parent = current;
                current = current.leftChild;
            } else if (node.value > current.value) {
                parent = current;
                current = current.rightChild;
            } else {
                return parent;
            }
        }
        return parent;
    }

    public void Max() {
        TreeNode maxNode = MaxNode(root);
        if (maxNode != null) {
            visitNode(maxNode);
        } else {
            System.out.print("null");
        }
    }

    private TreeNode MaxNode(TreeNode node) {
        if (node == null) {
            return null;
        }
        TreeNode current = node;
        while (current.rightChild != null) {
            current = current.rightChild;
        }
        return current;
    }

    public void Min() {
        TreeNode minNode = MinNode(root);
        if (minNode != null) {
            visitNode(minNode);
        } else {
            System.out.print("null");
        }
    }

    private TreeNode MinNode(TreeNode node) {
        if (node == null) {
            return null;
        }
        TreeNode current = node;
        while (current.leftChild != null) {
            current = current.leftChild;
        }
        return current;
    }

    public boolean search(int value) {
        if (innerSearch(root, value) != null) {
            return true;
        } else {
            return false;
        }
    }

    private TreeNode innerSearch(TreeNode root, int value) {
        if (root == null) {
            return null;
        }
        if (value < root.value) {
            return innerSearch(root.leftChild, value);
        } else if (value > root.value) {
            return innerSearch(root.rightChild, value);
        } else {
            return root;
        }
    }

    private void visitNode(TreeNode node) {
        System.out.print(node.value + " ");
    }

    public void midOrderTraversal() {
        innerMidOrderTraversal(root);
    }

    private void innerMidOrderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        innerMidOrderTraversal(root.leftChild);
        visitNode(root);
        innerMidOrderTraversal(root.rightChild);
    }

    public void remove(int value) {
        TreeNode delNode = innerSearch(root, value);
        TreeNode parent = getParent(delNode);
        if (delNode.leftChild == null && delNode.rightChild == null) {  //删除的节点无左右节点,则断开父节点和它的连接(若是根节点则置空)
            if (parent == null) {   //delNode = root
                root = null;
            } else {
                if (delNode == parent.leftChild) {
                    parent.leftChild = null;
                } else {
                    parent.rightChild = null;
                }
            }
        } else if (delNode.leftChild == null && delNode.rightChild != null) { //删除的节点有右节点,则让其右节点连上父节点(若是根节点,则右节点成为新根节点)
            if (parent == null) {   //delNode = root
                root = delNode.rightChild;
            } else {
                if (delNode == parent.leftChild) {
                    parent.leftChild = delNode.rightChild;
                } else {
                    parent.rightChild = delNode.rightChild;
                }
            }
        } else if (delNode.leftChild != null && delNode.rightChild == null) { //删除的节点有左节点,则让其左节点连上父节点(若是根节点,则左节点成为新根节点)
            if (parent == null) {   //delNode = root
                root = delNode.leftChild;
            } else {
                if (delNode == parent.leftChild) {
                    parent.leftChild = delNode.leftChild;
                } else {
                    parent.rightChild = delNode.leftChild;
                }
            }
        } else { //删除的节点左右节点都有
            if (parent == null) {   //delNode = root,则左右节点任意一个都可成为新根节点
                root = delNode.leftChild;
                //root = delNode.rightChild;
            } else {

                System.out.print("删除的节点:");
                visitNode(delNode);
                System.out.println();

                System.out.print("删除节点的后继节点:");
                TreeNode successor = getSuc(delNode);
                if (successor == null) {
                    System.out.print("null");
                } else {
                    visitNode(successor);
                }
                System.out.println();

                System.out.print("删除节点的后继节点的父节点:");
                TreeNode successorParent = getParent(successor);
                if (successorParent == null) {
                    System.out.print("null");
                } else {
                    visitNode(successorParent);
                }
                System.out.println();

                if (successorParent == delNode) {   //若后继节点是删除节点的右节点
                    parent.rightChild = successor;
                    successor.leftChild = delNode.leftChild;
                    delNode.leftChild = null;
                } else {                  //若后继节点是删除节点的右子树的左节点
                    successorParent.leftChild = successor.rightChild;   //断开successor
                    successor.rightChild = null;

                    parent.leftChild = successor;               //del父节点指向successor,即断开del
                    successor.rightChild = delNode.rightChild;  //successor连接delNode右子树
                    delNode.rightChild = null;                  //断开delNode右子树

                    successor.leftChild = delNode.leftChild;    //successor连接delNode左子树
                    delNode.leftChild = null;                   //断开delNode左子树
                }
            }
        }
    }
}

测试代码1

下面代码测试添加、遍历、最大最小值

BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(3);
binarySearchTree.add(5);
binarySearchTree.add(2);
binarySearchTree.add(1);
binarySearchTree.add(4);

System.out.print("add后中序遍历测试:");
binarySearchTree.midOrderTraversal();
System.out.println();

System.out.print("最大值:");
binarySearchTree.Max();
System.out.println();

System.out.print("最小值:");
binarySearchTree.Min();
System.out.println();

System.out.print("查找value=2:");
if (binarySearchTree.search(2)) {
    System.out.print("found");
} else {
    System.out.print("not found");
}
System.out.println();

打印如下

add后中序遍历测试:1 2 3 4 5 
最大值:5 
最小值:1 
查找value=2:found

测试代码2

下面单独测试删除

  • 情况1:删除的节点无左右节点(添加1-2,删除2),则断开父节点和它的连接(若是根节点则置空)
  • 打印:remove前中序遍历为:1 2 ————> remove后中序遍历为:1
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(1);
binarySearchTree.add(2);

System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.print("————> remove后中序遍历为:");
binarySearchTree.remove(2);
binarySearchTree.midOrderTraversal();
System.out.println();
  • 情况2:删除的节点有右节点无左节点(添加1-2-3,删除2),则让其右节点连上其父节点(若是根节点,则右节点成为新根节点)
  • 打印:remove前中序遍历为:1 2 3 ————> remove后中序遍历为:1 3
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(1);
binarySearchTree.add(2);
binarySearchTree.add(3);

System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.print("————> remove后中序遍历为:");
binarySearchTree.remove(2);
binarySearchTree.midOrderTraversal();
System.out.println();
  • 情况3:删除的节点有左节点无右节点(添加3-2-1,删除2),则让其左节点连上其父节点(若是根节点,则右节点成为新根节点)
  • 打印:remove前中序遍历为:1 2 3 ————> remove后中序遍历为:1 3
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(3);
binarySearchTree.add(2);
binarySearchTree.add(1);
System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();

System.out.print("————> remove后中序遍历为:");
binarySearchTree.remove(2);
binarySearchTree.midOrderTraversal();
System.out.println();
  • 情况4-1:删除的节点既有左节点又有右节点,其后继节点为待删除节点的右节点(添加2-4-3-6-9-7,删除4)
  • 操作为:待删除节点的父节点的右节点指向其后继节点,后继节点的左节点指向待删除节点的左节点,断开待删除节点的左节点
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(2);
binarySearchTree.add(4);
binarySearchTree.add(3);
binarySearchTree.add(6);
binarySearchTree.add(9);
binarySearchTree.add(7);

System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.println();

binarySearchTree.remove(4);
System.out.print("remove后中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.println();

打印如下

remove前中序遍历为:2 3 4 6 7 9 
删除的节点:4 
删除节点的后继节点:6 
删除节点的后继节点的父节点:4 
remove后中序遍历为:2 3 6 7 9 
  • 情况4-2:删除的节点既有左节点又有右节点,其后继节点为待删除节点的右子树的左节点(添加10-4-3-8-9-6-7,删除4)
  • 操作为:①后继节点的父节点的左节点指向后继节点的右节点,断开后继节点的右节点。②待删除节点的父节点的左节点指向后继节点,后继节点的右节点指向待删除节点的右节点,断开待删除节点的右节点。③后继节点的左节点指向待删除节点的左节点,断开待删除节点的左节点
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.add(10);
binarySearchTree.add(4);
binarySearchTree.add(3);
binarySearchTree.add(8);
binarySearchTree.add(9);
binarySearchTree.add(6);
binarySearchTree.add(7);
//binarySearchTree.add(5);

System.out.print("remove前中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.println();

binarySearchTree.remove(4);
System.out.print("remove后中序遍历为:");
binarySearchTree.midOrderTraversal();
System.out.println();

打印如下

remove前中序遍历为:3 4 6 7 8 9 10 
删除的节点:4 
删除节点的后继节点:6 
删除节点的后继节点的父节点:8 
remove后中序遍历为:3 6 7 8 9 10 

时间复杂度和优化

  • 最好情况:如果二叉搜索树刚好对半平分,n个节点的二叉搜索树的高度为log2n+1,复杂度为O(logn),同二分查找法

  • 最坏情况:如果二叉搜索树完全斜向一边,高度为n,复杂度为O(n),退化为顺序查找法

为了获得较好的查找性能,就要构造一棵平衡的二叉搜索树

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值