学习笔记 二叉搜索树(BST)

本文介绍了二叉搜索树(BST)的基本概念及其查找、插入和删除算法的实现原理。通过对比线性结构,阐述了BST如何提高查找效率,并详细解析了相关算法的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引入二叉搜索树(BST)结构,以及后续高级搜索树的变种,是为了倾向解决一个问题——查找技术。

  • 线性结构:

    • 向量结构,支持高效静态访问的典型结构,属于随机访问顺序容器
    • 列表结构,支持高效动态操作的典型结构,但是静态操作将花费O(n)的时间,可逆顺序容器
  • 半线性结构:

    • 二叉树结构,一般不支持查找接口,在一棵二叉树中查找某个节点极端情况也就是树高(退化成一条单链表)
    • 二叉搜索树,二叉树一个变种,如果说二叉树是任意一颗有序多叉树,那么二叉搜索树将更强调“有序

查找方式的转变:循关键码访问

这一新的访问方式,与数据对象的物理位置或逻辑次序均无关。查找的过程与结果,仅仅取决于目标对象的关键码。所以这种访问方式称之为循关键码访问。
词条模板如下:

template<typename k,typename v>struct Entry{
    K key;V value;
    Entry(K k=K(),V v=V()):/*默认构造函数*/
    key(k),value(v){}
    Entry(Entre<K,V> const& e)():/*复制构造函数*/
    key(e.key),value(e.value){}
    //重载运算符作为成员函数*强调内容*
    bool operator< (Entry<K,V> const& e){return key<e.key}
    ...需要时自己重载
} 
//BST模板类
#include "BinTree.h"
template<typename T>class BST : public BinTree<T>{
//BST模板类由BinTree<T>模板类派生而来
//基类:BinTree<T>
//派生类:BST<T>
protected:
    BinNodePosi(T) _hot;
public:

    //基类BinTree<T>没有含形参的构造函数
    //因此在派生类BST<T>中无需显式给出构造函数,由编译器调用默认构造函数即可。

    virtual BinNodePosi(T) insert(const T& e);//由BST<T>派生出的其他高级搜索树,因将成员函数设置为虚函数
    //注意 查找接口返回的是BinTree<T>*&
    virtual BinNodePosi(T) & Search(const T& e);//同上
    /*根据类型兼容规则,BinTree<T>::remove(BinNode<T>*)将被隐藏,注意:如需调用请使用作用域分辨符*/
    virtual bool remove(const T& e);
    //3+4重构:只有AVL树用得上多以不用添加virtual关键字
    BinNodePosi(T) connect34(BinNodePosi(T) a,BinNodePosi(T) b,BinNodePosi(T) c
    BinNodePosi(T) T0,BinNodePosi(T) T1,BinNodePosi(T) T2,BinNodePosi(T) T3);
    BinNodePosi(T) rotateAt(BinNodePosi(T) x);
}

查找算法的实现,采用减而治知的策略,实现二分查找树结构版。

//全局静态函数,适用于(AVL,SPLAY,RBTREE)派生类
template<typename T>static BinNodePosi(T) & SearchIn(BinNodePosi(T) &v,const T& e,BinNodePosi(T) &hot){
    /*处理递归基。
    *如果遇到v不存在或者直接命中,返回即可。
    *而如果没有遇到,将v交给_hot。
    */
    if(!v||e==v->data)  return v;
    _hot=v;
    if(e<v->data) return SearchIn(v->left,e,hot);//深入左子树
    else return SearchIn(v->right,e,hot);//深入右子树

    //迭代版
    if(!v||e==v->data) return v;
    while(true){
        BinNodePosi(T) &cur = (e < v->data) ? v->left : v->right;
        if (!cur || cur->data == e) return cur;
        hot = cur;
    }

}

//不论是查找成功或是失败,_hot都将返回“命中节点”的父亲。

template<typename T>BinNodePosi(T) & Search(BinNodePosi(T) &v,const T& e,BinNodePosi(T) &_hot){
    return SearchIn(_root,e,_hot=NULL);
}

在二叉搜索树的每一层,该算法至多访问一个节点,所以总体所需时间线性正比于树高,目标关键码出现在根附近仅需O(1)时间。最坏情况为退化为一条单链,由二叉搜索树退化为有序列表。

插入算法基于以下事实。
在二叉树中插入节点node之后

  • 除node的历代祖先以外,其余节点高度无需更新
  • 祖先高度不会降低,但至多加一
  • 一旦某个祖先高度不变,更高的祖先也必然高度不变
template<typename T>BinNodePosi(T) insert(const T& e){
    BinNodePosi(T) &x = Search(e);
    if(x) return x;
    else{
        x = new BinNode<T>(e,_hot);
        _size++;
        //时间复杂度主要消耗在更新树高
        updateHeightAbove(x);
        return x;
    }
}

每一个插入的节点,都需要更新其历代祖先的高度,故时间复杂度最多为树高,深度最深的节点插入时。

删除算法与插入算法有共同点也有差别。
若删除节点node后。

  • 除node的历代祖先以外,其余节点的高度无需更新
  • 祖先的高度不会增加,但至多减一
  • 一旦某个祖先高度不变,更高的祖先也必然高度不变
template<typename T>
bool BST<T>::remove(T const & e)
{
    BNodePosi(T) &node = Search(e);
    if (!node) return false;
    removeAt(node, _hot);
    _size--; updateHeightAbove(_hot);
    return true;
}

先Search(e)一趟,如果node返回的值是NULL,可以返回失败。如果找到则需调用removeAt()接口。实际删除操作是由removeAt()接口实现的。

//同名隐藏。
template<typename T>/*全局静态函数*/
static BinNodePosi(T) removeAt(BinNodePosi(T) &node, BinNodePosi(T) &hot) {
    BNodePosi(T) cur = node;
    BNodePosi(T) succ = NULL;
    if (!HasLChild(cur)) {
        succ = cur = cur->right;
    }
    else if (!HasRChild(cur)) {
        succ = cur = cur->left;
    }
    else {
        cur = cur->succ();//一味与后继交换,会产生问题。
        std::swap(cur->data, node->data);
        //分两种情况,当后继的父节点与待删除节点一致时,根据二叉搜索树的有序性,分别取右(左)孩子
        BNodePosi(T) u = cur->parent;
        ((u == node) ? u->right : u->left) = succ = cur->right;
    }
    //维持全树的拓扑关系
    hot = cur->parent;
    if (succ) succ->parent = hot;
    release(cur->data); release(cur);
    return succ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值