二叉查找树是一种非常重要的结构,它解决了链表查找慢,数组插入慢的问题,将查找和插入都降低到 lgN数量级。
本文主要参考了《算法(第4版)》里的思想,用C++进行实现。
1.节点结构
class treeNode
{
public :
treeNode(int key, int val, treeNode* left, treeNode* right);
treeNode(int key, int val);
public:
int _key;
int _val;
treeNode* _left;
treeNode* _right;
int _nodeNum;//以该节点为根的树的节点数
};
2.二叉树函数声明
class BST
{
public:
BST();
~BST();
void put(int key, int val);//添加键值为key,值为val的节点
treeNode* get(int key);//获得键值为key的节点
void inorder();//前序遍历树,并打印出键值
treeNode* select(int r);//获得排名为r的节点
int rank(int key);//获得键值为key的排名
int floor(int key);//获得不大于key的最大键值
treeNode* getMin();//获得键值最小的节点
int deleteMin();//删除键值最小的节点(释放内存)
bool deleteNode(int key);//删除键值为key的节点(释放内存)
private:
treeNode* put(treeNode* root, int key, int val);
treeNode* get(treeNode* root, int key);
void inorder(treeNode* root);
void deleteAllNodes(treeNode* root);
int size(treeNode* root);//获取以root为根的二叉树的节点数
treeNode* select(treeNode* root, int rank);
int rank(treeNode* root, int key);
treeNode* floor(treeNode* root, int key);
treeNode* getMin(treeNode* root);
treeNode* removeMinNode(treeNode* &root);//将键值最小的节点从树中移除,返回该节点(不释放内存)
treeNode* removeNode(treeNode* &root, int key);//将键值为key的节点从树中移除,返回该节点(不释放内存)
private:
treeNode* _root;
};
3.代码实现 //删除操作有误,见文末 01.05
BST::BST()
:_root(nullptr)
{
}
BST::~BST()
{
deleteAllNodes(_root);
}
void BST::deleteAllNodes(treeNode* root)//后序遍历二叉树,并删除节点
{
if(root==nullptr) return;
deleteAllNodes(root->_left);
deleteAllNodes(root->_right);
delete root;
root=nullptr;
}
void BST::put(int key, int val)//添加键值为key,值为val的节点
{
_root=put(_root, key, val);
}
treeNode* BST::put(treeNode* root, int key, int val)
{
if(root==nullptr) return new treeNode(key, val);
if(key< root->_key) root->_left=put(root->_left, key, val);
else if(key> root->_key) root->_right=put(root->_right, key, val);
else root->_val=val;//如果已存在则更新val
root->_nodeNum=size(root->_left)+size(root->_right)+1;//更新节点数,不用root->_nodeNum++;是考虑到节点已存在的情况
return root;
}
treeNode* BST::get(int key)//获取键值为key的值
{
return get(_root, key);
}
treeNode* BST::get(treeNode* root, int key)
{
if(root==nullptr) return root;
if(key< root->_key) return get(root->_left, key);
else if(key> root->_key) return get(root->_right, key);
else return root;
}
void BST::inorder()//前序遍历,顺序输出节点
{
std::cout<<"values in order:"<<std::endl;
inorder(_root);
std::cout<<std::endl;
}
void BST::inorder(treeNode* root)
{
if(root==nullptr) return ;
inorder(root->_left);
std::cout<<root->_key<<" ";
inorder(root->_right);
}
int BST::size(treeNode* root)//获取以root为根的二叉树的节点数
{
if(root==nullptr) return 0;
else return root->_nodeNum;
}
treeNode* BST::select(int rank)
{
if(rank>size(_root)) return nullptr;//如果排名比节点数还多
return select(_root, rank);
}
treeNode* BST::select(treeNode* root, int rank)
{
if(root==nullptr) return nullptr;
int curRank=size(root->_left)+1;//(!)当前节点(在该根节点的树中的)排名为左子树+1
if(rank==curRank) return root;
else if(rank<curRank) return select(root->_left, rank);
else return select(root->_right, rank-curRank);//(!)如果在右子树中,则在右子树中的排名为总排名-当前节点的排名
}
int BST::rank(int key)//获得键值为key的排名
{
return rank(_root, key);
}
int BST::rank(treeNode* root, int key)//获得键值为key的排名
{
if(root==nullptr) return 0;//如果没找到,返回0
int curRank=size(root->_left)+1;
if(key< root->_key) return rank(root->_left, key);
else if(key> root->_key) return curRank+rank(root->_right, key);
return curRank;
}
int BST::floor(int key)//获得不大于key的最大键值
{
treeNode* res=floor(_root, key);
if(res) return res->_key;
else return key+1;//如果没有找到,返回key+1
}
treeNode* BST::floor(treeNode* root, int key)
{
if(root==nullptr) return nullptr;
if(key< root->_key) return floor(root->_left, key);//如果比当前节点小,必定在左子树
else if(key> root->_key) //(!)如果比当前节点大,可能在右子树
{
treeNode* res=floor(root->_right, key);
if(res==nullptr) return root;//(!)如果不在右子树,则返回当前节点
else return res;
}
else return root;//如果等于当前节点,返回当前节点
}
treeNode* BST::getMin()
{
return getMin(_root);
}
treeNode* BST::getMin(treeNode* root)
{
if(root->_left==nullptr) return root;
return getMin(root->_left);
}
int BST::deleteMin()
{
if(_root==nullptr) return 0;
treeNode* res=removeMinNode(_root);
int key=res->_key;
delete res;//释放节点内存
return key;
}
treeNode* BST::removeMinNode(treeNode* &root)//(!)这里用指针的引用,因为会改变实参(指针)的内容
{
if(root->_left==nullptr) //如果没有左子树,则该节点为最小
{
treeNode* res=root;
root=root->_right;//(!)将右子树(可能为空)的根节点代替当前节点,形参用指针引用的原因
return res;
}
root->_nodeNum--;
return removeMinNode(root->_left);
}
bool BST::deleteNode(int key)
{
treeNode* res=removeNode(_root, key);
if(res==nullptr) return false;
delete res;
return true;
}
treeNode* BST::removeNode(treeNode* &root, int key)//(!)这里用指针的引用,因为会改变实参(指针)的内容
{
if(root==nullptr) return root;//没找到
if(key< root->_key) return removeNode(root->_left, key);
else if(key> root->_key) return removeNode(root->_right, key);
else //找到
{
treeNode* res=root;//要返回的节点
if(root->_left==nullptr)//如果只有一个或0个子树,则直接将存在的子树(或空)代替当前节点
{
root=root->_right;
}
else if(root->_right==nullptr)
{
root=root->_left;
}
else//(!)如果左右子树都存在,则用右子树中最小节点来代替当前节点
{
treeNode* rootNew=removeMinNode(root->_right);
rootNew->_left=root->_left;
rootNew->_right=root->_right;
root=rootNew;
}
return res;
}
}
主要实现思想都在注释中可以找到,其中要注意的几个编程点我在注释中用感叹号(!)进行标记,主要难点在于删除节点。
删除节点主要分成3种情况:
1)待删除的节点没有子树
这种情况最简单,只需要将节点直接删除即可
2)待删除节点只有一个子树
这种情况需要在节点删除后,用节点的子树根节点进行替换原节点
3)待删除节点存在2个子树
这种情况比较复杂,由于替换的节点必须保证比左子树都大,比右子树都小,所以要用左子树中的最大值或者右子树中的最小值进行替换,这里采用右子树中的最小节点来替换。
前2种情况可以合并为一种情况。
//01.05编辑//
今天跑的时候才发现删除操作没有更新节点的数目,导致删除后rank等几个函数出错了。另外没有考虑到删除右子树最小节点的时候有可能刚好就是右子树的根节点的情况,这种情况会导致rootNew->_right=root->_right; 语句使rootNew->_right =rootNew;
修改后如下:
treeNode* BST::removeNode(treeNode* &root, int key)//(!)这里用指针的引用,因为会改变实参(指针)的内容
{
if(root==nullptr) return root;//没找到
treeNode* res=nullptr;
if(key< root->_key) res= removeNode(root->_left, key);
else if(key> root->_key) res= removeNode(root->_right, key);
else //找到
{
if(root->_left==nullptr)//如果只有一个或0个子树,则直接将存在的子树(或空)代替当前节点
{
root=root->_right;
}
else if(root->_right==nullptr)
{
root=root->_left;
}
else//(!)如果左右子树都存在,则用右子树中最小节点来代替当前节点
{
treeNode* rootNew=removeMinNode(root->_right);
rootNew->_left=root->_left;
if(rootNew!=root->_right)//(!)如果右子树的最小节点刚好是root的右子节点,则不需要处理
rootNew->_right=root->_right;
root=rootNew;
}
}
root->_nodeNum=size(root->_left)+size(root->_right)+1;
return res;
}
但是递归里定义了一个临时变量res来存返回指针,而且每次递归结束前都不能被释放,感觉有点占用内存,看了一下算法书里的实现,发现里面返回的是当前节点(即第一层递归返回的是根节点)而不是删除节点,相同的还有removeMin函数。采用返回当前节点的方式的话就必须在这个函数里面同时释放删除节点的内存,实现如下:
treeNode* BST::removeNode(treeNode* root, int key)//(!)这里用指针的引用,因为会改变实参(指针)的内容
{
if(root==nullptr) return root;//没找到
if(key< root->_key) root->_left=removeNode(root->_left, key);
else if(key > root->_key) root->_right=removeNode(root->_right, key);
else
{
treeNode* N;
if(root->_left==nullptr)//如果只有一个或0个子树,则直接将存在的子树(或空)代替当前节点
{
N=root;
root=root->_right;
}
else if(root->_right==nullptr)
{
N=root;
root=root->_left;
}
else
{
N=root;
root=removeMinNode(root->_right);
root->_left=N->_left;
if(root!=N->_right)
root->_right=N->_right;
}
delete N;
}
root->_nodeNum=size(root->_left)+size(root->_right)+1;
return root;
}
注意用上面的实现代码的话,deleteNode函数也要作相应修改,此处略。