【数据结构-树论】二叉查找树(BinarySearchTree)

引入

回想二叉树。

二叉树可扩展为两种数据结构:堆和平衡树。

定义及思想

满足 BST 性质的二叉树就是一颗“二叉查找树”。

BST 性质:
  • 任意一个节点的值不小于其左子树的任意节点的值;
  • 任意一个节点的值不大于其右子树的任意节点的值;
    (这样:左子树 \(\leq\) 父亲 \(\leq\) 右子树。)
  • 不存在重复节点。

作用及实现

通过将大小不同的两组数据分别置于根的两边,达到排序的效果。

节点:

要存 左孩子、右孩子、自身值、子树大小、本身数量(就是该值的数共有几个)。

点击查看代码
struct node {
    int l, r, val, siz, cnt;
} tr[man];

插入:

首先判断:

  • 若树还没有节点(tot == 0),则直接将根节点赋值(大小为 \(1\))。

  • 否则执行插入操作:
    递归进入适合的位置:

    • 当前节点的 val 与要插入的 val 相同就直接将 cnt 加一;
    • 大了就递归进入左子树,若没有左子树就将孩子赋值;
    • 小了就递归进入右子树,若没有右子树就将孩子赋值。

    (由于每个经过的点必然是将要插入节点的父亲,所以要将子树大小增加 \(1\)。)

                        ①4
                       /  \
                     ②2   ③6
                    / \   /  \
                   ④1 N ⑤5  ⑥8
点击查看插入代码
    void ins (int x, int val) {
        ++ tr[x].siz;
        if (tr[x].val == val) ++ tr[x].cnt;
        else if (tr[x].val > val) 
            if (ls) ins(ls, val);
            else ls = ++ tot, tr[tot] = {0, 0, val, 1, 1};
        else 
            if (rs) ins(rs, val);
            else rs = ++ tot, tr[tot] = {0, 0, val, 1, 1};
        return ;
    }

查询值的排名:

(这里排名的定义是前面有几个小于本身的数 +1 而不是有几种小于本身的数 +1。)

在查询函数中只返回前面有几个小于本身,所以输出时要加一。

  • 如果不小心进入了奇怪的节点返回 \(0\)(就不会影响排名);
  • 如果值是相同返回左子树的大小;
  • 否则如果当前值大于查询值就返回对左孩子的递归搜索;
  • 否则(当前值小于查询值)就返回左子树大小、本身数量和对右孩子的递归搜索结果之和。

E.G.:
查询 5 的排名:先从根开始,4 < 5,进入右子树(+3);③6 > 5,进入左子树;5 = 5,左子树大小加本身 = 1,所以排名是 4。

                        ①4
                       /  \
                     ②2   ③6
                    /     /  \
                   ④1   ⑤5  ⑥8
点击查看查询排名代码
int querk (int x, int val) {
    if (!x) return 0;
    if (tr[x].val == val) return tr[ls].siz;
    else if (tr[x].val > val) return querk(ls, val);
    else return tr[ls].siz + tr[x].cnt + querk(rs, val);    
}

查询某个排名上的值:

排名意为包括自己比自己小的有几个。
先从根开始搜索:

  • 如果不小心进入了奇怪的节点返回 \(0\)
  • 如果左子树的大小大于等于排名(该排名的数在左子树上),就返回对左子树递归搜索;
  • 否则如果左子树的大小加上本身数量大于等于排名(本身就是这个排名了),就返回自己的值;
  • 否则(该排名的数在右子树上)就返回对右孩子的递归搜索结果(那么递归进去的排名要减去左子树大小和本身数量)。

E.G.:
第 4 大:先从根开始,左子树加上本身 = 3 < 4,进入右子树,递归进去(4-3=)1;③的左子树大小 = 1,所以结果是 ⑤5。

                        ①4
                       /  \
                     ②2   ③6
                    /     /  \
                   ④1   ⑤5  ⑥8
点击查看查询值代码
int find (int x, int rk) {
        if (!x) return 0;
        if (tr[ls].siz >= rk) return find(ls, rk);
        else if (tr[ls].siz+tr[x].cnt >= rk) return tr[x].val;
        else return find(rs, rk-tr[ls].siz-tr[x].cnt);
    }

