目录
1.二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
(1)若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
(2)若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。
(3)它的左右子树也分别为二叉搜索树。
(4)二叉搜索树可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义。如下图,左边是不支持插入相等的值,右边支持插入相等的值。
2.二叉搜索树的性能分析
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为:。最差情况下,二叉树退化为单支树(或者类似单支),其高度为:
。所有综合而言二叉搜索树增删查的时间复杂度为:
。
另外需要说明的是,二分查找也可以实现级别的查找效率,但是二分查找有两个大的缺陷:
(1)需要存储在支持下标随机访问的结构中,并且有序。
(2)插入和删除数据效率很低,因为存储在下标随机访问的结构(例如数组)中,插入和删除数据一般需要挪动数据。
所以二叉搜索树就能体现出价值所在,即通过二叉树中序遍历就能有序的访问数据,并且插入和删除都是节点的操作,不需要挪动数据。二叉搜索树的查找效率为,但是这个缺陷在后续的平衡二叉树AVL树已经红黑树中可以解决,将效率提高到
。
3.二叉搜索树的结构和中序遍历
3.1二叉搜索树中节点的结构
_key为节点中的值,_left和_right分别是指向左节点和右节点的指针。
template<class K>
struct BSTNode
{
K _key;
BSTNode<K>* _left;
BSTNode<K>* _right;
BSTNode(const K& key)
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
3.2二叉搜索树的结构
二叉树的结构中只用一个_root成员。
template<class K>
class BSTree
{
//typedef BSTNode<K> Node;
using Node = BSTNode<K>;
private:
Node* _root = nullptr;
};
3.3中序遍历
因为二叉搜索树左边比根节点小,右边比根节点大,中序遍历的顺序是:左子树,根节点,右子树,所以通过中序遍历遍历二叉搜索树,天然就是有序的。
中序遍历需要传一个根节点,在类外面不能直接访问私有成员,所以这里的中序遍历实现在private里面,然后外面再用InOrder()函数进行封装,这里在类外面直接调用InOrder()函数就可以实习中序遍历了,不需要传入根节点。
template<class K>
class BSTree
{
//typedef BSTNode<K> Node;
using Node = BSTNode<K>;
public:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
Node* _root = nullptr;
};
4.二叉搜索树的插入
插入的具体过程如下:
(1)树为空,则直接新增节点,赋值给root。
(2)树不为空,按二叉搜索树的性质,插入值比当前节点大往右走,插入值比当前节点小往左走,找到空位置插入新节点。
(3)如果支持插入相等的值,插入值跟当前节点相等的值可以往右走,也可以往左走,找到空位置插入新节点。(要注意保持逻辑的一致性,插入相等的值不要一会往右走,一会往左走)
这里以插入下列这棵树为例子:
1.不允许相同的值插入
//不允许相同的值插入
bool Insert(const K& key) //K是插入的值的类型
{
if (_root == nullptr) //当为空树时,直接赋值给root
{
_root = new Node(key);
return true;
}
Node* parent = nullptr; //parent指向cur的父亲节点
Node* cur = _root;
while (cur)
{
if (cur->_key < key) //当插入值key大于cur节点的值,往右走
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key) //当插入值key小于cur节点的值,往左走
{
parent = cur;
cur = cur->_left;
}
else //插入相等的值时,插入失败
{
return false;
}
}
//此时找到空位置了,cur == nullptr, parent指向cur的父亲节点,进行插入
cur = new Node(key);
if (parent->_key < key) //如果插入值大于parent节点的值,插入在parent的右边
{
parent->_right = cur;
}
else //如果插入值