引入二叉搜索树(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;
}