二叉树在前面的C数据结构已经学习过了,现在我们一起来认识一下二叉搜索树。
二叉搜索树
二叉搜索树的概念
二叉搜索树又称二叉排序树,它或是一颗空树,或者是具有一下性质的二叉树:
1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
2.若它的右子树不为空,则右子树上所有的节点的值都小于根节点的值。
3.它的左右子树也分别为二叉搜索树。
二叉搜索树又叫二叉排序树(BST,Binary Search Tree)。
二叉树的操作
1. 二叉搜索树的查找 :a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。 b、最多查找高度次,走到到空,还没找到,这个值不存在。
2. 二叉搜索树的插入 插入的具体过程如下: a. 树为空,则直接新增节点,赋值给root指针 b. 树不空,按二叉搜索树性质查找插入位置,插入新节点。
3. 二叉搜索树的删除 首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情 况: a. 要删除的结点无孩子结点 b. 要删除的结点只有左孩子结点 c. 要删除的结点只有右孩子结点 d. 要删除的结点有左、右孩子结点 看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程 如下: 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点 中,再来处理该结点的删除问题--替换法删除。
template<class T>
struct BSTnode
{
BSTnode(const T& x)
:_left(nullptr)
, _right(nullptr)
, _data(x)
{}
BSTnode<T>* _left;
BSTnode<T>* _right;
T _data;
};
首先我们,需要定一个节点,它里面包括了储存的值,它的左右孩子。
插入(insert)
我们现在来学习插入,首先上面我们已经知道了,当我们插入的时候,把比节点大的值查到右边,小的插到左边。逻辑很简单,走一个循环就行了,我们来看一下代码。
bool Insert(const T& x)
{
//定义一个节点
node* newnode = new node(x);
//如果为空,直接把节点给根
if (_root == nullptr)
{
_root = newnode;
return true;
}
node* cur = _root;
node* parent = nullptr;
while (cur)
{
parent = cur;
//小的放左边
if (x < cur->_data)
cur = cur->_left;
//大的放右边
else if (x > cur->_data)
cur = cur->_right;
else //树中已经存在
return false;
}
//小于放在parent的左边
if (x < parent->_data)
parent->_left = newnode;
//大于放在parent的右边
else if (x > parent->_data)
parent->_right = newnode;
return true;
}
在这里,我们需要定义一个parent指针,用来最后只想我们插入的cur节点,不断使用cur去更新parent的值,使得parent永远是cur的双亲节点。
查找(search)
查找就相对来说比较简单,我们只需要定义一个节点,去遍历整个树结构就行,从根节点开始遍历。如果相等就返回,如果小于就走左边,大于就走右边。我们直接看代码,还是非常简单的。
node* Find(const T& x)
{
if (_root == nullptr)
{
return nullptr;
}
node* cur = _root;
//遍历整个树,如果cur走到了空
//就说明没有该节点,返回空
while (cur)
{
if (x < cur->_data)
cur = cur->_left;
else if (x > cur->_data)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
删除(erase)
删除是相对来说最难的,它需要考虑的情况比较多。
首先,删除分为以下几种情况。
1. 删除的节点是叶子节点(没有左右孩子)。
2.删除的节点只有一个孩子(左或者右)。
3.删除的节点右两个孩子。
对于1,2来说,可以看作同一种情况。如果是左孩子为空,则让父亲指向它的右孩子;如果右孩子为空,则让父亲指向它的左孩子。
对于3来说相对比较复杂,因为它右两个孩子,我们无法判断去同时指向两个孩子。这时我们就需要使用替代法去解决它。
替代法:替代法是用于删除节点拥有左右孩子的节点。首先解决方法就是,我们去寻找删除节点的右子树的最左节点或者左子树的最又节点,把替代节点的值赋给需要删除的节点,最后删除替代节点就行了。为什么要这样做,因为这样做了之后,整个树还是满足一个搜索二叉树的原则。
下面我们来看一看代码。
bool Erase(const T& x)
{
//如果树为空,直接返回false
if (_root == nullptr)
return false;
//这里的逻辑和find一样
//我们需要找到删除节点,才能进进行删除
node* cur = _root;
node* parent = cur;
while (cur)
{
if (cur->_data == x)
break;
else if (x < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else
{
parent = cur;
cur = cur->_right;
}
}
//没找到该节点
if (cur == nullptr)
return false;
//如果cur的左为空,则让parent指向cur的右
if (cur->_left == nullptr)
{
//如果cur是树节点,我们就直接更新树节点就行
if (cur == _root)
_root = cur->_right;
else
{
//这里还需要判断cur是父亲的左还是右
//如果cur是左,那么parent就更新parent的左
if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
return true;
}
//逻辑和上面的左为空的逻辑相同
if (cur->_right == nullptr)
{
if (cur == _root)
_root = cur->_left;
else
{
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
return true;
}
//左右都不为空
//我们需要去寻找替代节点
//定义一个replace和replaceparent去
//操作替代节点
node* replace = cur->_right;
node* replaceparent = cur;
//找到replace节点
while (replace->_left)
{
replaceparent = replace;
replace = replace->_left;
}
//把替代节点的值给cur
cur->_data = replace->_data;
//修改replaceparent的指向,删除replace
if (replace == replaceparent->_left)
replaceparent->_left = replace->_right;
else
replaceparent->_right = replace->_right;
delete replace;
return true;
}
中序遍历(inorder)
当我们构建了这样一个二叉搜索树之后,当我们对它进行中序遍历的时候,它就会是一个有序的数列。中序遍历我们在前面的二叉树已经讲过了,就简单看下代码就行了。
void _Inorder(node* _root)
{
if (_root == nullptr)
return;
_Inorder(_root->_left);
std::cout << _root->_data << " ";
_Inorder(_root->_right);
}
二叉搜索树的性能
当我们查找一个元素的时候,通过观察,我们只需要查找高度次就可以了。最终需要的时间复杂度是O(n)。这是相对理想的情况,如果是一些极端的情况的话,它将会退化称O(N) 。
对于左边这个树来说,它的左右子树是是相对平衡的,那么它的查找效率就相对来说比较高,如果是右边这种树,其实我们都可以直接把他看作一条直线,它的时间复杂度就是O(N) 。 如果我们是去查找1 的话,就一直向左走,走到最左边。所以,我们后面还需要学习AVL树去平衡一些特殊情况的树。
总代码实现
namespace key
{
template<class T>
struct BSTnode
{
BSTnode(const T& x)
:_left(nullptr)
, _right(nullptr)
, _data(x)
{}
BSTnode<T>* _left;
BSTnode<T>* _right;
T _data;
};
template<class T>
class BStree
{
typedef BSTnode<T> node;
public:
BStree()
:_root(nullptr)
{}
BStree(const BStree<T>& cur)
{
queue<node*> tmp;
node* front = nullptr;
if (cur._root == nullptr)
;
else
tmp.push(cur._root);
while (!tmp.empty())
{
front = tmp.front();
Insert(front->_data);
if (front->_left)
tmp.push(front->_left);
if (front->_right)
tmp.push(front->_right);
tmp.pop();
}
}
void destructor(node* &tmp)
{
if (tmp == nullptr)
return;
destructor(tmp->_left);
destructor(tmp->_right);
delete tmp;
}
~BStree()
{
destructor(_root);
_root = nullptr;
}
BStree& operator=( BStree tmp)
{
swap(_root, tmp._root);
return *this;
}
bool Insert(const T& x)
{
//定义一个节点
node* newnode = new node(x);
//如果为空,直接把节点给根
if (_root == nullptr)
{
_root = newnode;
return true;
}
node* cur = _root;
node* parent = nullptr;
while (cur)
{
parent = cur;
//小的放左边
if (x < cur->_data)
cur = cur->_left;
//大的放右边
else if (x > cur->_data)
cur = cur->_right;
else //树中已经存在
return false;
}
//小于放在parent的左边
if (x < parent->_data)
parent->_left = newnode;
//大于放在parent的右边
else if (x > parent->_data)
parent->_right = newnode;
return true;
}
node* Find(const T& x)
{
if (_root == nullptr)
{
return nullptr;
}
node* cur = _root;
//遍历整个树,如果cur走到了空
//就说明没有该节点,返回空
while (cur)
{
if (x < cur->_data)
cur = cur->_left;
else if (x > cur->_data)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
bool Erase(const T& x)
{
//如果树为空,直接返回false
if (_root == nullptr)
return false;
//这里的逻辑和find一样
//我们需要找到删除节点,才能进进行删除
node* cur = _root;
node* parent = cur;
while (cur)
{
if (cur->_data == x)
break;
else if (x < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else
{
parent = cur;
cur = cur->_right;
}
}
//没找到该节点
if (cur == nullptr)
return false;
//如果cur的左为空,则让parent指向cur的右
if (cur->_left == nullptr)
{
//如果cur是树节点,我们就直接更新树节点就行
if (cur == _root)
_root = cur->_right;
else
{
//这里还需要判断cur是父亲的左还是右
//如果cur是左,那么parent就更新parent的左
if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
return true;
}
//逻辑和上面的左为空的逻辑相同
if (cur->_right == nullptr)
{
if (cur == _root)
_root = cur->_left;
else
{
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
return true;
}
//左右都不为空
//我们需要去寻找替代节点
//定义一个replace和replaceparent去
//操作替代节点
node* replace = cur->_right;
node* replaceparent = cur;
//找到replace节点
while (replace->_left)
{
replaceparent = replace;
replace = replace->_left;
}
//把替代节点的值给cur
cur->_data = replace->_data;
//修改replaceparent的指向,删除replace
if (replace == replaceparent->_left)
replaceparent->_left = replace->_right;
else
replaceparent->_right = replace->_right;
delete replace;
return true;
}
void Inorder()
{
_Inorder(_root);
}
private:
void _Inorder(node* _root)
{
if (_root == nullptr)
return;
_Inorder(_root->_left);
std::cout << _root->_data << " ";
_Inorder(_root->_right);
}
node* _root = nullptr;
};
}