一、二叉搜索树节点类(BSTreeNode)
template<class K, class V>
class BSTreeNode
{
public:
BSTreeNode(K key = K(), V value = V())
:_key(key),
_value(value),
_left(nullptr),
_right(nullptr)
{
}
K _key;
V _value;
BSTreeNode* _left;
BSTreeNode* _right;
};
这个类定义了二叉搜索树的节点。它包含了键值对(_key
和_value
)以及指向左右子节点的指针(_left
和_right
)。构
二、二叉搜索树类(BSTree)
插入操作(Insert)
造函数允许用户在创建节点时指定键值对,也可以使用默认值。
template<class K, class V>
class BSTree
{
//...
public:
bool Insert(const K& key, const V& value)
{
Node* newnode = new Node(key, value);
if (_root == nullptr)
{
_root = newnode;
return true;
}
Node* newnodeP = nullptr;
Node* cur = _root;
while (cur!= nullptr)
{
if (cur->_key > key)
{
newnodeP = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
newnodeP = cur;
cur = cur->_right;
}
else if(cur->_key == key)
{
return false;
}
}
if (cur == newnodeP->_left)
newnodeP->_left = newnode;
else
newnodeP->_right = newnode;
return true;
}
//...
};
插入操作首先创建一个新节点。如果树为空,则新节点成为根节点。如果树不为空,从根节点开始遍历,根据键的大小决定向左子树或右子树移动,直到找到合适的插入位置。如果要插入的键已经存在于树中,则返回false
,表示插入失败。
图形解释:
假设要插入键值对(5, "value5")到一个已有的二叉搜索树中。如果当前树的结构为:
3
/ \
1 4
从根节点 3 开始,5 大于 3,所以移动到右子树 4。由于 4 没有子节点,且 5 大于 4,所以将新节点插入到 4 的右子树位置。插入后的树结构为:
3
/ \
1 4
\
5
查找操作(Find)
Node* Find(const K& key)
{
Node* cur = _root;
while (cur!= nullptr)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
查找操作从根节点开始,根据要查找的键与当前节点键的大小关系,向左子树或右子树移动,直到找到目标键或者遍历到空节点。如果找到目标键,则返回对应的节点指针;如果未找到,则返回nullptr
。
图形解释:
比如要在上面的树中查找键为 4 的节点。从根节点 3 开始,4 大于 3,移动到右子树 4,找到目标节点并返回其指针。
删除操作(Erase)
bool Erase(const K& key)
{
Node* cur = _root;
Node* curP = nullptr;
while (cur!= nullptr)
{
if (cur->_key > key)
{
curP = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
curP = cur;
cur = cur->_right;
}
else
break;
}
if (cur == nullptr)
return false;
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else if (cur == curP->_left)
curP->_left = cur->_right;
else
curP->_right = cur->_right;
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else if (cur == curP->_left)
curP->_left = cur->_left;
else
curP->_right = cur->_left;
delete cur;
}
else
{
Node* delP = cur;
Node* exchange = cur->_right;
while (exchange->_left!= nullptr)
{
delP = exchange;
exchange = exchange->_left;
}
cur->_key = exchange->_key;
cur->_value = exchange->_value;
if(delP->_left == exchange)
{
delP->_left = exchange->_right;
}
else
delP->_right = exchange->_right;
delete exchange;
}
return true;
}
删除操作首先找到要删除的节点。如果节点没有左子树,则将其父节点的相应指针指向该节点的右子树;如果节点没有右子树,则将其父节点的相应指针指向该节点的左子树;如果节点既有左子树又有右子树,则找到该节点右子树中的最小节点,将其值赋给要删除的节点,然后删除最小节点。图形解释:
假设要删除上面树中的节点 4。首先找到节点 4,它有右子树但没有左子树。如果节点 4 是根节点,那么根节点变为 4 的右子树;如果节点 4 是其父节点的左子节点,那么其父节点的左子树变为 4 的右子树;如果节点 4 是其父节点的右子节点,那么其父节点的右子树变为 4 的右子树。删除后的树结构为:
3
/ \
1 5
中序遍历(InOrder)
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ':' << root->_value << ' ';
_InOrder(root->_right);
}
中序遍历是一种遍历二叉树的方式,它先遍历左子树,然后访问根节点,最后遍历右子树。对于二叉搜索树,中序遍历会得到一个有序的键值对序列。
图形解释:
对于上面的树进行中序遍历,结果为 1:value1 3:value3 5:value5。
测试
void TestBSTree()
{
string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
BSTree<string, int> countTree;
for (auto str : strs)
{
auto ret = countTree.Find(str);
if (ret == NULL)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
countTree.Erase("苹果");
countTree.InOrder();
countTree.Erase("樱桃");
countTree.InOrder();
countTree.Erase("西瓜");
countTree.InOrder();
}
这个函数首先创建一个字符串和整数的二叉搜索树,用于统计给定字符串数组中每个水果出现的次数。遍历字符串数组,如果某个水果在树中不存在,则插入一个新节点,值为 1;如果水果已经存在,则将对应节点的值加 1。然后通过删除操作依次删除 “苹果”“樱桃”“西瓜”,并在每次删除后进行中序遍历,展示树的变化。