学习《STL源码剖析》关联容器的章节,map,set等关联容器的底层实现都是红黑树,本着循序渐进的思想,先实现二叉搜索树。读懂了二叉搜索树的定义后,实现了二叉搜索树的基本功能。文章属原创,代码全手打,欢迎指正讨论。首先给出类的定义,如下:
#include<iostream>
#include<string>
#include<cstring>
#include<sstream>
//#include<>
using namespace std;
class BST
{
private:
//嵌套类,仅在BST中有效
class Node
{
public:
int val;
Node *left;
Node *right;
Node *parent; //增加父节点,在删除节点时父节点很有用
Node()
{
val = 0;
left = right = parent = NULL;
}
};
Node *m_root; //存储根节点
Node *m_cur; //存储查找的结果
int m_size; //存储树的大小
void SetNode(Node* cur,Node* newNode); //设置节点的父子关系
bool CompareTree(const Node *r1,Node* r2) const;
public:
bool InsertNode(const int val); //插入节点
bool FindNode(const int val); //找到返回true,val所在的位置就是pos
bool DelNode(const int val); //删除节点
void Inorder(Node *p, string &str) const; //中序遍历输出参数可自定义
void DestroyTree(Node *p); //销毁整棵二叉树,模仿后序遍历
bool operator==(BST &t) const; //用于判断两个BST是否相等
void CreateBST(const string &str);
Node* GetRootNode() const
{
return m_root;
}
BST() :m_root(NULL), m_cur(NULL),m_size(0){}
~BST()
{
DestroyTree(m_root);
}
};
一、实现节点插入功能,此处是构建二叉搜索树的基本操作。插入节点时需满足的条件有二:
1、左子树小于父节点。
2、右子树不小于父节点。
以下是详细实现代码,以下代码虽不是最简短的写法,但是但是能想到的思路最清晰的写法,适合用于学习理解二叉搜索树。
bool BST::InsertNode(const int val)
{
Node *p = new Node();
p->val = val;
m_size++;
if (m_root == NULL)
{
m_root = p;
return true;
}
Node * cur = m_root;
Node * last = m_root;
bool lflag = false; //为true说明是左子树
while (cur != NULL)
{
last = cur;
if (val >= cur->val)
{
cur = cur->right;
lflag = false;
}
else
{
cur = cur->left;
lflag = true;
}
}
//建立父节点与子树的关系
cur = p;
cur->parent = last;
lflag == true ? last->left = cur : last->right = cur;
return true;
}
二、建立二叉搜索树。此处只需要循环调用上面实现的InsertNode方法即可。
void BST::CreateBST(const string &str)
{
stringstream ss;
int val;
for (int i = 0; i < str.size(); i++)
{
ss << str[i];
ss >> val;
ss.clear();//多次转换需要清空
InsertNode(val);
}
}
三、中序遍历二叉搜索树。二叉搜索树中序遍历的结果可得到递增的有序序列,从此处可以理解为BST查找的平均复杂度是O(logn),因为其查找过程类似于二分查找。为了方便输出查看,将输出结果放在string中。中序遍历实现方法有两种,其一为采用递归的方式;其二为使用栈来辅助实现。此处给出递归版本,今后再补充给出非递归版本。
void BST::Inorder(Node *p, string &st) const
{
if (p == NULL)
{
return;
}
Inorder(p->left,st);
stringstream ss;
string t;
ss << p->val;
ss >> t;
st += t;
Inorder(p->right, st);
}
四、在二叉搜索树中查找节点。二叉排序树的查找操作,平均复杂度为O(logn),最差为O(n)。而平衡二叉搜索树能很好的处理最坏情况。常见的平衡二叉搜索树:红黑树等
bool BST::FindNode(const int val)
{
if (m_root == NULL)
{
return false;
}
bool ret = false;
Node * cur = m_root;
while (cur != NULL)
{
if (cur->val == val)
{
m_cur = cur;
ret = true;
break;
}
else if (val > cur->val)
{
cur = cur->right;
}
else
{
cur = cur->left;
}
}
return ret;
}
五、二叉搜索树删除节点。二叉搜索树不能只是简单的删除某个节点,应该保证BST删除元素后仍然符合二叉搜索树的定义。删除分为三种情况:
1、左子树和右子树均不为空,那么就从右子树中找到一个最小值来替换。
2、仅有一个单个子树,那就将当前元素删除即可
3、没有子树
在编码时一定要注意根节点是特殊情况,根节点没有父节点。
此处代码分为两个步骤,首先找到要替代节点(倘若有),然后替换掉原来的节点。
bool BST::DelNode(const int val)
{
if (m_root == NULL)
{
return false;
}
m_size--;
if (!FindNode(val))
{
return false;
}
//难点:找到叶子节点后如何处理父节点,防止父节点中出现野指针
//为方便操作,而返回去增加定义父指针
if (m_cur->left != NULL && m_cur->right != NULL)//有两个子树
{
Node * min = m_cur->right;
while (min->left != NULL)
{
min = min->left;
}
m_cur->right == min ? min->parent->right = NULL : min->parent->left = NULL;
SetNode(m_cur,min);
delete m_cur;
m_cur = NULL;
}
else if (m_cur->left != NULL || m_cur->right != NULL)//仅有一个子树
{
if (m_cur->left != NULL)
{
SetNode(m_cur, m_cur->left);
delete m_cur;
m_cur = NULL;
}
else
{
SetNode(m_cur, m_cur->right);
delete m_cur;
m_cur = NULL;
}
}
else //叶子节点、无子树的根节点直接删除
{
SetNode(m_cur, NULL);
delete m_cur;
m_cur = NULL;
}
return false;
}
DelNode方法中调用的SetNode代码实现如下,主要作用是重新设定原来节点周围的父子关系。
总共要设定五条关系
1、当前节点与父节点之间的关系(单边1条)
2、与左子树之间的关系(双边2条)
3、与右子树之间的关系(双边2条)
void BST::SetNode(Node* cur, Node* newNode)
{
//与父节点
if (cur->parent != NULL)
{
if (cur->parent->left == cur)
{
cur->parent->left = newNode;
}
else
{
cur->parent->right = newNode;
}
}
else
{
m_root = newNode; //父节点为空,说明为根节点
}
//与左子树
if (cur->left != NULL)
{
//子->父
cur->left->parent = newNode;
//父->子
if (newNode != cur->left)
{
newNode->left = cur->left;
}
else
{
//不操作
}
}
//右子树
if (cur->right != NULL)
{
//子->父
cur->right->parent = newNode;
if (newNode != cur->right)
{
newNode->right = cur->right;
}
else
{
//不操作
}
}
}
六、二叉树拓扑结构比较。为了比较两个二叉树是否相同,可采用递归的方式实现,代码如下:
bool BST::CompareTree(const Node *r1,Node* r2) const
{
if (r1 == NULL && r2 == NULL)
{
return true;
}
if (r1 == NULL || r2 == NULL)
{
return false;
}
if (r1->val == r2->val)
{
if (CompareTree(r1->left, r2->left) && CompareTree(r1->right, r2->right))
{
return true;
}
}
return false;
}
bool BST::operator==(BST &t) const
{
return CompareTree(GetRootNode(),t.GetRootNode());
}
七、二叉搜索树的销毁。所有节点均使用的new获取的堆空间,因此必须手动释放,否则会造成内存泄漏。释放的方法模仿后序遍历实现。
void BST::DestroyTree(Node *p)
{
if (p == NULL)
{
return;
}
DestroyTree(p->left);
DestroyTree(p->right);
delete p;
}
现在二叉搜索树已经实现,可以通过一道题目来练习。
题目描述
判断两序列是否为同一二叉搜索树序列
输入描述:
开始一个数n,(1<=n<=20) 表示有n个需要判断,n= 0 的时候输入结束。
接下去一行是一个序列,序列长度小于10,包含(0~9)的数字,没有重复数字,根据这个序列可以构造出一颗二叉搜索树。
接下去的n行有n个序列,每个序列格式跟第一个序列一样,请判断这两个序列是否能组成同一颗二叉搜索树。
输出描述:
如果序列相同则输出YES,否则输出NO
输入例子:
2
567432
543267
576342
0
输出例子:
YES
NO
整个工程的全部代码如下:
#include<iostream>
#include<stack>
#include<vector>
#include<queue>
#include<map>
#include<algorithm>
#include<cctype>
#include<string>
#include<cstring>
#include<sstream>
//#include<>
using namespace std;
class BST
{
private:
//嵌套类,仅在BST中有效
class Node
{
public:
int val;
Node *left;
Node *right;
Node *parent; //增加父节点,在删除节点时父节点很有用
Node()
{
val = 0;
left = right = parent = NULL;
}
};
Node *m_root; //存储根节点
Node *m_cur; //存储查找的结果
int m_size; //存储树的大小
void SetNode(Node* cur,Node* newNode); //设置节点的父子关系
bool CompareTree(const Node *r1,Node* r2) const;
public:
bool InsertNode(const int val); //插入节点
bool FindNode(const int val); //找到返回true,val所在的位置就是pos
bool DelNode(const int val); //删除节点
void Inorder(Node *p, string &str) const; //中序遍历输出参数可自定义
void DestroyTree(Node *p); //销毁整棵二叉树,模仿后序遍历
bool operator==(BST &t) const; //用于判断两个BST是否相等
void CreateBST(const string &str);
Node* GetRootNode() const
{
return m_root;
}
BST() :m_root(NULL), m_cur(NULL),m_size(0){}
~BST()
{
DestroyTree(m_root);
}
};
//二叉排序树的查找操作,平均复杂度为O(logn),最差为O(n)。而平衡二叉搜索树能很好的
//处理最坏情况。常见的平衡二叉搜索树:红黑树等
bool BST::FindNode(const int val)
{
if (m_root == NULL)
{
return false;
}
bool ret = false;
Node * cur = m_root;
while (cur != NULL)
{
if (cur->val == val)
{
m_cur = cur;
ret = true;
break;
}
else if (val > cur->val)
{
cur = cur->right;
}
else
{
cur = cur->left;
}
}
return ret;
}
bool BST::InsertNode(const int val)
{
Node *p = new Node();
p->val = val;
m_size++;
if (m_root == NULL)
{
m_root = p;
return true;
}
Node * cur = m_root;
Node * last = m_root;
bool lflag = false;
while (cur != NULL)
{
last = cur;
if (val >= cur->val)
{
cur = cur->right;
lflag = false;
}
else
{
cur = cur->left;
lflag = true;
}
}
cur = p;
cur->parent = last;
lflag == true ? last->left = cur : last->right = cur;
return true;
}
void BST::CreateBST(const string &str)
{
stringstream ss;
int val;
for (int i = 0; i < str.size(); i++)
{
ss << str[i];
ss >> val;
ss.clear();//多次转换需要清空
InsertNode(val);
}
}
/*
总共要设定五条关系
1、当前节点与父节点之间的关系(单边1条)
2、与左子树之间的关系(双边2条)
3、与右子树之间的关系(双边2条)
*/
void BST::SetNode(Node* cur, Node* newNode)
{
//与父节点
if (cur->parent != NULL)
{
if (cur->parent->left == cur)
{
cur->parent->left = newNode;
}
else
{
cur->parent->right = newNode;
}
}
else
{
m_root = newNode; //父节点为空,说明为根节点
}
//与左子树
if (cur->left != NULL)
{
//子->父
cur->left->parent = newNode;
//父->子
if (newNode != cur->left)
{
newNode->left = cur->left;
}
else
{
//不操作
}
}
if (cur->right != NULL)
{
//子->父
cur->right->parent = newNode;
if (newNode != cur->right)
{
newNode->right = cur->right;
}
else
{
//不操作
}
}
}
//二叉搜索树不能只是简单的删除某个节点,应该保证删除元素后
//仍然符合二叉搜索树的定义
/*
1、左子树和右子树均不为空,那么就从右子树中找到一个最小值来替换。
2、仅有一个单个子树或者没有子树,那就将当前元素删除即可
*/
bool BST::DelNode(const int val)
{
if (m_root == NULL)
{
return false;
}
m_size--;
if (!FindNode(val))
{
return false;
}
//难点:找到叶子节点后如何处理父节点,防止父节点中出现野指针
//为方便操作,而返回去增加定义父指针
if (m_cur->left != NULL && m_cur->right != NULL)
{
Node * min = m_cur->right;
while (min->left != NULL)
{
min = min->left;
}
m_cur->right == min ? min->parent->right = NULL : min->parent->left = NULL;
SetNode(m_cur,min);
delete m_cur;
m_cur = NULL;
}
else if (m_cur->left != NULL || m_cur->right != NULL)
{
if (m_cur->left != NULL)
{
SetNode(m_cur, m_cur->left);
delete m_cur;
m_cur = NULL;
}
else
{
SetNode(m_cur, m_cur->right);
delete m_cur;
m_cur = NULL;
}
}
else //叶子节点、无子树的根节点直接删除
{
SetNode(m_cur, NULL);
delete m_cur;
m_cur = NULL;
}
return false;
}
bool BST::CompareTree(const Node *r1,Node* r2) const
{
if (r1 == NULL && r2 == NULL)
{
return true;
}
if (r1 == NULL || r2 == NULL)
{
return false;
}
if (r1->val == r2->val)
{
if (CompareTree(r1->left, r2->left) && CompareTree(r1->right, r2->right))
{
return true;
}
}
return false;
}
bool BST::operator==(BST &t) const
{
return CompareTree(GetRootNode(),t.GetRootNode());
}
void BST::Inorder(Node *p, string &st) const
{
if (p == NULL)
{
return;
}
Inorder(p->left,st);
stringstream ss;
string t;
ss << p->val;
ss >> t;
st += t;
Inorder(p->right, st);
}
void BST::DestroyTree(Node *p)
{
if (p == NULL)
{
return;
}
DestroyTree(p->left);
DestroyTree(p->right);
delete p;
}
int main()
{
//注释掉的为原始测试代码
//BST tree;
//BST tree1,tree2;
//string str = "567432";// "567043289";
//string str1 = "543267";
//string str2 = "576342";
//tree.CreateBST(str);
//tree1.CreateBST(str1);
//tree2.CreateBST(str2);
//if (tree2 == tree)
//{
// cout << "same tree!!" << endl;
//}
//str.clear();
//tree.Inorder(tree.GetRootNode(),str);
//cout << str << endl;
//if(tree.FindNode(4))
//{
// cout << "I find it !!" << endl;
//}
//else
//{
// cout << "I find nothing!" << endl;
//}
//tree.DelNode(4);
//if (tree.FindNode(4))
//{
// cout << "I find it !!" << endl;
//}
//else
//{
// cout << "I find nothing!" << endl;
//}
//str.clear();
//tree.Inorder(tree.GetRootNode(), str);
//cout << str << endl;
int n = 0;
string str;
while (cin >> n >> str)
{
BST tree;
tree.CreateBST(str);
while (n--)
{
string match;
cin >> match;
BST treeMatch;
treeMatch.CreateBST(match);
if (tree == treeMatch)
{
cout << "YES" << endl;
}
else
{
cout << "NO"<< endl;
}
}
}
return 0;
}
写在最后:完整的实现了二叉搜索树后回过来看,在实现删除功能时可以不增加父指针,增加的指针带来了替换操作的复杂度上升。直接导致这部分代码需要更多的时间调试。