我们在数据结构初期认识到了基础的二叉树,大概对二叉树有了初步的了解。但是二叉树对于我来说却是一个不晓得难点;下面分享一下在学习二叉树的变形二插搜索树的一些经验;
二叉搜索树
二叉搜索树又叫二叉排序树,它或者是一颗空树,或者是一颗具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有的节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有的节点的值都大于根节点的值
- 它的左右子树也分别是二叉搜索树
相关的操作
(1)二插搜索树的查找
它既然能够起名叫做二插搜索树那么必然在搜索这方面具有优势,下图给出一个二叉搜索树尝试查找一下进行分析一下;
从图片的分析中我们就可以很明了的看出来,这种用递归和非递归可以完成相对来说我觉得在二叉树的操作中递归反而更好写的而非递归想多有挑战性。
//声明一下,这里只是将最后封装的类中的函数拿出来体现一下思想,完整代码在结尾处展示
//1.非递归版本
Node* Find(const K& key)//搜索二叉树的查找
{
Node* cur = _root//根节点
while(cur)
{
if(cur->_key > key)//进入左子树
cur = cur->_left;
else if(cur->_key < key)//进入右子树
cur = cur->_right;
else
return cur;
}
return NULL;
}
//2.递归版本
Node* _FindR(Node* root,const K& key)
{
if(root == NULL)
return NULL;
if(root->_key = key)
return root;
else if(root->_key < key)
return _FindR(root->_right,key);
else if(root->_key > key)
return _FindR(root->_left,key)
}
(2)二插搜索树的插入
- 如果本来就是空树那就直接插入
- 如果不是空树按照其规则进行插入
相比较于空树的直接插入,而向非空的二叉树中插入式就要进行比较,这里我们可以考虑有两个指针一个指针Cur指向当前查找节点,另外一个指针Parent指向当前节点的前一个(为了插入新节点时能够同树连接起来)。
//声明一下,这里只是将最后封装的类中的函数拿出来体现一下思想,完整代码在结尾处展示
//1.非递归版本
bool Insert(const K& key)
{
if(_root == nullptr)//空树的情况
{
_root = new Node(key);
}
Node* perent = nullptr;
Node* cur = _root;
while(cur)
{
if(cur->_key > key)
{
parent = cur;
cur = cur->left;
}
else if(cur->_key < key)
{
parent = cur;
cur = cur->right;
}
else//注意有一个注意那就是在搜索二叉树中每一个节点数都是“惟一的”
{
return false;
}
}
if(parent->_key > key)
{
parent->_left = new Node(key);
}
else//这里已经排除等于的情况不大于那就小于
{
parent->_right = new Node(key);
}
return true;
}
//2.递归版本
bool _Insert(Node* root,const K& key)
{
if(root->_key == nullptr)//空树
root = new Node(key);
if(root->_key > key)
return _Insert(root->_left,key);
else if(root->_key < key)
return _Insert(root->_right,key);
else
return false;
}
(3)二插搜索树的删除
二叉树的删除可以分为四个情况:
- 要删除的结点没左右孩子
- 要删除的节点只有左孩子
- 要删除的节点只有右孩子
- 要删除的节点有左右孩子
从上面的图中我们呢可以看出来在搜索二叉树中,要删除一个节点总共就是有四种情况;但是在用代码实现时,我们还可以将上面的情况进行综合简化,总的来说就可以规划为下面的三种情况: - 情况一:删除该节点并且使该节点的双亲指向被删除节点的左孩子
- 情况二: 删除该节点并且使该节点的双亲指向被删除节点的右孩子
- 情况三:要删除的节点具有双亲,在它的左子树找到左子树最大节点其余交换,然后是删除交换后的节点(这样做是为了保证删除后不会破坏二叉树的结构)或者是找到该节点右子树中最小节点与其交换然后然后删除就OK了。
bool Earse(const K& key)
{
//1.要删除该节点就先要找到该节点
Node* parent = nullptr;
Node* cur = _root;
while(cur)
{
if(cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if(cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
if(cur->_left == nullptr)//情况二:要删除的节点只有右孩子
{
if(parent->_left == cur)//如果是父亲节点的左孩子
{
parent->_left = cur->_right;
}
else if(parent->_right == cur)//如果是父亲节点的右孩子
{
parent->_right = cur->_right;
}
}
else if(cur->_right == nullptr)//情况一:要删除的节点只有孩子
{
if(parent->_left == cur)//如果是父亲节点的左孩子
{
parent->_left = cur->_left;
}
else if(parent->_right == cur)//如果是父亲节点的右孩子
{
parent->_right = cur->_left;
}
}
else//情况三:要删除的节点有左右孩子
{
Node* del = cur;//要删除的节点
//方式一:找到该节点的左子树中最大,然后和该节点进行交换然后删除节点
//方式二:找到该节点的右子树中最小,然后和该节点进项交换然后删除节点
//这里只是实现:其中一种情况同理,请自行实现;
Node* lessParent = cur;
Node* lessRight = cutr->_right;
//去该节点的右子树要到最大节点,其实很简单就是先进行入右子树一直往左边找直到nullptr
//为止,那么此时lessparent节点就是右子树的最大节点.
while(lessRight->_left)
{
lessParent = lessRight;
lessRight = lessRight->_left;
}
//进行值得交换或者是覆盖
cur->_key = lessRight->_key;
del = lessRight;
if (lessParent->_left == lessRight)
{
lessParent->_left = lessRight->_right;
}
else
{
lessParent->_right = lessRight->_right;
}
}
delete del;
return true;
}
}
return false;
}
//2.递归版本
bool _EarseR(Node** root, const k& key)
{
Node* cur = *root;
Node* del = cur;
Node* replace = nullptr;
if (*root == nullptr)//是一个空树
return false;
if (cur->_key > key)
return _EarseR(&(cur->_left), key);
else if (cur->_key < key)
return _EarseR(&(cur->_right), key);
else
{
del = cur;
if (cur->_left == nullptr)//要删除的节点只有右子树
{
*root = cur->_right;
}
else if (cur->_right == nullptr)//要删除的节点只有左子树
{
*root = cur->_left;
}
else//要删除的节点具有左右子树
{
replace = cur->_right;
while (replace->_left)
{
replace = replace->_left;
}
del = replace;
cur->_key = replace->_key;
return _EarseR(&(cur->_right), replace->_key);
}
delete del;
del = NULL;
return true;
}
}
注意: 相对于二插搜索树的其他操作,二叉搜索树的删除就会具有一定的难度。一点就是要删除要考虑的情况比较多,还有就是在进行递归的转换时应当考虑同非递归的处理方法也有不同;在非递归中我们在删除的时候,我们都会有一个就是前面的有一个指针保存要删除节点的父节点,用来保证删除后二插搜索树的正确性,但是在递归的啥时候相当于把删除操作进行了细化,当递归到是要删除的节点时,此时在递归内面在一个全新的栈帧内面,此时要删除的节点就是这个树的根节点,而他的父亲节点却不知道。这里该怎么办?我想到了一个处理的方法,就是使用二级指针,此时就是根节点的地址了,比如说上图要删除节点8,那次是函数传入的就是节点7的右孩子的二级指针,直接就可以*root = cur->_right;了,
这样就比较方便了,剩下的删除逻辑就是同非递归一样的;
完整代码
//搜索二叉树的实现
//K模型
template<class k>
struct BSTreeNode//实现一个数节点的结构体
{
BSTreeNode(const k& data = K()) :_left(nullptr), _right(nullptr), _key(data)
{}
BSTreeNode<k>* _left;
BSTreeNode<k>* _right;
k _key;
};
template<class k>
class BSTree
{
typedef BSTreeNode<k> Node;
public:
BSTree() :_root(nullptr)
{}
~BSTree()
{
}
//搜索二叉树插入
bool Insert(const k& key)
{
if (_root == nullptr)//如果是空树
{
_root = new Node(key);
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (parent->_key > key)
{
parent->_left = new Node(key);
}
else if (parent->_key < key)
{
parent->_right = new Node(key);
}
return true;
}
//搜索二叉树中序遍历
void Inorder()
{
_Inorder(_root);
cout << endl;
}
//搜索二叉树的查找
Node* Find(const k& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key < key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return NULL;
}
//二插搜索树删除---要删除的节点要么是只有一个孩子要么就是没有孩子
bool Erase(const k& key)
{
Node* parent = NULL;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key>key)
{
parent = cur;
cur = cur->_right;
}
else
{
Node* del = cur;
//1.是叶子;或者只有一个叶子
if (cur->_left == NULL)
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else if (parent->_right == cur)
{
parent->_right = cur->_right;
}
}
else if (cur->_right == NULL)
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else if (parent->_right == cur)
{
parent->_right = cur->_left;
}
}
else//2.左右都不为空
{
Node* lessParent = cur;
Node* lessRight = cur->_right;
while (lessRight->_left)
{
lessParent = lessRight;
lessRight = lessRight->_left;
}
cur->_key = lessRight->_key;
del = lessRight;
if (lessParent->_left == lessRight)
{
lessParent->_left = lessRight->_right;
}
else
{
lessParent->_right = lessRight->_right;
}
}
delete del;
return true;
}
}
return false;
}
//递归版本
bool _InsertR(Node*& root, const k& key)//这里传入的是根节点的引用,因为进入递归后,再去传入的节点地址和上一次没有任何关系;相当于什么都没有插入进去
{
if (root == nullptr)
root = new Node(key);
if (root->_key > key)
return _InsertR(root->_left, key);
else if (root->_key < key)
return _InsertR(root->_right, key);
else
return false;
}
bool InsertR(const k& key)
{
return _InsertR(_root, key);
}
Node* _FindR(Node* root, const k& key)
{
if (root == NULL)
return NULL;
if (root->_key == key)//底肥终止条件
return root;
else if (root->_key > key)
return _FindR(root->_left, key);
else if(root->_key < key)
return _FindR(root->_right,key);
}
Node* FindR(const k& key)
{
return _FindR(_root, key);
}
bool _EarseR(Node** root, const k& key)
{
Node* cur = *root;
Node* del = cur;
Node* replace = nullptr;
if (*root == nullptr)//是一个空树
return false;
if (cur->_key > key)
return _EarseR(&(cur->_left), key);
else if (cur->_key < key)
return _EarseR(&(cur->_right), key);
else
{
del = cur;
if (cur->_left == nullptr)//要删除的节点只有右子树
{
*root = cur->_right;
}
else if (cur->_right == nullptr)//要删除的节点只有左子树
{
*root = cur->_left;
}
else//要删除的节点具有左右子树
{
replace = cur->_right;
while (replace->_left)
{
replace = replace->_left;
}
del = replace;
cur->_key = replace->_key;
return _EarseR(&(cur->_right), replace->_key);
}
delete del;
del = NULL;
return true;
}
}
bool EarseR(const k& key)
{
return _EarseR(&_root, key);
}
private:
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);
}
private:
Node* _root;
};
void Test()
{
int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
BSTree<int> t;
for (auto e : a)
{
t.Insert(e);
}
t.InsertR(11);
t.InsertR(12);
t.InsertR(13);
//std::cout << t.FindR(6)->_key << std::endl;
//t.Inorder();
//t.Erase(8);
t.EarseR(8);
t.Inorder();
}
//代码正确,测试请自行测试,
以上就是二插搜索树的K模型的递归和非递归实现,,当然还有一个K-V模型我们在后面实现。虽然我们实现理解不是最优,你是希望能给大家一点帮助,那样也就有价值了!!!