二叉查找树(BST)

前言

二叉查找树是一棵二叉树,如果用来构造符号表的话,那么每个节点含有一个键值对,对于每个节点,其键大于其左子树中所有节点的键,且小于其右子树中所有节点的键,如果中序遍历二叉查找树并打印其键,那么键将按从小到大的顺序打印。如果仅仅是为了描述二叉查找树,是可以不用构造符号表的,每个节点只含有键就够了,但是本着实用主义,还是构造了符号表,符号表又称字典、索引,数据库的曾删改查就是基于符号表(B-树、红黑树构造的符号表)的,此外,Java集合的实现也用到了(TreeMap、TreeSet是用红黑树构造的符号表实现的)。它是二叉树遍历的应用,是AVL、红黑树、B-树等的基础。相对来说,删除操作要麻烦一点,但是理解了二叉树的递归定义和遍历,那么还是比较容易理解的。

查找操作

根据键查找值,先序遍历的应用,这里就不多说了,直接给出代码实现。

//递归实现,采用非递归的话使用一个栈模拟,也不需要驱动程序了
    public int search(String key){
        return search(root, key);
    }
    private int search(TreeNode node, String key){
        if(node == null)
            return -1;
        //用-1表示未找到,相比之下,用泛型会更好,那样就可以返回null,但为了简单,这里没有采用泛型

        int cmp = key.compareTo(node.key);
        if(cmp < 0)
            return search(node.left, key);//左子树中递归查找
        else if(cmp > 0)
            return search(node.right, key);//右子树中递归查找
        else
            return node.value;//当前节点的key就是要查找的key
    }

插入操作

还是先序遍历的应用,递归地在左子树或右子树中插入,直到当前节点为null,此时才是真正意义上的插入操作。要 注意每次插入都要返回当前节点的引用以通知父节点 ,因为若新增加了一个节点,那么当前结点的父节点的链接之一要由null变为非null,否则该节点后面将无法访问。

public void insert(String key, int value){
        root = insert(root, key, value);
    }
    private TreeNode insert(TreeNode node, String key, int value){
        if(node == null)
            return new TreeNode(key, value);

        int cmp = key.compareTo(node.key);
        if(cmp < 0)
            node.left = insert(node.left, key, value);//在左子树中递归插入
        else if(cmp > 0)
            node.right = insert(node.right, key, value);//在右子树中递归插入
        else
            node.value = value;//在此之前树中就有该key,更新value的值
        return node;
    }

删除操作

首先,删除分三种情况:
1. 待删除的节点是叶子节点,即没有孩子,直接采用null替代该节点
2. 待删除的节点有一个孩子,那么用孩子替代该节点
3. 待删除的节点有两个孩子,可以采用前驱节点或后继节点替代,然后更新链接,最后删除前驱节点或后继节点即可。
前驱节点即在中序遍历中某一节点的前一个节点,后继节点即在中序遍历中某一节点的后一个节点。在BST中,前驱节点就是当前节点的左子树中含有最大键的节点(右边界的节点),后继节点就是当前结点的右子树中含有最小键的节点(左边界的节点)。这里删除前驱节点或后继节点也要注意通知父节点 ,因为前驱或后继节点的父节点的链接要由非null变为null,否则中序遍历BST时,本来要被删除的节点将被打印两次。注意,虽然有三种情况,第一种实际上是不用写的,因为第二种情况已经包括了。

