二叉搜索树学习笔记

之前都是野蛮生长

怕离开了互联网当场退役,就重修一下

什么是搜索树

它是一种将链表插入的灵活性和有序数组查找的高效性结合起来的数据结构

算了我直接写 API 实现了以后再填坑吧

二叉搜索树(Binary Search Tree)

public class Tree<Key extends Comparable<Key>> {

    private Node root;

    public Key get(Key key) {
        return get(root, key);
    }

    private Key get(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0)       return get(x.left, key);
        else if (cmp > 0) return get(x.right, key);
        return x.key;
    }

    public void put(Key key) { this.root = put(root, key); }

    private Node put(Node x, Key key) {
        if (x == null) return new Node(key);
        int cmp = key.compareTo(x.key);
        if (cmp < 0)        x.left = put(x.left, key);
        else if (cmp > 0) x.right = put(x.right, key);
        return x;
    }

    private class Node {

        Key key;
        Node left, right;

        Node(Key key) { this.key = key; }
    }
}

在二叉搜索树最难实现的就是 delete() 方法
所以分层展示

public class Tree<Key extends Comparable<Key>> {

    private Node root;

    public Key get(Key key) { return null; }
    
    public void put(Key key) { }

    private Node findMin(Node x) {
        if (x.left == null) return x;
        return findMin(x.left);
    }

    private Node deleteMin(Node x) {
        if (x.left == null) return x.right;
        x.left = deleteMin(x.left);
        return x;
    }

    public void delete(Key key) { root = delete(root, key); }

    public Node delete(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0)        x.left = delete(x.left, key);
        else if (cmp > 0) x.right = delete(x.right, key);
        else {
            if (x.left == null) return x.right;
            if (x.right == null) return x.left;
            Node temp = x;
            x = findMin(x.right);
            x.right = deleteMin(temp.right);
            x.left = temp.left;
        }
        return x;
    }

    private class Node { . . . }
}

为什么 delete() 方法是最难实现的?

因为插入和搜索,无非就是入参小于当前节点键,就往左走,大于当前节点键,就往右走
非此即彼

但删除过程中,要想保持二叉树的有序性,需要考虑的情况可不止这两种

首先最简单是删除叶子节点,删除叶子节点不会破会树的有序性,所以可以直接删除

再就是删除只有一个子树的节点,删除这个节点时,只需将自己的子树交给父节点即可

这两种情况我们不展开讨论
它们都可以被归纳成一种情况

if (x.left == null) return x.right;
if (x.right == null) return x.left;

删除方法的难点就在于,在节点同时拥有左右子树时,如何填补删除后的空白

我们将补上的节点称为后继节点

如何去选择后继节点,如何去截取后继节点,操作后能否继续保持树的有序性,这都是值得入门者讨论的事

我们通常把中序遍历删除节点后的第一个节点称为后继节点
按照二叉搜索树的定义,这个节点是删除节点右树最小的节点

我们要做的就是把这个节点截取下来放到删除节点的位置上面

当然根据搜索二叉树的定义
你可以继承右树把左树直接插入到右树最左,或者反过来应该也是可以的,但这样既不会多少运行时间,同时会让树的深度难以控制,这个放在平衡二叉树中讨论


基本模板

public class Tree<Key extends Comparable<Key>> {

    private Node root;

    public Key get(Key key) {
        return get(root, key);
    }

    private Key get(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) return get(x.left, key);
        else if (cmp > 0) return get(x.right, key);
        return x.key;
    }

    public void put(Key key) { this.root = put(root, key); }

    private Node put(Node x, Key key) {
        if (x == null) return new Node(key);
        int cmp = key.compareTo(x.key);
        if (cmp < 0) x.left = put(x.left, key);
        else if (cmp > 0) x.right = put(x.right, key);
        return x;
    }

    private Node findMin(Node x) {
        if (x.left == null) return x;
        return findMin(x.left);
    }

    private Node deleteMin(Node x) {
        if (x.left == null) return x.right;
        x.left = deleteMin(x.left);
        return x;
    }

    public void delete(Key key) { root = delete(root, key); }

    public Node delete(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) x.left = delete(x.left, key);
        else if (cmp > 0) x.right = delete(x.right, key);
        else {
            if (x.left == null) return x.right;
            if (x.right == null) return x.left;
            Node temp = x;
            x = findMin(x.right);
            x.right = deleteMin(temp.right);
            x.left = temp.left;
        }
        return x;
    }

    private class Node {

        Key key;
        Node left, right;

        Node(Key key) { this.key = key; }
    }
}

