目录
一,搜索二叉树是个啥?
话不多所,先给各位来一个搜索二叉树:
从这棵树中可以看到这棵树有如下性质:
1.根节点的左节点的值小于根节点的值,根节点的右节点的值大于根节点的值。
2.这棵树的中序遍历的结果是一个升序的数组。
3.这棵树的左子树和右子树都是一颗搜索二叉树。
以上三点便是一棵搜索二叉树的性质!!!
二,搜索二叉树的实现
1.前期工作
要实现一棵搜索二叉树,首先便要实现它的各个节点。实现如下:
template<class K> struct BSNode { BSNode(const K& key) :_left(nullptr) ,_right(nullptr) ,_key(key) {} BSNode<K>* _left; BSNode<K>* _right; K _key; };
这个节点的成员就是它的左指针_left,右指针_right,还有这个节点里包含的一个值_key。
接下来便要开始实现一下这棵树。实现如下:
template<class K> class BSTree { public: private: BSNode<K>* _root; }
这棵树的成员便只有一个,那便是_root这个根节点。
2.方法实现
1.插入
在前期工作准备好以后便要来实现我们的方法了,现在来实现我们的插入方法。实现思路如下:
1.因为我们的_root是是私有的,所以我们不能实现一个需要传参的Insert方法。所以它必须是无参的。
2.要实现一个无参的方法,那我们就得套一层_Insert()方法在Insert()方法里面。
3.实现_Insert()方法步骤如下:
1.如果_root是nullptr便new一个节点,让_root接收这个新节点。
2.如果key比我当前的节点值要大,便向右走。
3.如果比我当前的节点值要小,那就向左走。
4.如果走到空(_root的替代值未为nullptr)那就在该位置生成一个新节点,并让该节点的parent的左或者右指针指向这个新节点。
代码如下:
实现无参:
bool Insert( const K& key) { return _Insert(key); }
_Insert()方法实现:
bool _Insert(const K& key) { if (_root == nullptr)//若_root是一个nullptr那就给_root new 一个节点 { _root = new BNode<K>(key); return true; } else { BNode<K>* cur = _root; BNode<K>* parent = nullptr; while (cur!=nullptr)//找位置 { parent = cur; if (cur->_key < key) { cur = cur->_right; } else if (cur->_key > key) { cur = cur->_left; } else { return false; } } cur = new BNode<K>(key);//找到后便给这个位置new一个节点 if (key > parent->_key)//判断一下是左边还是右边然后链接 { parent->_right = cur; } else { parent->_left = cur; } return true; } }
2,查找
查找方法的实现也很简单,其实就是将_Insert()里面的查找代码给复制一份过来便可以了。同样的,我们的查找算法在类的外边也是不能调用_root的,所以也会有封装。Find()函数实现如下:
bool Find(const K& key) { return _Find(key); }
我们的_Find()函数实现如下:
bool _Find(const K& key) { BNode<K>* cur = _root; while (cur) { if (key > cur->_key) { cur = cur->_right; } else if (key < cur->_key) { cur = cur->_left; } else { return true; } } return false; }
3.删除
删除方法的实现大概是最难写的一个代码了,首先我们得先找到这个要删除的节点找到以后分为三种情况讨论:
以如下搜索二叉树为例:
1.要删除节点的左节点为空。如以下情况:
比如要删除10这个节点,我们该如何操作呢?
我们的操作如下:
1.找到我的父亲。
2.判断我是父亲的那个节点。
3.如果我是父亲的左节点便让父亲的左节点连接到我的右节点上。如果我是父亲右节点,便让父亲的右节点指向我的右节点。
但是这里需要注意一个点:如果我是root,我便没有父亲。如以下情况:
在这种情况下我们便可以直接让右节点担任root节点:
if (cur == _root) { _root = _root->_right; }
左节点为空的情况下实现删除代码如下:
if (cur->_left == nullptr) { if (cur == _root) { _root = _root->_right; } else { if (cur->_key > parent->_key) { parent->_right = cur->_right; } else { parent->_left = cur->_right; } } return true; }
2.要删除的节点的右节点为空。
其实这种情况下的的代码的值和前面的实现逻辑是一样的,所以不解释直接给出实现代码:
else if (cur->_right == nullptr) { if (cur == _root) { _root = cur->_left; } else { if (cur->_key > parent->_key) { parent->_right = cur->_left; } else { parent->_left = cur->_left; } } return true; }
3.当我要删除的节点的左右两个节点都在
这个删除便是我们这个删除方法里面最难实现的一个代码,在这里我们要使用替换法来删除:
1.如何使用替换法呢?
步骤:1.定义parent和subLeft,subLeft定义为cur->right。
记录我的父亲,这个父亲要初始化cur(当前节点)。
2.找到当前节点的右子树的最左节点subLeft并更新parent位subLeft的父亲节点。
3.交换cur节点和subLeft两个节点的值(使用swap).
4.链接。
了解完以上步骤以后写下如下代码:
else {//有两个孩子,替换法。(找右子树的最左节点) BNode<K>* Parent = cur; BNode<K>* SubLeft = cur->_right; while (SubLeft->_left) { Parent = SubLeft; SubLeft = SubLeft->_left; } swap(cur->_key, SubLeft->_key); if (SubLeft == Parent->_left) { Parent->_left = SubLeft->_left; } else { Parent->_right = SubLeft->_left; } return true; }
在这里解释一下:
1.为什么parent要初始为cur,如以下例子:
假如是以上的情况,那我的这段代码是不会进去的:
while (SubLeft->_left) { Parent = SubLeft; SubLeft = SubLeft->_left; }
那如果我的parent 如果赋值为nullptr的话,那便会解引用nullptr:
if (SubLeft == Parent->_left) { Parent->_left = SubLeft->_left; } else { Parent->_right = SubLeft->_left; }
所以我们必须要将parent初始化为cur。这个时候也能删除。
2.链接该如何连接?
我实现的连接代码是这样的:
if (SubLeft == Parent->_left) { Parent->_left = SubLeft->_left; } else { Parent->_right = SubLeft->_left; }
在这里我们首先得先判断一下我们的subLeft是我的parent节点的哪一位?
可能是右节点:
就像我们上面的删除8的情况一样,我要删除的是根节点,我的根节点的左节点是nullptr。
也可能是左节点:
我进入了循环:
while (SubLeft->_left) { Parent = SubLeft; SubLeft = SubLeft->_left; }
三,实现二叉搜索树的全部代码
#include<iostream>
using namespace std;
#include<assert.h>
template<class K>
struct BNode
{
BNode(const K& key)
:_key(key)
,_left(nullptr)
,_right(nullptr)
{}
K _key;
BNode<K>* _left;
BNode<K>* _right;
};
template<class K>
class BSTree
{
public:
bool Insert( const K& key)
{
return _Insert(key);
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
bool Find(const K& key)
{
return _Find(key);
}
bool Erase(const K& key)
{
return _Erase(key);
}
private:
bool _Insert(const K& key)
{
if (_root == nullptr)
{
_root = new BNode<K>(key);
return true;
}
else
{
BNode<K>* cur = _root;
BNode<K>* parent = nullptr;
while (cur!=nullptr)
{
parent = cur;
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return false;
}
}
cur = new BNode<K>(key);
if (key > parent->_key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
}
bool _Erase(const K& key)
{
assert(_root);
BNode<K>* cur = _root;
BNode<K>* parent = cur;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = _root->_right;
}
else
{
if (cur->_key > parent->_key)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
return true;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (cur->_key > parent->_key)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
return true;
}
else
{//有两个孩子,替换法。(找右子树的最左节点)
BNode<K>* Parent = cur;
BNode<K>* SubLeft = cur->_right;
while (SubLeft->_left)
{
Parent = SubLeft;
SubLeft = SubLeft->_left;
}
swap(cur->_key, SubLeft->_key);
if (SubLeft == Parent->_left)
{
Parent->_left = SubLeft->_left;
}
else
{
Parent->_right = SubLeft->_left;
}
return true;
}
}
}
return false;
}
bool _Find(const K& key)
{
BNode<K>* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if (key < cur->_key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
void _Inorder(BNode<K>* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);
}
BNode<K>*_root =nullptr ;
};
实际上还可以实现一个递归版本的二叉搜索树,有时间再写。