• 「算法笔记」二叉查找树


    一、二叉查找树

    二叉查找树(Binary Search Tree,下文简称 BST)是一种二叉树的树形数据结构。

    树上的每个节点带有一个数值,称为节点的“关键码”(也可以叫其他的 QAQ)。对于树中的任意一个节点:

    • 该节点的关键码不小于它的左子树中任意节点的关键码。
    • 该节点的关键码不大于它的右子树中任意节点的关键码。

    即左子树任意节点的值 (<) 根节点的值 (<) 右子树任意节点的值。

    满足上述性质的二叉树就是一棵 BST。显然,BST 的中序遍历是一个关键码单调递增的节点序列。

    二、基本操作

    约定:(lc(u))(rc(u)) 分别表示节点 (u) 的左子节点和右子节点,(val(u)) 表示节点 (u) 的关键码。

    1. BST 的建立

    为了避免越界,减少边界情况的判断,我们在 BST 中额外插入关键码为 (+infty)(-infty) 的节点。仅由这两个节点构成的 BST 就是一棵初始的空 BST。

    简便起见,在接下来的操作中,假设 BST 不含有关键码相同的节点。

    int tot,rt,lc[N],rc[N],val[N];    //rt 为根结点在数组中的下标,lc(u) 和 rc(u) 分别表示 u 的左右子节点在数组中的下标,val(u) 表示节点 u 的关键码 
    void build(){
        val[++tot]=-1e18,val[++tot]=1e18;    //新建关键码为负无穷和正无穷的节点(它们在数组中的下标分别为 1、2) 
        rt=1,rc[1]=2;    //1 为根结点(对应关键码为负无穷的节点),它的右儿子为关键码为正无穷的节点 
    }

    2. BST 的检索

    在 BST 中检索是否存在关键码为 (k) 的节点。

    (p) 为根结点,执行以下过程:

    1. (val(p)=k),则已找到。

    2. (val(p)>k):若 (lc(p)) 为空,则不存在 (k);否则,在 (p) 的左子树中递归进行检索。

    3. (val(p)<k):若 (rc(p)) 为空,则不存在 (k);否则,在 (p) 的右子树中递归进行检索。

    int find(int p,int k){    
        if(!p) return 0;    //检索失败 
        if(val[p]==k) return p;    //检索成功 
        return k<val[p]?find(lc[p],k):find(rc[p],k);
    } 

    3. BST 的插入

    在 BST 中插入一个关键码为 (k) 的节点。(假设目前 BST 中不存在关键码为 (k) 的节点)

    与 BST 的检索类似。要走向的 (p) 的子节点为空,说明 (k) 不存在时,直接建立关键码为 (k) 的新节点作为 (p) 的子节点。

    void insert(int &p,int k){
        if(!p){val[++tot]=k,p=tot;return ;}    //注意 p 是引用,其父节点的 lc 或 rc 值会被同时更新 
        if(val[p]==k) return ;
        if(k<val[p]) insert(lc[p],k);
        else insert(rc[p],k);
    } 

    4. BST 求前驱/后继

    以“后继”为例。(k) 的后继指在 BST 中关键码大于 (k) 的节点中,关键码最小的节点。

    初始化 (ans) 为关键码为 (+infty) 的节点。然后,在 BST 中检索 (k)。每经过一个节点,都尝试更新 (ans)

    1. 没有找到 (k)。此时 (k) 的后继就在已经经过的节点中,(ans) 即为所求。

    2. 找到了节点 (p) 使得 (val(p)=k)。若 (rc(p)) 为空,则 (ans) 即为所求;否则,从 (rc(p)) 出发,一直向左走,就找到了 (k) 的后继。

    int getnxt(int k){
        int ans=2,p=rt;    //val(2)=+∞
        while(p){
            if(val[p]==k){
                if(rc[p]>0){p=rc[p]; while(lc[p]>0) p=lc[p]; ans=p;}
                break;
            }
            if(val[p]>k&&val[p]<val[ans]) ans=p;    //尝试更新 ans
            p=k<val[p]?lc[p]:rc[p]; 
        }
        return ans;
    }

    5. BST 的节点删除

    在 BST 中删除关键码为 (k) 的节点。

    首先,在 BST 中搜索 (k),得到节点 (p)

    (p) 没有左子树或没有右子树,则直接删除 (p),并令 (p) 的子节点代替 (p) 的位置,与 (p) 的父节点相连。

    (p) 左右子树都有,则在 BST 中求出 (k) 的后继节点 (next)。因为 (next) 没有左子树(因为 (next) 是从 (p) 的右子节点出发,一直向左走得到的),所以可以直接删除 (next),并令 (next) 的右子树代替 (next) 的位置。最后,再让 (next) 节点代替 (p) 节点,删除 (p) 即可。举个栗子:

    应该还是比较好理解哒,具体见代码。

    void del(int &p,int k){    //从子树 p 中删除值为 k 的阶段 
        if(!p) return ;
        if(val[p]==k){    //已经检索到值为 k 的阶段 
            if(!lc[p]) p=rc[p];    //没有左子树,右子树代替 p 的位置,注意 p 是引用 
            else if(!rc[p]) p=lc[p];    //没有右子树,左子树代替 p 的位置,注意 p 是引用 
            else{    //既有左子树又有右子树
                int nxt=rc[p]; 
                while(lc[nxt]>0) nxt=lc[nxt];    //求后继节点(从 p 的右子节点出发,一直向左走) 
                del(rc[p],val[nxt]);    //next 一定没有左子树,直接删除 
                lc[nxt]=lc[p],rc[nxt]=rc[p],p=nxt;    //令节点 next 代替节点 p 的位置。注意 p 是引用 
            }
            return ;
        }
        if(k<val[p]) del(lc[p],k);
        else del(rc[p],k); 
    }

    三、参考资料

    • 《算法竞赛进阶指南》(大棒子,做摘抄 233)

    注:这篇文章可能会有锅 QAQ

  • 相关阅读:
    数据库-自定义函数
    数据库-存储过程
    数据库配置
    水电费管理系统需求分析与设计_待完善
    SQL中Group By的使用
    部分查询功能语句
    10-11数据库练习
    Oracle-SQL
    开发环境之Gradle
    解决程序端口占用
  • 原文地址:https://www.cnblogs.com/maoyiting/p/14220265.html
走看看 - 开发者的网上家园