//采用前驱节点替代实现删除操作
    public void deleteWithPreNode(String key){
        root = deleteWithPreNode(root, key);
    }
    private TreeNode deleteWithPreNode(TreeNode node, String key){
        if(node == null)
            return null;

        int cmp = key.compareTo(node.key);

        if(cmp < 0)
            node.left = deleteWithPreNode(node.left, key);
        else if(cmp > 0)
            node.right = deleteWithPreNode(node.right, key);
        else{
            if(node.left == null)
                return node.right;
            if(node.right == null)
                return node.left;
            TreeNode temp = node;
            node = max(temp.left);
            node.right = temp.right;
            node.left = deleteMax(temp.left);
        }
        return node;
    }
    //找到前驱节点
    private TreeNode max(TreeNode node){
        if(node == null)
            return null;
        while(node.right != null)
            node = node.right;
        return node;
    }
    //删除前驱节点
    private TreeNode deleteMax(TreeNode node){
        if(node.right == null)
            return node.left;
        node.right = deleteMax(node.right);
        return node;
    }

    //采用后继节点替代实现删除操作
    public void deleteWithPostNode(String key){
        root = deleteWithPostNode(root, key);
    }
    private TreeNode deleteWithPostNode(TreeNode node, String key){
        if(node == null)
            return null;
        int cmp = key.compareTo(node.key);
        if(cmp < 0)
            node.left = deleteWithPostNode(node.left, key);
        else if(cmp > 0)
            node.right = deleteWithPostNode(node.right, key);
        else{
            if(node.left == null)
                return node.right;
            if(node.right == null)
                return node.left;
            TreeNode temp = node;
            node = min(temp.right);
            node.left = temp.left;//要放在调整右链接前面,因为调整右链接可能改变树的结构
            node.right = deleteMin(temp.right);
        }
        return node;
    }
    //找到后继节点
    private TreeNode min(TreeNode node){
        if(node == null)
            return null;
        while(node.left != null)
            node = node.left;
        return node;
    }
    //删除后继节点
    private TreeNode deleteMin(TreeNode node){
        if(node.left == null)
            return node.right;
        node.left = deleteMin(node.left);
        return node;
    }

最后附上所有代码:

package binarySearchTree;

import java.util.*;
/**
 * 二叉查找树BST
 * @author 小锅巴
 */
public class BST {
    private TreeNode root;

    private class TreeNode{
        String key;//键,简单起见,没有采用泛型
        int value;//值,
        TreeNode left;//左链接
        TreeNode right;//右链接

        TreeNode(String key, int value){
            this.key = key;
            this.value = value;
        }
    }


    //递归实现,采用非递归的话使用一个栈模拟,也不需要驱动程序了
    public int search(String key){
        return search(root, key);
    }
    private int search(TreeNode node, String key){
        if(node == null)
            return -1;
        //用-1表示未找到,相比之下,用泛型会更好,那样就可以返回null,但为了简单,这里没有采用泛型

        int cmp = key.compareTo(node.key);
        if(cmp < 0)
            return search(node.left, key);//左子树中递归查找
        else if(cmp > 0)
            return search(node.right, key);//右子树中递归查找
        else
            return node.value;//当前节点的key就是要查找的key
    }


    public void insert(String key, int value){
        root = insert(root, key, value);
    }
    private TreeNode insert(TreeNode node, String key, int value){
        if(node == null)
            return new TreeNode(key, value);

        int cmp = key.compareTo(node.key);
        if(cmp < 0)
            node.left = insert(node.left, key, value);//在左子树中递归插入
        else if(cmp > 0)
            node.right = insert(node.right, key, value);//在右子树中递归插入
        else
            node.value = value;//在此之前树中就有该key,更新value的值
        return node;
    }

    //采用前驱节点替代实现删除操作
    public void deleteWithPreNode(String key){
        root = deleteWithPreNode(root, key);
    }
    private TreeNode deleteWithPreNode(TreeNode node, String key){
        if(node == null)
            return null;

        int cmp = key.compareTo(node.key);

        if(cmp < 0)
            node.left = deleteWithPreNode(node.left, key);
        else if(cmp > 0)
            node.right = deleteWithPreNode(node.right, key);
        else{
            if(node.left == null)
                return node.right;
            if(node.right == null)
                return node.left;
            TreeNode temp = node;
            node = max(temp.left);
            node.right = temp.right;
            node.left = deleteMax(temp.left);
        }
        return node;
    }
    //找到前驱节点
    private TreeNode max(TreeNode node){
        if(node == null)
            return null;
        while(node.right != null)
            node = node.right;
        return node;
    }
    //删除前驱节点
    private TreeNode deleteMax(TreeNode node){
        if(node.right == null)
            return node.left;
        node.right = deleteMax(node.right);
        return node;
    }

    //采用后继节点替代实现删除操作
    public void deleteWithPostNode(String key){
        root = deleteWithPostNode(root, key);
    }
    private TreeNode deleteWithPostNode(TreeNode node, String key){
        if(node == null)
            return null;
        int cmp = key.compareTo(node.key);
        if(cmp < 0)
            node.left = deleteWithPostNode(node.left, key);
        else if(cmp > 0)
            node.right = deleteWithPostNode(node.right, key);
        else{
            if(node.left == null)
                return node.right;
            if(node.right == null)
                return node.left;
            TreeNode temp = node;
            node = min(temp.right);
            node.left = temp.left;//要放在调整右链接前面,因为调整右链接可能改变树的结构
            node.right = deleteMin(temp.right);
        }
        return node;
    }
    //找到后继节点
    private TreeNode min(TreeNode node){
        if(node == null)
            return null;
        while(node.left != null)
            node = node.left;
        return node;
    }
    //删除后继节点
    private TreeNode deleteMin(TreeNode node){
        if(node.left == null)
            return node.right;
        node.left = deleteMin(node.left);
        return node;
    }

    //层序遍历
    private void layerTraversal(TreeNode node){
        Queue<TreeNode> s = new LinkedList<>();
        s.add(node);
        TreeNode curNode;
        TreeNode nlast = null;
        TreeNode last = node;
        while(!s.isEmpty()){
            curNode = s.poll();
            System.out.print(curNode.key+" ");
            if(curNode.left != null){
                s.add(curNode.left);
                nlast = curNode.left;
            }
            if(curNode.right != null){
                s.add(curNode.right);
                nlast = curNode.right;
            }
            if(curNode == last){
                System.out.println();
                last = nlast;
            }
        }
    }
    public static void main(String[] args) {
        BST bst = new BST();
        System.out.println("请输入节点的个数:");
        Scanner s = new Scanner(System.in);
        int num = s.nextInt();
        System.out.println("请依次输入"+num+"个字母:");
        for(int i = 1 ; i <= num; i++){
            String key = s.next();
            bst.insert(key, i);
        }
        System.out.println("层序遍历:");
        bst.layerTraversal(bst.root);
        System.out.println();

        System.out.println("测试查找操作,请输入要查询的key:");
        String key = s.next();
        System.out.println("键为"+key+"的键值对中的值为"+bst.search(key));

        System.out.println("测试采用前驱节点替代的删除操作,请输入要删除的节点:");
        String key1 = s.next();
        bst.deleteWithPreNode(key1);
        System.out.println("删除"+key1+"节点后,层序遍历:");
        bst.layerTraversal(bst.root);
        System.out.println();

        System.out.println("测试采用后继节点替代的删除操作,请输入要删除的节点:");
        String key2 = s.next();
        bst.deleteWithPostNode(key2);
        System.out.println("删除"+key2+"节点后,层序遍历:");
        bst.layerTraversal(bst.root);
        System.out.println();
    }
}

测试用例,为了构造一个平衡二叉树(AVL,此树同时也是个满二叉树),输入顺序是先序遍历的结果。

请输入节点的个数:
7
请依次输入7个字母:
D
B
A
C
F
E
G
层序遍历:
D 
B F 
A C E G 

测试查找操作,请输入要查询的key:
C
键为C的键值对中的值为4
测试采用前驱节点替代的删除操作,请输入要删除的节点:
G
删除G节点后,层序遍历:
D 
B F 
A C E 

测试采用后继节点替代的删除操作,请输入要删除的节点:
E
删除E节点后,层序遍历:
D 
B F 
A C 

参考资料;
1. 算法第四版
2. http://www.tuicool.com/articles/Uvem2y

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值