二叉查找树
- 先附上完整的代码
template <class KEY,class OTHER>
struct SET
{
KEY key;
OTHER other;
};
template<class KEY,class OTHER>
class dynamicSearchTable
{
public:
virtual SET<KEY, OTHER>* find(const KEY& x) = 0;
virtual void insert(const SET<KEY, OTHER>& x) = 0;
virtual void remove(const KEY& x) = 0;
virtual ~dynamicSearchTable() {};
};
template<class KEY, class OTHER>
class BinarySearchTree :public dynamicSearchTable<KEY, OTHER>
{
private:
struct BinaryNode
{
SET<KEY, OTHER>data;
BinaryNode* left;
BinaryNode* right;
BinaryNode(const SET<KEY, OTHER>& thedata, BinaryNode* It = NULL,
BinaryNode* rt = NULL):data(thedata),right(rt){}
};
BinaryNode* root;
public:
BinarySearchTree();
~BinarySearchTree();
SET <KEY, OTHER>* find(const KEY& x)const;
void insert(const SET<KEY, OTHER>& x);
void remove(const KEY& x);
private:
void insert(const SET<KEY, OTHER>& x, BinaryNode*& t);
void remove(const KEY& x, BinaryNode*& t);
SET <KEY, OTHER>* find(const KEY& x,BinaryNode* t)const;
void makeEmpty(BinaryNode* t);
};
template<class KEY, class OTHER>
SET <KEY, OTHER>* BinarySearchTree<KEY, OTHER>::find(const KEY& x)const
{
return find(x, root);
}
template<class KEY, class OTHER>
SET <KEY, OTHER>* BinarySearchTree<KEY, OTHER>::find(const KEY& x, BinaryNode* t)const
{
if (t == NULL || (t->data.key == x))
return (SET<KEY,OTHER>*)t;
if (x < t->data.key)
return find(x.t->left);
else return find(x, right);
}
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::insert(const SET<KEY, OTHER>& x)
{
insert(x, root);
}
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::insert(const SET<KEY, OTHER>& x, BinaryNode*& t)
{
if (t == NULL)
t = new BinaryNode(x, NULL, NULL);
else if (x.key < t->data.key)
insert(x, t->left);
else if (t->data.key < x.key)
insert(x, t->right);
}
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::remove(const KEY& x)
{
remove(x, root);
}
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::remove(const KEY& x, BinaryNode*& t)
{
if (t == NULL)return;
if (x < t->data.key)remove(x, t->left);
else if (t->data.key < x)remove(x, t->right);
else if (t->left != NULL && t->right != NULL)
{
BinaryNode* tmp = t->right;
while (tmp->left != NULL)tmp = tmp->left;
t->data = tmp->data;
remove(t->data.key, t->right);
}
else
{
BinaryNode* oldNode = t;
t = (t->left != NULL) ? t->left : t->right;
delete oldNode;
}
}
template<class KEY, class OTHER>
BinarySearchTree<KEY, OTHER>::BinarySearchTree()
{
root = NULL;
}
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::makeEmpty(BinaryNode* t)
{
if (t == NULL)return;
makeEmpty(t->left);
makeEmpty(t->right);
delete t;
}
template<class KEY, class OTHER>
BinarySearchTree<KEY, OTHER>::~BinarySearchTree()
{
makeEmpty(root);
}
int main()
{
return 0;
}
前提组成
template <class KEY,class OTHER> struct SET { KEY key; //关键字信息 OTHER other; //和其他信息 };
这是基本的数据元素
分别包括键值key和其他值value(就像字典里面对应的查找关键和词语解释)
template<class KEY,class OTHER> class dynamicSearchTable { public: virtual SET<KEY, OTHER>* find(const KEY& x) = 0; //查找目标元素,返回值为指针 virtual void insert(const SET<KEY, OTHER>& x) = 0; //插入功能 virtual void remove(const KEY& x) = 0; //显然是删除功能 virtual ~dynamicSearchTable() {}; //析构函数 };
这就是二叉查找树的父类动态查找表,也可以看作一个模板而已,提供了基础的一个动态查找的基本功能模板
动态二叉树
- 动态二叉树的主体类函数
template<class KEY, class OTHER>
class BinarySearchTree :public dynamicSearchTable<KEY, OTHER>
{
private:
struct BinaryNode
{
SET<KEY, OTHER>data;
BinaryNode* left;
BinaryNode* right;
BinaryNode(const SET<KEY, OTHER>& thedata, BinaryNode* It = NULL,
BinaryNode* rt = NULL):data(thedata),right(rt){}
}; BinaryNode* root;
public:
BinarySearchTree(); //构造函数
~BinarySearchTree(); //析构函数
SET <KEY, OTHER>* find(const KEY& x)const; //查找函数(公有,面向使用者)
void insert(const SET<KEY, OTHER>& x); //插入函数(公有,面向使用者)
void remove(const KEY& x); //删除函数(公有,面向使用者)
private:
void insert(const SET<KEY, OTHER>& x, BinaryNode*& t); //查找函数(私有,面向公有调用函数)
void remove(const KEY& x, BinaryNode*& t); //插入函数(公有,面向公有调用函数)
SET <KEY, OTHER>* find(const KEY& x,BinaryNode* t)const; //删除函数(公有,面向公有调用函数)
void makeEmpty(BinaryNode* t); //清空函数
};
树的节点分析:
struct BinaryNode
{
SET<KEY, OTHER>data; //本体的数据
BinaryNode* left; //左子节点的指针
BinaryNode* right; //右子节点的指针
BinaryNode(const SET<KEY, OTHER>& thedata, BinaryNode* It = NULL, //节点的构造函数,可以直接设定三个成员
BinaryNode* rt = NULL):data(thedata),right(rt){}
}; BinaryNode* root;
注意:后面经常会用到公有函数调用私有函数,虽然名称一样,但一个面向使用者,私有的面向共有的函数调用内部
void remove(const KEY& x) //删除函数(公有,面向使用者)
{
remove(x,root); //这里调用的就是私有函数
}
void remove(const KEY& x, BinaryNode*& t); //这就是私有函数的接口和实现
接下来分析函数的实现
- 构造函数
template<class KEY, class OTHER>
BinarySearchTree<KEY, OTHER>::BinarySearchTree()
{
root = NULL; //简单初始化了一下根节点
}
- makempty
主要还是递归循环删除,采用了前序遍历的思想
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::makeEmpty(BinaryNode* t)
{
if (t == NULL)return; //递归终止的条件(删到了空节点)
makeEmpty(t->left); //正常步骤先删左节点,往下递归
makeEmpty(t->right); //再删右节点,往下递归
delete t; //删除自己
}//最后就删除了所给节点和节点的所有子节点
拓展:其实最近刷题还看到了一种实现方法就是入队和出队在这里
优点:就是摆脱了递归的对于空间的大量调用,利用循环和队列实现(更多是在链表使用的方法)
其实这是层序遍历的使用方法,只是用这个实现了层序遍历的删除方式
**自己拓展的,不是范例的** Queue<BinaryNode*> queue; //存放节点指针的队列 template<class KEY, class OTHER> void BinarySearchTree<KEY, OTHER>::makeEmpty(BinaryNode* t) { queue.enqueue(t); //先把根节点放入队列 while(t.isempty()) //终止条件:检查队列是否为空,不空则继续进行 { BinaryNode* t=queue.dequeue(); //出队队列的第一个节点 if(t->left!=nullptr)queue.enqueue(t->left); //左子节点有就放进队列,没则不处理 if(t->right!=nullptr)queue.enqueue(t->right); //同理 delete t; //然后删除自己 } }
- 析构函数
template<class KEY, class OTHER>
BinarySearchTree<KEY, OTHER>::~BinarySearchTree()
{
makeEmpty(root); //就从root开始删除,很简单,不解释了
}
- 插入函数(insert)
从这插入函数观察二叉树的构成特点:左子节点小于父节点,右子节点大于父节点(节点不会重复)
这个特点就导致插入的时候有一个特性:以这个特点往下寻找插入点必然是叶节点,从而使插入特别方便。
函数还是递归实现的函数,逐次往下寻找(二叉树的结构导致如果树高了必然会导致搜索耗费大量的时间,之后会有扁平的处理方法)
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::insert(const SET<KEY, OTHER>& x)
{
insert(x, root); //表示从根节点往下寻找插入点
}
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::insert(const SET<KEY, OTHER>& x, BinaryNode*& t)
{
if (t == NULL)
t = new BinaryNode(x, NULL, NULL); //若根节点不存在,则直接存进去
else if (x.key < t->data.key) //若小于当前节点则向左递归
insert(x, t->left);
else if (t->data.key < x.key) //大于则向右递归
insert(x, t->right);
//等于的情况直接退出不干,二叉树的插入必须是不同
}
- 查找函数(find)
查找函数就是基于二叉树的结构特点建立的,左小右大
template<class KEY, class OTHER>
SET <KEY, OTHER>* BinarySearchTree<KEY, OTHER>::find(const KEY& x)const
{
return find(x, root);
}
template<class KEY, class OTHER>
SET <KEY, OTHER>* BinarySearchTree<KEY, OTHER>::find(const KEY& x, BinaryNode* t)const
{
if (t == NULL || (t->data.key == x)) //如果是不存在或者就是当前的节点
return (SET<KEY,OTHER>*)t; //找到就返回这个指针,没有的话就是返回空指针
if (x < t->data.key) //如果小于则向左找
return find(x.t->left);
else return find(x, right); //大于则向右找
}
注意:有人可能会注意到,t不是BinarNode吗,怎么能强制转换为SET类的指针啊
return (SET<KEY,OTHER>*)t;
这里需要解释一下结构体和类的一个特点:
对于类和结构的存贮使从第一个数据成员开始的。
举个例子:对于BinarNode的存储空间第一个就是存放的SET,因为在定义里面是写在第一个的。
从而对于指向BinaryNode存储空间的指针某种意义上也是指向了SET的开头,进而可以进行强制转换
//树节点的结构体 struct BinaryNode { SET<KEY, OTHER>data; BinaryNode* left; BinaryNode* right; BinaryNode(const SET<KEY, OTHER>& thedata, BinaryNode* It = NULL, BinaryNode* rt = NULL):data(thedata),right(rt){} }; BinaryNode* root;
- 删除函数(remove)
主体思路:
- 删除大体是这个思路为了保证树的结构不变,主要分三种情况
- 1.如果这个删除节点没有度数为0,很简单删除就好了
- 2.只有一个子节点,把字节点复制上去,把字节点删除
- 3.这个最复杂,因为他又俩个节点,为了保证树结构不变必然是在叶子节点中找到最靠近这个节点的数
- 其实根据这个查找二叉树的结构,分析出最接近的是左子节点一直往子节点的右子节点找(左子节点<x<当前节点),或者是右子节点的左子节点一直找(当前节点<x<右子节点)
- 对于这题我们假设向右子节点寻求最接近的值
最关键的是:
首先,替代问题,为了保证稳定性必须要替换一个最接近的节点互换位置,顶替
其次由于结构最接近无非 最近但小于(左节点的最右节点)和最近但大于 (右节点的最左字节点)
这个寻找方法便捷的地方在于必然是叶子节点替换,也易于删除
//这里的实现主要是最右子节点最左节点(大于且最靠近的树)
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::remove(const KEY& x)
{
remove(x, root);
}
template<class KEY, class OTHER>
void BinarySearchTree<KEY, OTHER>::remove(const KEY& x, BinaryNode*& t)
{
//先找到要删除的节点
if (t == NULL)return;
//第一种情况空节点返回
if (x < t->data.key)remove(x, t->left);
//第二种情况,要删除小于当前节点,往左递归
else if (t->data.key < x)remove(x, t->right);
//第三种情况要找的大于当前节点,往右边递归
else if (t->left != NULL && t->right != NULL)
{
BinaryNode* tmp = t->right;
while (tmp->left != NULL)tmp = tmp->left;
//循环找到右子节点的最左节点
t->data = tmp->data;
//替换俩个节点顺序
remove(t->data.key, t->right);
//删除替换后的叶子节点,结束
}
//第四种,找到了当前节点就是要删除的节点,但是左右都有子节点
else
{
BinaryNode* oldNode = t; t = (t->left != NULL) ? t->left : t->right;
//如果有一个子节点,t就是直接替换为子节点,然后删除原来的节点,即使没有子节点,t变为空间点也合乎逻辑
delete oldNode;
}
//第五种,找到了要么一个子节点,要么没有
//有没有发现,我选择右子树寻找替代,那么替代的那个点也是右子树要处理
}
有一点需要注意:
void BinarySearchTree<KEY, OTHER>::remove(const KEY& x, BinaryNode*& t)
我们发现节点的参数传递都是引用传递,那是为了方便节点直接替换
例如remove,正是因为引用传递,在层层递归中确保每次传下来的t都是确确实实来自于父节点里面直接的左右节点的指针
这样就维持了上下关系,确保修改是有效的
##总结:至此就分析结束了
(有时间在这里补一个二叉树查找性能的分析)