七、【数据结构】二叉搜索树(bst)的解析与实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_18108083/article/details/84927274

本文使用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

bst接口列表
操作 功能 对象
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);
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq_18108083/article/details/84927274
今日推荐