今天在看完了《算法导论》的二叉排序树和极客时间王争老师讲的二叉排序树有了不少体会,下面进行总结一下:
自己在阅读完书籍后,立马自己手写了一遍二叉排序树:
#pragma once
struct TreeNode
{
TreeNode(int val) :value(val),left_child(nullptr), right_child(nullptr),parent(nullptr){}
int value;
TreeNode* left_child, *right_child,*parent;
};
class BinaryTree {
public:
BinaryTree() :Root(nullptr) {}
TreeNode* search(int val);
void insert(TreeNode* node);
void remove(TreeNode* des);
void remove(int val);
TreeNode* Successor(TreeNode* x);
TreeNode* Predesessor(TreeNode* x);
void Inorder();
void PreOrder();
void AftOrder();
private:
TreeNode* ret_minimum(TreeNode* x);
TreeNode* ret_maxnimum(TreeNode* x);
void Transplant(TreeNode* u, TreeNode* v);
private:
TreeNode* Root;
};
#include "BinaryTree.h"
#include <stack>
#include <iostream>
using std::stack;
using std::cout;
using std::swap;
TreeNode* BinaryTree::search(int val)
{
if (!Root)
return nullptr;
TreeNode* cur = Root;
while (cur)
{
if (val > cur->value)
cur = cur->right_child;
else if (val < cur->value)
cur = cur->left_child;
else
return cur;
}
return nullptr;
}
void BinaryTree::insert(TreeNode* node)
{
if (!node||!Root)
{
if (!Root)
{
Root = node;
}
return;
}
TreeNode* cur = Root;
TreeNode* pre=nullptr;
while (cur)
{
pre = cur;
if (node->value >= cur->value)
cur = cur->right_child;
else
cur = cur->left_child;
}
if (node->value >= pre->value)
pre->right_child = node;
else
pre->left_child = node;
node->parent = pre;
}
void BinaryTree::remove(TreeNode * des)
{
if (!des)
return;
if (des->left_child == nullptr)
{
Transplant(des, des->right_child);
}
else if (des->right_child == nullptr)
{
Transplant(des, des->left_child);
}
else
{
TreeNode* next_Node = Successor(des);
swap(next_Node->value, des->value);
Transplant(next_Node, next_Node->right_child);
}
}
void BinaryTree::remove(int val)
{
TreeNode* node = search(val);
return remove(node);
}
TreeNode * BinaryTree::Successor(TreeNode * x)
{
if (!x)
return nullptr;
TreeNode* par = x->parent;
if (x->right_child)
{
return ret_minimum(x->right_child);
}
else
{
while (par&&x == par->right_child)
{
x = par;
par = par->parent;
}
}
return par;
}
TreeNode * BinaryTree::Predesessor(TreeNode * x)
{
if (!x)
return nullptr;
if (x->left_child)
{
return ret_maxnimum(x->right_child);
}
else
{
TreeNode* par= x->parent;
while (par&&x == par->left_child)
{
x = par;
par = par->parent;
}
return par;
}
}
void BinaryTree::Inorder()
{
stack<TreeNode*> sta;
TreeNode* cur = Root;
while (1)
{
while (cur)
{
if (cur)
sta.push(cur);
cur = cur->left_child;
}
if (sta.empty())
break;
cur = sta.top();
cout << cur->value<<" ";
sta.pop();
cur = cur->right_child;
}
}
void BinaryTree::PreOrder()
{
stack<TreeNode*> sta;
TreeNode* cur = Root;
while (1)
{
while (cur) {
cout << cur->value << " ";
if (cur->right_child)
sta.push(cur->right_child);
cur = cur->left_child;
}
if (sta.empty())
break;
cur = sta.top();
sta.pop();
}
}
void BinaryTree::AftOrder()
{
stack<TreeNode*> sta_1,sta_2;
TreeNode* cur = Root;
sta_1.push(Root);
while (!sta_1.empty())
{
cur = sta_1.top();
sta_1.pop();
sta_2.push(cur);
if (cur->left_child)
sta_1.push(cur->left_child);
if (cur->right_child)
sta_1.push(cur->right_child);
}
while (!sta_2.empty())
{
TreeNode* temp = sta_2.top();
cout <<temp->value <<" ";
sta_2.pop();
}
}
TreeNode* BinaryTree::ret_minimum(TreeNode* x)
{
if (!x)
return nullptr;
TreeNode* cur = x;
while (cur->left_child)
{
cur = cur->left_child;
}
return cur;
}
TreeNode* BinaryTree::ret_maxnimum(TreeNode* x)
{
if (!x)
return nullptr;
TreeNode* cur = x;
while (cur->right_child)
{
cur = cur->right_child;
}
return cur;
}
void BinaryTree::Transplant(TreeNode * u, TreeNode * v)
{
if (u->parent == nullptr)
Root = v;
else if (u == u->parent->left_child)
u->parent->left_child = v;
else
u->parent->right_child = v;
if (v)
v->parent = u->parent;
}
实现过程中,最大的体会是在于删除这步操作上,删除要考虑三种情况,只有左子树、只有右子树、左右子树都有,前两种情况比较好操作,我们定义了一个Transplant操作用来替代子树。所以当只有一个子树时,我们只要把当前待删节点的子树直接接上去就可以了。麻烦的是左右子树都有的情况,一般我们会选择待删节点的前驱或后继来代替当前节点,这样就依然能维持二叉排序树的性质。这里我选择了后继来替代待删节点。所以删除的过程就成了,先找到后继结点,又由于后继节点肯定是没有左孩子的所以我们可以先将后继结点的值与待删除节点的值进行交换,然后删除此时这个后继节点的位置。
void BinaryTree::remove(TreeNode * des)
{
if (!des)
return;
if (des->left_child == nullptr)
{
Transplant(des, des->right_child);
}
else if (des->right_child == nullptr)
{
Transplant(des, des->left_child);
}
else
{
TreeNode* next_Node = Successor(des);
swap(next_Node->value, des->value);
Transplant(next_Node, next_Node->right_child);
}
}
swap操作可能需要3次拷贝操作,所以如果拷贝操作开销比较大的话,我们可以选择另一种原理一样但删除稍显不同的操作。这种操作只要改变几次指针的指向就行了。
这种操作就又要分两种情况1.后继节点此时就是待删除节点的右孩子 2.后继结点在待删除节点右孩子的左子树中。如果是情况二的话,我们需要多做几部操作。
1.首先将后继结点从树中脱离出来(使用后继节点的右子树代替它的位置)
2.然后将这个后继结点的右孩子指向待删除结点的右孩子
3.待删除节点右孩子的父母指向该后继结点
4.然后将这个后继节点代替待删除节点的位置
5.然后这个后继节点的左孩子指向待删除结点的左孩子
6.最后待删除节点左孩子的父母指向这个后继节点
void BinaryTree::remove(TreeNode * des)
{
if (!des)
return;
if (des->left_child == nullptr)
{
Transplant(des, des->right_child);
}
else if (des->right_child == nullptr)
{
Transplant(des, des->left_child);
}
else
{
TreeNode* y = Successor(des);
if (y->parent != des)
{
Transplant(y, y->right_child);
y->right_child = des->right_child;
y->right_child->parent = y;
}
Transplant(des, y);
y->left_child = des->left_child;
y->left_child->parent = y;
}
}
支持重复数据的二叉查找树
前面讲二叉查找树的时候,我们默认树中节点存储的都是数字。很多时候,在实际的软件开发中,我们在二叉查找树中存储的,是一个包含很多字段的对象。我们利用对象的某个字段作为键值(key)来构建二叉查找树。我们把对象中的其他字段叫作卫星数据。
前面我们讲的二叉查找树的操作,针对的都是不存在键值相同的情况。那如果存储的两个对象键值相同,这种情况该怎么处理呢?我这里有两种解决方法。
第一种方法比较容易。二叉查找树中每一个节点不仅会存储一个数据,因此我们通过链表和支持动态扩容的数组等数据结构,把值相同的数据都存储在同一个节点上。
第二种方法比较不好理解,不过更加优雅。
每个节点仍然只存储一个数据。在查找插入位置的过程中,如果碰到一个节点的值,与要插入数据的值相同,我们就将这个要插入的数据放到这个节点的右子树,也就是说,把这个新插入的数据当作大于这个节点的值来处理。
当要查找数据的时候,遇到值相同的节点,我们并不停止查找操作,而是继续在右子树中查找,直到遇到叶子节点,才停止。这样就可以把键值等于要查找值的所有节点都找出来。
对于删除操作,我们也需要先查找到每个要删除的节点,然后再按前面讲的删除操作的方法,依次删除。
由于二叉排序树的查找效率与树的高度有关,所以如果树不平衡的话,最终的查找效率还是不够理想,最差的时候就是左斜树或则右斜树,这样二叉排序树的查找效率就与链表相差不多了。所以这就引入了平衡二叉树,来防止效率的降低。