一、二叉搜索树的概念
1、定义
二叉搜索树,二叉查找树,(又称为二叉排序树),它满足如下四点性质:
1)空树是二叉搜索树;
2)若它的左子树不为空,则左子树上所有结点的值均小于它根结点的值
3)若它的右子树不为空,则右子树上所有结点的值均大于它根结点的值;
4)它的左右子树均为二叉搜索
2、用途
从二叉搜索树的定义可知,它的前提是二叉树,并且采用了递归的方式进行定义,它的结点间满足一个偏序关系,左子树根结点的一定比父结点小,右子树根结点的值定比父结点大。
正如它的名字所说,构造这样一棵树的目的是为了提高搜索的速度,如果对二叉搜索树进行中序遍历,我们可以发现,得到的序列是一个递增序列。
二、代码实现
1、创建树节点
template<typename T>
struct TreeNode {//树节点
T val;
TreeNode* left;
TreeNode* right;
TreeNode() :val(0), left(NULL), right(NULL) {}
TreeNode(T x) :val(x), left(NULL), right(NULL) {}
};
创建一个树节点,包括数据域,左子树节点,右子树节点和树结点的构造函数。
2、创建二叉搜索树
template<typename T>
class BinaryTreeSearch {//二叉搜索树
private:
TreeNode<T>* root;
TreeNode<T>* insertNode(TreeNode<T>* node, T value);//插入
TreeNode<T>* removeNode(TreeNode<T>* node, T value);//删除
bool searchNode(TreeNode<T>* node, T value);//搜索
void inOrder(TreeNode<T>* node);//中序遍历
};
二叉搜索树中包括根节点和一些对这棵树进行操作的函数,插入节点,删除节点,搜索,以及中序遍历。
下面是提供给外部的一些接口:
public:
BinaryTreeSearch() :root(NULL){}
~BinaryTreeSearch();
void insert(T value) {
root = insertNode(root, value);
}
void remove(T value) {
root = removeNode(root, value);
}
bool search(T value) {
return searchNode(root, value);
}
void inOrderTraversal() {
inOrder(root);
cout << endl;
}
3.insertNode插入节点
template<typename T>//插入
TreeNode<T>* BinaryTreeSearch<T>::insertNode(TreeNode<T>* node, T value) {
if (node == NULL) return new TreeNode<T>(value);
if (value < node->val)
{
node->left = insertNode(node->left, value);
}
else if (value > node->val)
{
node->right = insertNode(node->right, value);
}
return node;
}
因为这是一个二叉搜索树,所以会在插入时就进行排序,如果插入值大于当前节点的值,就放在右子树,如果小于,就放在左子树。通过递归,当递归到当前树节点为空时,就返回一个有参构造后的树节点,链接在父节点的对应位置。最后,当递归结束后,返回根节点给root。
4.searchNode搜索
对于一颗二叉搜索树,搜索的功能肯定是必不可少的
template<typename T>//搜索
bool BinaryTreeSearch<T>::searchNode(TreeNode<T>* node, T value) {
if (node == NULL) return false;
if (value < node->val) {
return searchNode(node->left, value);
}
else if (value > node->val) {
return searchNode(node->right, value);
}
return true;
}
同样,当查找值大于当前节点的值时,走右子树,小于则走左子树,通过不断地递归,当查找值既不大于节点值也不小于时,就说明在当前二叉树中找到了该值,返回true;如果递归到当前节点等于NULL时,就说明递归到了叶子节点,就说明没有找到该值,返回false。
5.inOrder中序遍历
template<typename T>//中序遍历
void BinaryTreeSearch<T>::inOrder(TreeNode<T>* node) {
if (node) {
inOrder(node->left);
cout << node->val << " ";
inOrder(node->right);
}
}
这个没什么好说的,看代码叭。
6.removeNode删除!!!
这一段代码就很值得讨论了。首先,删除值为 val 的结点的过程,从根结点出发,总共七种情况依次判断:
第1步、如果当前结点为空,则直接返回空树,
第2步、如果要删除的值小于当前结点的值,则递归调用左子树进行结点的删除
第3步、如果要删除的值大于当前结点的值,则递归调用右子树进行结点的删除。
第4步、如果当前结点是一个叶子结点,即它没有左子树和右子树,那么删除该结点,并且返
回空树。第5步、如果当前结点只有右子树而没有左子树,那么删除当前结点并将右子树返回,
第6步、如果当前结点只有左子树而没有右子树,那么删除当前结点并将左子树返回,
第7步、如果当前结点既有左子树又有右子树,那么找到当前结点右子树中的最小值结点(即最左边的结点),将当前结点的值替换为这个最小值结点的值,然后递归地删除右子树中该最小值结点。
对于删除节点这一过程来说,用递归去找到需要删除的节点 即:
if (node == NULL)return NULL;
if (value < node->val) {
node->left = removeNode(node->left, value);
}
else if (value > node->val) {
node->right = removeNode(node->right, value);
}
else{
.......//找到需要删除的节点了
}
通过不断递归,找到需要删除的节点,如果该节点为空,则直接返回NULL;
找到需要删除的节点后 ,如果该节点为叶子节点,则直接删除节点,然后返回NULL;
if (node->left == NULL && node->right == NULL) {//删除叶子节点
delete node;
return NULL;
}
如果该节点有左子树或者右子树,则先保存其左or右子树,然后删除节点,并返回其左or右子树:
else if (node->left == NULL) {//左子树为空,右子树不为空
TreeNode<T>* rightChild = node->right;
delete node;
return rightChild;
}
else if (node->right == NULL) {//右子树为空,左子树不为空
TreeNode<T>* leftChild = node->left;
delete node;
return leftChild;
}
如果左右子树均不为空,则 在其右子树中找到最小值后,把值赋给需要删除的节点,并把最小值的节点删除。
解释:因为二叉搜索树的右子树中任意值肯定大于左子树中任意值,要保证二叉搜索树这一特征,且通过中序遍历后 是一个升序排列,所以要找一个大于左子树但是小于右子树的值来作为根节点,那么,右子树中的最小值就再合适不过了。
else {//左右子树均不为空
TreeNode<T>* replacement = node->right;
while (replacement->left) {
replacement = replacement->left;
}
node->val = replacement->val;
node->right = removeNode(node->right, replacement->val);
}
其中,replacement为右子树中的最小值的节点,将replacement的值赋给需要删除的节点,就能保证二叉搜索树的特征,然后,通过removeNode删除replacement 。
下面用一个GIF来演示这一过程:
下面是删除节点的完整代码:
template<typename T>//删除
TreeNode<T>* BinaryTreeSearch<T>::removeNode(TreeNode<T>* node, T value) {
if (node == NULL)return NULL;
if (value < node->val) {
node->left = removeNode(node->left, value);
}
else if (value > node->val) {
node->right = removeNode(node->right, value);
}
else {
if (node->left == NULL && node->right == NULL) {//删除叶子节点
delete node;
return NULL;
}
else if (node->left == NULL) {//左子树为空,右子树不为空
TreeNode<T>* rightChild = node->right;
delete node;
return rightChild;
}
else if (node->right == NULL) {//右子树为空,左子树不为空
TreeNode<T>* leftChild = node->left;
delete node;
return leftChild;
}
else {//左右子树均不为空
TreeNode<T>* replacement = node->right;
while (replacement->left) {
replacement = replacement->left;
}
node->val = replacement->val;
node->right = removeNode(node->right, replacement->val);
}
}
return node;
}
三、代码测试
和set一样,对于相同的值,是无法插入的。通过中序遍历,可以发现,原来无序的插入,最后变成了有序的。
删除节点:
查找: