本文使用C++实现二叉搜索树(binary search tree,bst)数据结构。
一、为什么要引入二叉搜索树
通过对向量和列表这两类线性数据结构的原理分析,我们可以发现向量结构在对静态查询操作的支持比较好,有序向量的二分查找能做到在O(logn)的时间,但是对删除和插入操作在最坏情况下能达到O(n)的时间。而与向量相对立的列表结构能很好地支持动态操作,如删除和插入只需要O(1)的时间,但是它对于静态查找操作的支持却很差,如有序列表的查找在最坏情况下也需要O(n)的时间。所以问题来了:若既要求对象集合的组成可以高效率地动态调整,同时也需要能够高效率的查找,则向量和列表结构都难以胜任。
若将二分查找策略推广到类似列表这种动态存储的数据结构,就能既能保证静态查找操作的高效性能,也能保证动态增删操作的高效性能。基于这种思想,将二分查找策略进行抽象和推广,可以定义并实现二叉搜索树结构。
二、二叉搜索树的特点
所谓的二叉搜索树,处处都满足顺序性:在任一节点r的左(右)子树中,所有节点均不大于(不小于)r。
如上图可见,任何一棵二叉树是二叉搜索树,当且仅当其中序遍历序列单调非降。
三、二叉搜索树的性能
因为二叉搜索树的结构融合了二分查找的思想,所以可以通过二分查找策略对指定值进行搜索,对于深度为h的二叉搜索树,不难证明在最坏情况下的时间复杂度为O(h)。然而不幸的是,对于规模为n的二叉搜索树,深度在最坏的情况下能达到O(n),比如当树退化为一条单链时,查找的时间复杂度就和列表结构一样为O(n)了。
由上可见,若要控制单次查找在最坏情况下的运行时间,必须从二叉搜索树的高度入手,其实二叉搜索树本身没有多么实际的作用,它最大的贡献是将二分查找策略融合到树结构中,这种树结构使树结构的查找操作效率大大提升。事实上,若能解决高度的问题,那么二叉搜索树就能真正地使用了,这就是后面要介绍的平衡二叉树,其具有很多的变种,可解决高度的问题。
四、二叉搜索树的实现
所以,二叉搜索树的实现最大的意义是为各种平衡二叉树提供模板接口,主要包括查找,插入和删除操作。这里通过构造bst类实现二叉搜索树数据结构,其由binTree类继承而来,参见https://blog.csdn.net/qq_18108083/article/details/84727888。
操作 | 功能 | 对象 |
search(const T& e) | 查找指定元素,返回命中节点,并返回其父亲节点 | 二叉搜索树 |
insert(const T& e) | 按照二叉搜索树的结构要求插入指定元素 | 二叉搜索树 |
remove(const T& e) | 按照二叉搜索树的结构要求删除元素 | 二叉搜索树 |
(1) bst.h
#pragma once
#include"binTree.h"
//二叉搜索树模板类
template<typename T> class bst :public binTree<T>
{
public:
binNode<T>* _hot; //所命中的节点的parent
binNode<T>* connect34( //按照3+4结构,联接3个节点及4颗树
binNode<T>*, binNode<T>*, binNode<T>*,
binNode<T>*, binNode<T>*, binNode<T>*, binNode<T>*
);
binNode<T>* rotateAt(binNode<T>* v); //对x及父亲、祖父做统一旋转调整
public:
static binNode<T>* & searchIn(binNode<T>* &v, const T& e, binNode<T>* &hot); //在以v为根的子树中查找关键码e,并将命中节点的parent返回给hot
virtual binNode<T>* & search(const T& e); //查找
virtual binNode<T>* insert(const T& e); //插入
virtual bool remove(const T& e); //删除
static binNode<T>* removeAt(binNode<T>* &x, binNode<T>* &hot); //删除位置x所指的节点(以判断存在),返回值指向实际被删除者的接替者,hot指向被实际删除者的父亲
};
template<typename T> binNode<T>* & bst<T>::searchIn(binNode<T>* &v, const T& e, binNode<T>* &hot)
{
if (!v || (v->data == e)) //若v本身不存在或则直接命中,则返回v
return v;
hot = v;
return searchIn((e < (v->data)) ? v->lc : v->rc, e, hot);
}
template<typename T> binNode<T>* & bst<T>::search(const T& e)
{
return searchIn(_root, e, _hot = nullptr);
} //返回时,返回值指向命中节点或假想的哨兵节点,hot指向其parent
template<typename T> binNode<T>* bst<T>::insert(const T& e)
{
//首先search查询是否存在或待插入的位置
binNode<T>* &x = search(e);
if (x) //若已经存在则返回
return x;
//否则直接插在x上,设置好前后连接关系
x = new binNode<T>(e, _hot);
_size++;
updateHeightAbove(x);
return x;
}
template<typename T> bool bst<T>::remove(const T& e)
{
binNode<T>* succ=nullptr; //缓存替代者节点
//首先搜索是否存在
binNode<T>* &x = search(e); //缓存将要被删除的节点
binNode<T>* temp = x;
if (!x) //不存在则直接返回
return false;
//case 1 :命中节点至多只有一个孩子,则直接删除,孩子顶上
if (!(x->lc)) //左孩子为空,则右孩子顶上
{
succ = x = x->rc;
}
else if(!(x->rc))
{
succ = x = x->lc;
}
//case 2 :命中节点有两个孩子,则通过succ找直接后继节点
else
{
temp = temp->succ();
swap(x->data, temp->data); //交换数据后temp成为实际要删除的点
binNode<T>* u = temp->parent;
((u == x) ? u->rc : u->lc) = succ = temp->rc; //跨过节点succ(只有一种情况是左孩子)
}
_hot = temp->parent;
if (succ)
succ->parent = _hot;
delete temp;
_size--;
updateHeightAbove(_hot);
return true;
}
template<typename T> binNode<T>* bst<T>::removeAt(binNode<T>* &x, binNode<T>* &hot)
{
binNode<T>* w = x; //实际被摘除的节点
binNode<T>* succ = nullptr; //实际被删除的节点的接替者
if (!(x->lc)) //左孩子为空,则右孩子顶上
{
succ = x = x->rc;
}
else if (!(x->rc))
{
succ = x = x->lc;
}
//case 2 :命中节点有两个孩子,则通过succ找直接后继节点
else
{
w = w->succ(); //中序遍历的直接后继
swap(x->data, w->data); //交换数据
binNode<T>* u = w->parent;
((u == x) ? u->rc : u->lc) = succ = w->rc; //隔离实际删除点
}
hot = w->parent;
if (succ) succ->parent = hot;
delete w;
return succ;
}
template<typename T> binNode<T>* bst<T>::connect34( //按照3+4结构,联接3个节点及4颗树
binNode<T>* a, binNode<T>* b, binNode<T>* c,
binNode<T>* T0, binNode<T>* T1, binNode<T>* T2, binNode<T>* T3)
{
a->lc = T0; if (T0) T0->parent = a;
a->rc = T1; if (T1) T1->parent = a;
updateHeight(a); //更新a的高度
c->lc = T2; if (T2) T2->parent = c;
c->rc = T3; if (T3) T3->parent = c;
updateHeight(c);
b->lc = a; a->parent = b;
b->rc = c; c->parent = b;
updateHeight(b);
return b;
}
template<typename T> binNode<T>* bst<T>::rotateAt(binNode<T>* v)
{
binNode<T>* p = v->parent; binNode<T>* g = p->parent; //首先根据v找到p和g节点
if (g->lc == p) //失衡是由节点删除导致的(p是g的左孩子)
{
if (p->lc == v) //进一步,v是p的左孩子
{
p->parent = g->parent;
return connect34(v, p, g, v->lc, v->rc, p->rc, g->rc);
}
else
{
v->parent = g->parent;
return connect34(p, v, g, p->lc, v->lc, v->rc, g->rc);
}
}
else //失衡是由节点插入造成的
{
if (p->rc == v)
{
p->parent = g->parent;
return connect34(g, p, v, g->lc, p->lc, v->lc, v->rc);
}
else
{
v->parent = g->parent;
return connect34(g, v, p, g->lc, v->lc, v->rc, p->rc);
}
}
}