最近读《数据结构与算法分析》的二叉树这一部分,实在是烧脑筋。感觉比双向链表还要抽象。勉强算弄懂了4.3中的查找,插入,和删除操作。这里作者的代码运用了大量的递归,当然,也有很多的技巧,学到了不少东西。
首先先要明确这样一个事实,即:对于树中的每个节点x,它的左子树的所有项的值都小于x中的项,而右子树的所有项的值都大于x中的项。
![9d2ac2202592c411a39ac6c9e6c9d3a2.png](https://i-blog.csdnimg.cn/blog_migrate/a6682d9093317cfc512e14e3e638e44c.png)
好,现在给出了一个int类型的二叉查找树。当然,用C++的模板的话也是很简单的。这个接口代码如下:
#include<iostream>
using namespace std;
class BinarySearchTrees {
public:
BinarySearchTrees();
BinarySearchTrees(const BinarySearchTrees &rhs);
BinarySearchTrees(BinarySearchTrees&& rhs);
~BinarySearchTrees();
const int& findMin()const {};
const int& findMax()const;
bool contains(const int& x)const;
bool isEmpty()const;
void printTree(ostream& out = cout)const;
void makeEmpty();
void insert(const int& x);
void insert(int&& x);
void remove(const int& x);
BinarySearchTrees& operator=(const BinarySearchTrees& rhs);
BinarySearchTrees& operator=(BinarySearchTrees&& rhs);
private:
struct BinaryNode {
int element;
BinaryNode* left;
BinaryNode* right;
BinaryNode(const int& theElement, BinaryNode* lt, BinaryNode* rt) :
left{ lt }, right{ rt }, element{theElement}{};
BinaryNode(int&& theElement, BinaryNode* lt, BinaryNode* rt) :
element{ std::move(theElement) }, left{ lt }, right{ rt }{};
};
BinaryNode* root;
void insert(const int& x, BinaryNode*& t);
void insert(int&& x, BinaryNode*& t);
void remove(const int&x, BinaryNode*& t);
BinaryNode* FindMax(BinaryNode* t)const;
BinaryNode* FindMin(BinaryNode* t)const;
bool contains(const int& x, BinaryNode* t)const;
void makeEmpty(BinaryNode*& t);
void printTree(BinaryNode* t, ostream& out)const;
BinaryNode* clone(BinaryNode* t)const;
};
其中public部分中的某些函数是为了方便调用private部分的函数,主要目的就是封装。
而第一个操作是查找某个数是否在这个二叉查找树里面。
根据这个二叉查找树的性质,它的左子树的所有项的值都小于x中的项;右子树的所有的项的值都大于x中的项。那么,仅仅需要分别运用递归的手法,按照这个性质即可查找。
bool BinarySearchTrees::contains(const int& x, BinaryNode* t)const {
if (t == nullptr)
return false; //如果一直查找到尽头还是没有,那么返回flase;
else if (x < t->element)
return contains(x, t->left);
//如果查找的项x<当前节点的值,那么向这个节点的左子树去查找;
else if (x > t->element)
return contains(x, t->right);
//如果查找的项x>当前节点的值,那么向这个节点的左子树去查找;
else
return true;//除了这三种情况以外,自然是找到了;
}
接下来的操作是寻找树的最小值和树的最大值。这个操作也用这个性质来实现。
查找最小就一路从左子树出发,当当前节点没有左节点的时候,那显然它是最小的。
BinarySearchTrees::BinaryNode* BinarySearchTrees::FindMin(BinaryNode* t)const {
if (t == nullptr)
return nullptr;
if (t->left == nullptr)
return t;
return FindMin(t->left);
}
右子树用来查找最大
BinarySearchTrees::BinaryNode* BinarySearchTrees::FindMax(BinaryNode* t)const {
if (t == nullptr)
return nullptr;
if (t->right == nullptr)
return t;
return FindMax(t->right);
}
同时,注意到这里的递归是用尾递归的形式来实现的。尾递归可以用while来转化。如下代码所示。
FindMin(BinaryNode*t)const{
if(t!=nullptr)
while(t->left!=nullptr)
t=t->left;
return t;
}
接下来是插入操作。这个插入操作思路也很简单。如果找到x,那么什么也不做;否则,将x插入到遍历路径的最后一点上。而这个操作的实现,又要依靠之前遍历的方法了。除此之外,指针本身也要产生改变。因此,在这里用到了一个技巧,int *&t,这代表这个int指针既可以改变指针指向,也可以改变自己本身。
代码如下:
void BinarySearchTrees::insert(const int& x, BinaryNode*& t) {
if (t == nullptr)
t = new BinaryNode{ x,nullptr,nullptr };
else if (t->element > x)
insert(x, t->right);
else if (t->element < x)
insert(x, t->left);
else
;
}
void BinarySearchTrees::insert(int&& x, BinaryNode*& t) {
if (t == nullptr)
t = new BinaryNode{ std::move(x),nullptr,nullptr };
else if (t->element > x)
insert(std::move(x), t->right);
else if (t->element < x)
insert(std::move(x), t->right);
else
;
}
在递归例程中,只有当一个新树叶生成的时候,t才改变。当这种情况发生的时候,就说明递归例程被其它节点调用了。该节点p是新树叶的父亲。它的调用者将是insert(x,p->left)或者是insert(x,p->right)。换句话说,当递归调用到最后,达到条件时,那一瞬间,t产生了变化,而原先的调用函数的本来是t的子节点的节点变成了t的父亲。当然,这是我个人的想法。也许情况并不是这样。
接下来,是比较麻烦的删除。删除操作相比较起来其实是最难的。如果节点仅仅只是一片树叶,那么直接删除就好了。如果节点有一个儿子,那么删去节点,改变指向就好了。真正麻烦的是一个节点有两个子节点。这时候又要运用性质了。只需要用右子树的最小的数据代替这片要被删除的节点就好了。如图。
![325f04808b42184087369646c6a200dc.png](https://i-blog.csdnimg.cn/blog_migrate/ac71c2e1a71c79fc3aa74e409b7b3acc.png)
void BinarySearchTrees::remove(const int& x, BinaryNode*& t) {
if (t == nullptr)
return;
else if (t->element < x)
remove(x, t->left);
else if (t->element > x)
remove(x, t->right);
//这一步是遍历的步骤。去寻找要去删除的元素
//如果找到了这个元素,但是却是有两个子节点的树,那么就要分两步处理了
第一步:找到右子树的最小值来代替。
第二部:删除找到的那个右子树的最小值
else if (t->left != nullptr && t->right != nullptr) {
t->element = FindMin(t->right)->element;//第一步的实现
remove(t->element, t->right);//第二步的删除
}
else {//接下来就是针对单子节点和无子节点的删除操作了。
BinaryNode* old = t;
t = (t->left != nullptr) ? t->left : t->right;
delete old;
}
}
最后,就是整个类的析构了。这个也不是太难。还是递归调用。
BinarySearchTrees::~BinarySearchTrees() {
makeEmpty();
}
void BinarySearchTrees::makeEmpty(BinaryNode*&t) {
if (t != nullptr) {
makeEmpty(t->left);
makeEmpty(t->right);
delete t;
}
t = nullptr;
}