查询前驱后继:

前驱:在数列中小于自己的最大的数;
后继:在数列中大于自己的最小的数。

这是前驱:
先设置一个答案,再从根开始搜索:
如果当前值等于查询值,那么先进入他的左子树,再找左子树中最靠右的叶子,直接退出;
否则判断当前值是否小于查询值且大于已记录答案值,若成立,更改答案。
从当前节点进入左子树或右子树(看当前值和查询值的大小关系),再进行下一轮循环。
返回的时候判断当前是否是真正的节点,若不是返回约定值。

点击查看前驱代码
int finlst (int val){
    int res = miv, x = 1;
    while (x) {
        if (val == tr[x].val) {
            if (ls) {
                x = ls;
                while (rs) x = rs;
                res = tr[x].val;
            } break;
        } if (tr[x].val<val && tr[x].val>res) res = tr[x].val;
        x = val<tr[x].val? ls : rs;
    } return res;
}
点击查看后继代码
int finext (int val) {
    int res = mav, x = 1;
    while (x) {
        if (val == tr[x].val) {
            if (rs) {
                x = rs;
                while (ls) x = ls;
                res = tr[x].val;
            } break;
        } if (tr[x].val>val && tr[x].val<res) res = tr[x].val;
        x = val<tr[x].val? ls: rs;
    } return res;
}

E.G. 1:
6 的前驱:先从根开始,进入第二个 if,res 更新为 ①4,进入 x 的右孩子,当前值 == 6,进入左子树,找到叶子,答案是 ⑤5.

                        ①4
                       /  \
                     ②2   ③6
                    / \   /  \
                   ④1 N ⑤5  ⑥8

E.G. 2:
N 的后继:先从根开始,进入左孩子,res 更新为 ①4,进入 ② 的右孩子,当前值 == N,没有右子树,无法更新,答案是 ①4.

删除


例题

完整代码
class BST {
public:
    int tot;
    struct node {
        int l, r, val, siz, cnt;
    } tr[man];
    #define ls tr[x].l
    #define rs tr[x].r
    void ins (int x, int val) {
        ++ tr[x].siz;
        if (tr[x].val == val) ++ tr[x].cnt;
        else if (tr[x].val > val) 
            if (ls) ins(ls, val);
            else ls = ++ tot, tr[tot] = {0, 0, val, 1, 1};
        else 
            if (rs) ins(rs, val);
            else rs = ++ tot, tr[tot] = {0, 0, val, 1, 1};
        return ;
    }
    int querk (int x, int val) {
        if (!x) return 0;
        if (tr[x].val == val) return tr[ls].siz;
        else if (tr[x].val > val) return querk(ls, val);
        else return tr[ls].siz + tr[x].cnt + querk(rs, val);    
    }
    int find (int x, int rk) {
        if (!x) return 0;
        if (tr[ls].siz >= rk) return find(ls, rk);
        else if (tr[ls].siz+tr[x].cnt >= rk) return tr[x].val;
        else return find(rs, rk-tr[ls].siz-tr[x].cnt);
    }
    int finlst (int val) {
        int res = miv, x = 1;
        while (x) {
            if (val == tr[x].val) {
                if (ls) {
                    x = ls;
                    while (rs) x = rs;
                    res = tr[x].val;
                } break;
            } if (tr[x].val<val && tr[x].val>res) res = tr[x].val;
            x = val<tr[x].val? ls : rs;
        } return res;
    }
    int finext (int val) {
        int res = mav, x = 1;
        while (x) {
            if (val == tr[x].val) {
                if (rs) {
                    x = rs;
                    while (ls) x = ls;
                    res = tr[x].val;
                } break;
            } if (tr[x].val>val && tr[x].val<res) res = tr[x].val;
            x = val<tr[x].val? ls: rs;
        } return res;
    }
    #undef ls
    #undef rs
} B; 

扩展

平衡树

posted @ 2024-07-01 16:11  STA_Morlin  阅读(14)  评论(0编辑  收藏  举报