二叉搜索树的特点
对于二叉搜索树来说,每一个节点都存有一个关键字(key)用来区分不同的节点,每一个节点的key值各不相同。根据人们的需要,在我们可以同时放进一个value类型,来表示key的关键字所存的内容。这就和字典的功能比较相似,也就是俗称的KV。
1.左子树上所有节点的key值都小于根节点。
2.右子树上所有节点的key值都大于根节点。
从这里可知,二叉搜索树的key值能存放的类型必须是可以比较大小的类型。
这样的一棵树的特点是,如果用中序遍历这棵树,得到的将会是一组有序数组。
二叉搜索树的增删查改
对于一棵二叉搜索树的增删查改,首先改是不被允许的,因为一个节点的数据被修改了,这颗树很可能就不是二叉搜索树了,会改变这棵树原有的结构,这对于之后的AVL树以及红黑树都是一样的。
下面我们先来看一下它的增。
对于增来说,很简单,我们只要从根节点开始比较要插入节点的key值与当前节点cur的key值就可以了,如果根节点为空,则在_root插入该节点,若不为空,并且cur的key值比key值小,则往cur的右子树走,反之则往cur的左子树走,直到找到一个为空的节点插入该节点。如上图要插入key值为10的节点,则从根节点起,不断地走它的右子树,直到key值为9的右子树为空,则插入10。
对于找来说,也是一样的。从根节点起开始找,比较要找的key值与当前cur的key值的大小,cur的key比key小,则往右子树走,反之则往左子树走。若找到了该节点,则返回该节点,若找不到则返回NULL。
最难实现的二叉搜索树的删除。因为要删除一个节点,就会改变整棵树的形状,但我们又不想将这颗树的形状改变太多,怎么办呢?这里我们采用替换删除法。比如我们要删除上图的5,那么我们可以发现,如果我们把5删除,把6移到5的位置,整棵树依旧是二叉搜索树,且它的形状基本没有怎么变。把4移到5的位置效果是一样的。所以我们就可以用替换法来进行删除,不难发现的是,6这个节点是5的右树的最左节点(即中序遍历5的右子树的第一个节点),4这个节点是5的左树的最右节点(即中序遍历5的左子树的最后一个节点),因为两者效果是一样的,这里我们采用用左树的最右的节点的替换方法。
接下来我们分析要删除的节点的可能的情况(相对于第一张图),总的来说有以下三种情况:
1.左子树为空(如8节点)
2.右子树为空
3.左右子树都不为空(如3、5、7节点)
对于叶子节点来说,它既可以看成是左为空,也可以看成是右为空,这里不做特殊处理。
1.首先对于左子树为空的节点。我们在找到这个节点的过程中,同时记下它的父亲节点parent,如果cur是parent的左,则将cur的右给parent的左,反之则将cur的右给parent的右(如果parent为空,则将cur的右给_root)。
2.右子树为空与左子树为空一样,无非就是将上面cur的右变为cur的左,思想完全一样。
3.对于左右都不为空的情况,这时我们就要找到它右树的最左节点subLeft,在找subLeft的过程中,记下subLeft的父亲parent,把subLeft的key值赋给cur的key,如果subLeft是parent的右,则将subLeft的右赋给parent的右,反之则将subLeft的右赋给parent的左,然后将这个节点直接删除。
具体实现的代码如下:
#pragma once
#include <iostream>
using namespace std;
template <class K,class V>
struct BinarySearchTreeNode
{
K _key;
V _value;
BinarySearchTreeNode<K, V>* _left;
BinarySearchTreeNode<K, V>* _right;
BinarySearchTreeNode(const K& key, const V& value)
: _key(key)
, _value(value)
, _left(NULL)
, _right(NULL)
{}
};
template <class K,class V>
class BinarySearchTree
{
typedef BinarySearchTreeNode<K, V> Node;
public:
BinarySearchTree()
:_root(NULL)
{}
bool Insert(const K& key, const V& value)
{
if (_root == NULL)
{
_root = new Node(key, value);
return true;
}
Node* cur = _root;
Node* parent = NULL;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key, value);
if (cur->_key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
bool Remove(const K& key)
{
if (_root == NULL)
{
return false;
}
Node* parent = NULL;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
Node* del = cur;
//1.左为空 2.右为空 3.左右都不为空
if (cur->_left == NULL)
//左为空
{
if (parent == NULL)
//如果cur是根节点
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
}
else if (cur->_right == NULL)
//右为空
{
if (parent == NULL)
//如果cur是根节点
{
_root = cur->_left;
}
else
{
if (cur = parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
}
else
//左右都不为空
{
Node* parent = cur;
Node* subLeft = cur->_right;
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
del = subLeft;
cur->_key = subLeft->_key;
if (parent->_left == subLeft)
{
parent->_left = subLeft->_right;
}
else
{
parent->_right = subLeft->_right;
}
}
delete del;
return true;
}
}
return false;
}
Node* Find(const K& key)
{
if (_root == NULL)
{
return NULL;
}
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_right)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return NULL;
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
protected:
bool _Inorder(Node* root)
{
if (root == NULL)
{
return false;
}
_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);
return true;
}
private:
Node* _root;
};
void TestBinarySearchTree()
{
int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
BinarySearchTree<int, int> t;
for (size_t i = 0; i < sizeof(a) / sizeof(int); ++i)
{
t.Insert(a[i], i);
}
t.Inorder();
t.Remove(8);
t.Inorder();
t.Remove(0);
t.Inorder();
t.Remove(1);
t.Inorder();
t.Remove(3);
t.Inorder();
t.Remove(2);
t.Inorder();
t.Remove(9);
t.Inorder();
t.Remove(5);
t.Inorder();
t.Remove(4);
t.Inorder();
t.Remove(7);
t.Inorder();
t.Remove(6);
t.Inorder();
}
测试是随机删除的,每删一次打印一次,测试结果如下:
以上的增删查都是非递归的,接下来来看下递归的代码实现,因为核心思想是类似的,只是多了基层递归调用。不同的地方是,我们在传值的时候用了一点巧妙的手段,拿_RemoveR来说,我们传了一个节点的引用。这有什么用呢?因为在删除的时候,我们需要知道当前cur的parent,然而函数的返回值不可能既返回当前节点,又返回当前节点的父亲的,只能返回一个。
比如我们删8的时候,我们需要知道8的父亲7,把8的孩子9连到7上。这是我们给函数传一个(Node*& root),我们删8的时候,root的值是8,但同时,root也是7这个节点的别名,这样我们就巧妙地记下了当前节点的父亲节点。
#pragma once
#include <iostream>
using namespace std;
template <class K,class V>
struct BinarySearchTreeNode
{
K _key;
V _value;
BinarySearchTreeNode<K, V>* _left;
BinarySearchTreeNode<K, V>* _right;
BinarySearchTreeNode(const K& key, const V& value)
: _key(key)
, _value(value)
, _left(NULL)
, _right(NULL)
{}
};
template <class K,class V>
class BinarySearchTree
{
typedef BinarySearchTreeNode<K, V> Node;
public:
BinarySearchTree()
:_root(NULL)
{}
bool InsertR(const K& key, const V& value)
{
return _InsertR(_root, key, value);
}
bool _InsertR(Node*& root, const K& key, const V& value)
{
if (root == NULL)
{
root = new Node(key, value);
return true;
}
if (root->_key < key)
{
return _InsertR(root->_right, key, value);
}
else if (root->_key>key)
{
return _InsertR(root->_left, key, value);
}
else
{
return false;
}
}
bool RemoveR(const K& key)//递归删除
{
return _RemoveR(_root, key);
}
bool _RemoveR(Node*& root, const K& key)
{
if (root == NULL)
{
return false;
}
if (root->_key < key)
{
return _RemoveR(root->_right, key);
}
else if (root->_key > key)
{
return _RemoveR(root->_left, key);
}
else
{
Node* del = root;
if (root->_left == NULL)
//左为空
{
root = root->_right;
}
//右为空
else if (root->_right == NULL)
{
root = root->_left;
}
else
//左右都不为空
{
Node* parent = root;
Node* subLeft = root->_right;
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
del = subLeft;
root->_key = subLeft->_key;
if (parent->_left == subLeft)
{
parent->_left = subLeft->_right;
}
else
{
parent->_right = subLeft->_right;
}
}
delete del;
return true;
}
}
const Node* FindR(const K& key)
{
return(_root, key);
}
const Node* _FindR(Node* root,const K& key)
{
if (root == NULL)
{
return NULL;
}
if (root->_key < key)
{
return _FindR(root->_right, key);
}
else if (root->_key < key)
{
return _FindR(root->_left, key);
}
else
{
return root;
}
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
protected:
bool _Inorder(Node* root)
{
if (root == NULL)
{
return false;
}
_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);
return true;
}
private:
Node* _root;
};
void TestBinarySearchTree()
{
int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
BinarySearchTree<int, int> t;
for (size_t i = 0; i < sizeof(a) / sizeof(int); ++i)
{
t.InsertR(a[i], i);
}
t.RemoveR(8);
t.Inorder();
t.RemoveR(0);
t.Inorder();
t.RemoveR(1);
t.Inorder();
t.RemoveR(3);
t.Inorder();
t.RemoveR(2);
t.Inorder();
t.RemoveR(9);
t.Inorder();
t.RemoveR(5);
t.Inorder();
t.RemoveR(4);
t.Inorder();
t.RemoveR(7);
t.Inorder();
t.RemoveR(6);
t.Inorder();
}
测试结果也是一样的