在这个实现里键就是之,值就是键
他还遵循了二叉搜索树的基本定义

在这个实现里,每个节点
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
它的左、右子树也分别为二叉排序树

这也就意味着,它无法存储重复键
而大部分的入门级训练题往往都包含并需要重复值进行计算以确保结果的正确,想要实现这一点,可以薛微修改一下定义
如果需要,大部分情况下我们使得右子树的节点均大于等于它根节点的值

但这么定义的二叉排序树几乎不可能达到平衡,因为一个 key 出现 n 次就会有 n - 1 个无法被插入的左树

所以我们需要加权,真正的把 key 派上用场

也没人跟我讨论啊

节点大小平衡二叉树,和红黑树的模板,有缘就填坑

节点大小横树

模板

public class Tree<Key extends Comparable<Key>, Value> {

    private Node root;

    public void put(Key key, Value value) { this.root = put(root, key, value); }

    public Node put(Node x, Key key, Value value) {
        if (x == null) return new Node(key, value);
        x.size++;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            x.left = put(x.left, key, value);
            x = LeftMaintain(x);
        } else if (cmp > 0) {
            x.right = put(x.right, key, value);
            x = RightMaintain(x);
        } else {
            x.value = value;
            x.size--;
        }
        return x;
    }

    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) return get(x.left, key);
        else if (cmp > 0) return get(x.right, key);
        return x.value;
    }

    private Node LeftRotate(Node x) {
        Node t = x.right;
        t.size = x.size;
        x.right = t.left;
        t.left = x;
        x.size = sizeOf(x.left) + sizeOf(x.right) + 1;
        return t;
    }

    private Node Maintain(Node x) { return RightMaintain(LeftMaintain(x));  }

    private Node LeftMaintain(Node x) {
        if (x.left != null && sizeOf(x.left.left) > sizeOf(x.right)) {
            x = RightRotate(x);
            x.right = RightMaintain(x.right);
            return Maintain(x);
        } else if (x.left != null && sizeOf(x.left.right) > sizeOf(x.right)) {
            x.left = LeftRotate(x.left);
            x = RightRotate(x);
            x.left = LeftMaintain(x.left);
            x.right = RightMaintain(x.right);
            return Maintain(x);
        } else return x;
    }

    private Node RightMaintain(Node x) {
        if (x.right != null && sizeOf(x.right.right) > sizeOf(x.left)) {
            x = LeftRotate(x);
            x.left = LeftMaintain(x.left);
            return Maintain(x);
        } else if (x.right != null && sizeOf(x.right.left) > sizeOf(x.left)) {
            x.right = RightRotate(x.right);
            x = LeftRotate(x);
            x.left = LeftMaintain(x.left);
            x.right = RightMaintain(x.right);
            return Maintain(x);
        } else return x;
    }

    private Node RightRotate(Node x) {
        Node t = x.left;
        t.size = x.size;
        x.left = t.right;
        t.right = x;
        x.size = sizeOf(x.left) + sizeOf(x.right) + 1;
        return t;
    }

    private int sizeOf(Node x) { return x == null? 0: x.size; }

    private class Node {

        Key key;
        Value value;
        int size =  1;
        Node left, right;

        Node(Key key, Value value) {
            this.key = key;
            this.value = value;
        }
    }
}

没有 & 引用的Java,写的头皮发麻

其他方法应该都是普通搜索二叉树的实现

主要还是没有模拟全部维护情况的能力,不能完全知道在什么时候会遇到 null 虽然加个 x.right != null 就好,但写的不是很舒服

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值