二叉搜索树的实现
二叉搜索树(Binary Search Tree,BST)是一种基于二叉树的数据结构,具有以下特点:
- 对于树中的每个节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。
- 左子树和右子树也都是二叉搜索树。
这个特性使得二叉搜索树能够高效地支持插入、删除和查找操作。
插入操作:
要在二叉搜索树中插入一个新节点,需要按照以下步骤进行:
- 如果树为空,则将新节点作为根节点。
- 如果树不为空,则从根节点开始比较新节点的值与当前节点的值。
- 如果新节点的值小于当前节点的值,继续在当前节点的左子树中插入新节点。
- 如果新节点的值大于当前节点的值,继续在当前节点的右子树中插入新节点。
- 重复步骤3和4,直到找到一个空位置插入新节点。
删除操作:
要在二叉搜索树中删除一个节点,需要按照以下步骤进行:
- 首先,找到要删除的节点。如果节点不存在,则删除操作失败。
- 如果要删除的节点没有子节点,直接删除即可。
- 如果要删除的节点只有一个子节点,将其子节点替代要删除的节点。
- 如果要删除的节点有两个子节点,需要找到其右子树中的最小节点(或左子树中的最大节点),将其值替换到要删除的节点上,然后删除这个最小(或最大)节点。
查找操作:
要在二叉搜索树中查找一个特定的值,需要按照以下步骤进行:
- 从根节点开始,将要查找的值与当前节点的值进行比较。
- 如果要查找的值等于当前节点的值,则找到了目标节点,返回该节点。
- 如果要查找的值小于当前节点的值,继续在当前节点的左子树中查找。
- 如果要查找的值大于当前节点的值,继续在当前节点的右子树中查找。
- 重复步骤3和4,直到找到目标节点或者遍历到叶子节点为止。
二叉搜索树的时间复杂度取决于树的平衡性。在最坏情况下,树可能变成链表,导致插入、删除和查找操作的时间复杂度为 O(n)。但是,如果树保持平衡,例如平衡二叉搜索树(AVL树、红黑树等),则这些操作的时间复杂度可以保持在 O(log n)。
以下是BST的代码实现:
定义BST的节点结构
BSTreeNode
,包含左子树指针_left
、右子树指针_right
和键值_key
。#include<iostream> using namespace std; template<class K> struct BSTreeNode { BSTreeNode<K>* _left; BSTreeNode<K>* _right; K _key; BSTreeNode(const K& key) :_left(nullptr) , _right(nullptr) , _key(key) { } };
定义BSTree类,其中的
Find
函数用于查找指定的键值。从根节点开始,根据当前节点的键值与目标键值的大小关系,沿着左子树或右子树逐步查找,直到找到目标键值或遍历完整个树。template<class K> class BSTree { typedef BSTreeNode<K> Node; public: bool Find(const K& key) { Node* cur = _root; while (cur) { if (cur->_key < key) { cur = cur->_right; } else if (cur->_key > key) { cur = cur->_left; } else return true; } return false; }
bool Erase(const K& key)
{
if (_root == nullptr)
return false;
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
//不能在这更新parent,否则删除节点时parent和cur指向相同
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else//删除操作
{
//只有一个孩子,托孤法
if (cur->_right == nullptr)//只有一个左孩子(+无孩子)
{
if (parent == nullptr)
{
_root = cur->_left;
delete cur;
return true;
}
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
}
else if (cur->_left == nullptr)//只有一个右孩子
{
if (parent == nullptr)
{
_root = cur->_right;
delete cur;
return true;
}
if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
}
else //左右都有孩子
{
//有两个孩子,替换删除法;左子树的最大(最右)节点或右子数的最小(最左)节点
//找到右子树的最左节点
Node* subLeft = cur->_right;
Node* subParent = cur;
while (subLeft->_left)
{
subParent = subLeft;
subLeft = subLeft->_left;
}
swap(subLeft->_key, cur->_key);
//经过交换后,现在只需删除subLeft,已经转化为只有一个孩子的情况,使用托孤法
if (subParent->_left == subLeft)
subParent->_left = subLeft->_right;
else
subParent->_right = subLeft->_right;
delete subLeft;
}
return true;
}
}
return false;
}
Erase
函数用于删除指定的键值对应的节点。删除操作分为四种情况:
- 节点只有一个左孩子(包括无孩子),使用"托孤法"将其父节点指向其左孩子。
- 节点只有一个右孩子,同样使用"托孤法"将其父节点指向其右孩子。
- 节点既有左孩子又有右孩子,采用"替换删除法",找到右子树的最左节点(或左子树的最右节点),将其键值与当前节点键值交换,然后删除这个最左节点。
- 无孩子可以随便合并到只有一个孩子的情况,代码是兼容的。
bool insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
else
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
//parent = cur; 虽然写这里也可以,但是写在里面结构更清晰
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
cur = new Node(key);
parent->_key > cur->_key ? parent->_left = cur : parent->_right = cur;
}
}
insert
函数用于插入一个新节点。若树为空,直接创建根节点;否则,从根节点开始,根据当前节点的键值与待插入键值的大小关系,沿着左子树或右子树逐步查找插入位置,直到找到一个空位置,然后创建新节点并插入。
void midOrder()
{
_midOrder(_root);
}
private:
Node * _root = nullptr;
void _midOrder(Node* node)
{
if (node == nullptr)
return;
_midOrder(node->_left);
cout << node->_key << " ";
_midOrder(node->_right);
}
};
midOrder
函数用于中序遍历树。由于根节点指针_root
是私有的,无法在类外直接使用,因此提供了一个公有的无参遍历函数,在类内部调用私有的递归遍历函数_midOrder
。
int main()
{
int a[] = { 8,3,1,6,4,7,14,13 };
BSTree<int> bst;
for (int x : a)
{
bst.insert(x);
}
//测试:遍历删除
for (int x : a)
{
bst.midOrder();
cout << endl;
bst.Erase(x);
bst.midOrder();
cout << endl;
cout << endl;
}
cout << "全部删除成功" << endl;
system("pause");
return 0;
}
在主函数中,首先创建了一个BSTree对象bst
,然后依次插入数组a
中的元素。接下来进行遍历删除的测试,每次删除一个元素后,都会输出当前树的中序遍历结果。最后输出"全部删除成功",并暂停程序的执行。
这段代码实现了BST的基本功能,包括查找、插入和删除操作,并且通过遍历删除的测试验证了代码的正确性。
完整代码如下:
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{ }
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
return true;
}
return false;
}
//删除节点,四种情况,可以合并为三种执行
bool Erase(const K& key)
{
if (_root == nullptr)
return false;
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
//不能在这更新parent,否则删除节点时parent和cur指向相同
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else//删除操作
{
//只有一个孩子,托孤法
if (cur->_right == nullptr)//只有一个左孩子(+无孩子)
{
if (parent == nullptr)
{
_root = cur->_left;
delete cur;
return true;
}
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
}
else if (cur->_left == nullptr)//只有一个右孩子
{
if (parent == nullptr)
{
_root = cur->_right;
delete cur;
return true;
}
if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
}
else //左右都有孩子
{
//有两个孩子,替换删除法;左子树的最大(最右)节点或右子数的最小(最左)节点
//找到右子树的最左节点
Node* subLeft = cur->_right;
Node* subParent = cur;
while (subLeft->_left)
{
subParent = subLeft;
subLeft = subLeft->_left;
}
swap(subLeft->_key, cur->_key);
//经过交换后,现在只需删除subLeft,已经转化为只有一个孩子的情况,使用托孤法
if (subParent->_left == subLeft)
subParent->_left = subLeft->_right;
else
subParent->_right = subLeft->_right;
delete subLeft;
}
return true;
}
}
return false;
}
//插入节点,要保证插入的值不同,一旦发现相同的,返回false
bool insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
else
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
//parent = cur; 虽然写这里也可以,但是写在里面结构更清晰
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
cur = new Node(key);
parent->_key > cur->_key ? parent->_left = cur : parent->_right = cur;
}
}
//遍历树需要传根节点指针,但是树的指针是私有的
//解决方法:套一层壳,给用户提供无参的遍历函数,在类内部调用传参遍历函数
void midOrder()
{
_midOrder(_root);
}
private:
Node * _root = nullptr;
void _midOrder(Node* node)
{
if (node == nullptr)
return;
_midOrder(node->_left);
cout << node->_key << " ";
_midOrder(node->_right);
}
};
int main()
{
int a[] = { 8,3,1,6,4,7,14,13 };
BSTree<int> bst;
for (int x : a)
{
bst.insert(x);
}
//测试:遍历删除
for (int x : a)
{
bst.midOrder();
cout << endl;
bst.Erase(x);
bst.midOrder();
cout << endl;
cout << endl;
}
cout << "全部删除成功" << endl;
system("pause");
return 0;
}