二叉搜索树(Binary Search Tree,BST)是一种二叉树的特殊形式,它具有以下关键性质:
BST
中不允许包含重复的值。每个值在树中只能出现一次。由于这些性质,二叉搜索树在很多应用中都非常有用,尤其是在需要高效地进行搜索、插入和删除操作的情况下。
以下是一些关于二叉搜索树的详细解释:
O(n)
,其中n
是节点数。为了避免这种情况,通常使用平衡二叉搜索树(如AVL树或红黑树)来确保树的高度保持在较小的范围内。总之,二叉搜索树是一种基于节点值大小有序排列的二叉树,它支持高效的搜索、插入和删除操作,但需要注意平衡性问题以避免性能退化。
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
在二叉查找树b中查找x的过程为:
向一个二叉搜索树b中插入一个节点s的算法,过程为:
s->data
等于b的根节点的数据域之值,则返回,否则:s->data
小于b的根节点的数据域之值,则把s所指节点插入到左子树中,否则:首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除
二叉搜索树的遍历最常用的是中序遍历,因为二叉搜索树(BST)的中序遍历结果是升序的。中序遍历是一种遍历二叉树的方式,按照以下规则进行:
- 首先遍历左子树。
- 然后遍历当前节点。
- 最后遍历右子树。
由于BST的性质,即左子树的值都小于当前节点的值,右子树的值都大于当前节点的值,所以中序遍历的过程中,首先访问左子树,然后访问当前节点,最后访问右子树。这导致中序遍历结果是按照升序排列的。
因此,中序遍历是一种有助于获取BST中所有节点按升序排列的有效方法。这个性质也是BST在进行搜索操作时能够快速定位目标值的关键之一。
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
_left
:指向左子节点的指针。_right
:指向右子节点的指针。_key
:存储节点的关键字或值。
每个节点都有一个关键字 _key
,用来表示节点在二叉搜索树中的位置。这个关键字通常是用来比较节点的大小,以便将节点插入到正确的位置,以满足二叉搜索树的性质。根据节点关键字的大小,节点将被放置在左子树或右子树中。
这是一个模板类,它可以用于创建不同类型的二叉搜索树,只需指定不同的关键字类型 K
即可。这使得代码更加通用,可以适用于不同类型的数据。
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
private:
Node* _root = nullptr;
}
二叉搜索树(Binary Search Tree,BST)类模板 BSTree
的定义。这个类包含一个私有成员 _root
,它是指向树的根节点的指针。
template
:这是一个类模板声明,使用了模板参数 K
,它表示关键字或值的数据类型。这允许你在创建 BSTree
实例时指定不同的数据类型。typedef BSTreeNode Node;
:这行代码用于定义一个别名 Node
,它是 BSTreeNode
类的别名。这个别名使得在类中使用节点类型更加方便。Node* _root = nullptr;
:这是一个指向根节点的指针 _root
,它初始化为 nullptr
,表示树开始为空树。构造函数
BSTree() = default;
BSTree() = default;
是C++中的默认构造函数的声明方式。这行代码表示你正在声明一个默认构造函数(无参数的构造函数),并使用 = default
表示使用默认的构造函数实现。
默认构造函数是一个特殊的构造函数,它在对象创建时被自动调用,不接受任何参数。默认构造函数的主要作用是初始化对象的成员变量,确保对象在创建后处于一个合法的初始状态。
使用 = default
表示你希望编译器生成默认的构造函数实现,而不需要自己编写构造函数的定义。这通常用于以下情况:
这里需要注意的是在C++中,如果你提供了自定义的拷贝构造函数,编译器通常不会再自动生成默认的拷贝构造函数,所以我们需要再将默认构造自定义提供
析构函数
~BSTree()
{
_Destory(_root);
}
private:
void _Destory(Node*& root)
{
if (root == nullptr)
{
return;
}
_Destory(root->_left);
_Destory(root->_right);
delete root;
root = nullptr;
}
~BSTree()
_Destory(_root)
,传递根节点 _root
作为参数,开始递归地销毁整棵树。_Destory(Node*& root)
nullptr
,以避免悬挂指针问题。nullptr
,以确保不会再访问已释放的节点。这样,当你销毁BST对象时,析构函数会从根节点开始递归地销毁整棵树,确保释放所有节点的内存,防止内存泄漏。这是一种正确释放资源的方式,以确保程序的健壮性。
拷贝构造
BSTree(const BSTree<K>& t)
{
_root = _Copy(t._root);
}
private:
Node* _Copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* copyRoot = new Node(root->_key);
copyRoot->_left = _Copy(root->_left);
copyRoot->_right = _Copy(root->_right);
return copyRoot;
}
BSTree(const BSTree& t)
t
进行拷贝构造的。_Copy(t._root)
,传递 t
的根节点 _root
作为参数,从而创建了一个新的BST。_Copy(Node* root)
root
为空(nullptr
),则返回 nullptr
,表示空树。copyRoot
,并将其关键字设置为 root
节点的关键字。copyRoot->_left
和 copyRoot->_right
。copyRoot
,这样递归过程会构建出一棵与原始树相同结构和内容的树。这样,拷贝构造函数 _Copy
会递归地复制整棵树,确保你创建的新BST对象与原始对象具有相同的结构和内容。这对于在不破坏原始树的情况下创建一个新树非常有用。
迭代写法
public:
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
_root
是否为 nullptr
)。如果树为空,它会创建一个新的根节点,该节点的关键字为 key
,然后返回 true
表示插入成功。key
,则继续向右子树移动,将 parent
设置为当前节点,以便稍后插入新节点到右子树。key
,则继续向左子树移动,同样将 parent
设置为当前节点,以便稍后插入新节点到左子树。key
,则返回 false
,表示不允许重复关键字的节点插入。cur
,该节点的关键字为 key
。parent
节点的关键字与 key
的比较结果,将新节点 cur
插入到正确的位置。如果 parent->_key < key
,则将 cur
插入为 parent
的右子节点,否则插入为 parent
的左子节点。true
,表示插入成功。这个 Insert
函数实现了BST的插入操作,它确保新节点按照BST的有序性质被插入到正确的位置。如果关键字已经存在于树中,函数会返回 false
,表示插入失败(因为BST不允许重复关键字)。
递归写法
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
private:
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (root->_key < key)
return _InsertR(root->_right, key);
else if (root->_key > key)
return _InsertR(root->_left, key);
else
return false;
}
bool InsertR(const K& key)
_InsertR
,并将根节点 _root
和要插入的关键字 key
作为参数传递给它。bool _InsertR(Node*& root, const K& key)
root
是否为空。如果为空,表示找到了插入位置,它会创建一个新节点并将其关键字设置为 key
,然后将 root
指向新节点,最后返回 true
表示插入成功。root
不为空,它会比较当前节点的关键字 root->_key
与要插入的关键字 key
:
root->_key < key
,则递归地在右子树中插入新节点,调用 _InsertR(root->_right, key)
。root->_key > key
,则递归地在左子树中插入新节点,调用 _InsertR(root->_left, key)
。root->_key == key
,表示已经存在相同关键字的节点,直接返回 false
表示插入失败。迭代写法
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 Find(const K& key)
key
。cur
为根节点 _root
,然后进入一个循环。key
的比较结果来决定向左子树还是右子树移动:
key
,则说明要查找的关键字应该在当前节点的右子树中,所以将 cur
指向右子节点 cur->_right
。key
,则说明要查找的关键字应该在当前节点的左子树中,所以将 cur
指向左子节点 cur->_left
。key
,则表示找到了目标关键字,函数返回 true
表示查找成功。cur
指向空(nullptr
),这意味着整棵树都被搜索过了,但没有找到目标关键字,所以函数返回 false
表示查找失败。递归写法
bool FindR(const K& key)
{
return _FindR(_root, key);
}
private:
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
return _FindR(root->_right, key);
else if (root->_key > key)
return _FindR(root->_left, key);
else
return true;
}
bool FindR(const K& key)
key
是否存在。它首先调用私有的递归函数 _FindR
,并将根节点 _root
和要查找的关键字 key
作为参数传递给它。bool _FindR(Node* root, const K& key)
key
是否存在。root
是否为空(nullptr
)。如果为空,表示已经搜索到了树的底部,关键字 key
不存在于树中,所以返回 false
表示查找失败。root
不为空,函数会比较当前节点的关键字 root->_key
与要查找的关键字 key
:
root->_key < key
,则说明要查找的关键字应该在当前节点的右子树中,所以递归调用 _FindR(root->_right, key)
在右子树中查找。root->_key > key
,则说明要查找的关键字应该在当前节点的左子树中,所以递归调用 _FindR(root->_left, key)
在左子树中查找。root->_key == key
,表示找到了目标关键字,函数返回 true
表示查找成功。这个递归查找操作会在树的节点之间不断递归地进行,直到找到目标关键字或确认目标不存在。如果目标关键字存在于树中,函数返回 true
;否则,返回 false
。
迭代写法
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
// 1、左为空
// 2、右为空
// 3、左右都不为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
cur = nullptr;
}
else if (cur->_right == nullptr)
{
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
else
{
// 找到右子树最小节点进行替换
Node* minParent = cur;
Node* min = cur->_right;
while (min->_left)
{
minParent = min;
min = min->_left;
}
swap(cur->_key, min->_key);
if (minParent->_left == min)
minParent->_left = min->_right;
else
minParent->_right = min->_right;
delete min;
}
return true;
}
}
return false;
}
parent
和 cur
分别为 nullptr
和根节点 _root
,然后进入一个循环。这个循环用于在BST中找到待删除节点,并进行删除操作。key
的比较结果来决定向左子树还是右子树移动,同时更新 parent
和 cur
指针。key
,表示要开始删除操作。删除操作分为以下几种情况:
true
表示删除成功。key
,说明关键字不存在于树中,函数返回 false
表示删除失败。递归写法
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
private:
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
return _EraseR(root->_right, key);
else if (root->_key > key)
return _EraseR(root->_left, key);
else
{
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
// 找右树的最左节点替换删除
Node* min = root->_right;
while (min->_left)
{
min = min->_left;
}
swap(root->_key, min->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
bool EraseR(const K& key)
key
的节点。它首先调用私有的递归函数 _EraseR
,并将根节点 _root
和要删除的关键字 key
作为参数传递给它。bool _EraseR(Node*& root, const K& key)
key
的节点。root
是否为空(nullptr
)。如果为空,表示已经搜索到了树的底部,关键字 key
不存在于树中,所以返回 false
表示删除失败。root
不为空,函数会比较当前节点的关键字 root->_key
与要删除的关键字 key
:
root->_key < key
,则说明要删除的关键字应该在当前节点的右子树中,所以递归调用 _EraseR(root->_right, key)
在右子树中进行删除操作。root->_key > key
,则说明要删除的关键字应该在当前节点的左子树中,所以递归调用 _EraseR(root->_left, key)
在左子树中进行删除操作。root->_key == key
,表示找到了要删除的目标节点,此时需要执行删除操作。true
表示删除成功。key
,说明关键字不存在于树中,函数返回 false
表示删除失败。void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void InOrder()
_InOrder(_root)
,并传递根节点 _root
作为参数,开始进行中序遍历。void _InOrder(Node* root)
root
是否为空。如果为空,表示到达树的底部,直接返回,不执行任何操作。_InOrder(root->_left)
,遍历左子树。root->_key
,通常是将节点值打印到控制台。_InOrder(root->_right)
,遍历右子树。这个中序遍历函数会遍历整个二叉搜索树,并按照从小到大的顺序输出节点的关键字值。这是一种常用的方式来访问二叉搜索树的所有节点,并按照升序排列输出它们的值。
namespace Key
{
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 Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
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)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
// 1、左为空
// 2、右为空
// 3、左右都不为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
cur = nullptr;
}
else if (cur->_right == nullptr)
{
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
else
{
// 找到右子树最小节点进行替换
Node* minParent = cur;
Node* min = cur->_right;
while (min->_left)
{
minParent = min;
min = min->_left;
}
swap(cur->_key, min->_key);
if (minParent->_left == min)
minParent->_left = min->_right;
else
minParent->_right = min->_right;
delete min;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
~BSTree()
{
_Destory(_root);
}
BSTree() = default;
BSTree(const BSTree<K>& t)
{
_root = _Copy(t._root);
}
// t2 = t1
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
private:
Node* _Copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* copyRoot = new Node(root->_key);
copyRoot->_left = _Copy(root->_left);
copyRoot->_right = _Copy(root->_right);
return copyRoot;
}
void _Destory(Node*& root)
{
if (root == nullptr)
{
return;
}
_Destory(root->_left);
_Destory(root->_right);
delete root;
root = nullptr;
}
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
return _EraseR(root->_right, key);
else if (root->_key > key)
return _EraseR(root->_left, key);
else
{
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
// 找右树的最左节点替换删除
Node* min = root->_right;
while (min->_left)
{
min = min->_left;
}
swap(root->_key, min->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (root->_key < key)
return _InsertR(root->_right, key);
else if (root->_key > key)
return _InsertR(root->_left, key);
else
return false;
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key < key)
return _FindR(root->_right, key);
else if (root->_key > key)
return _FindR(root->_left, key);
else
return true;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
}
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误
KV模型:每一个关键码key,都有与之对应的值Value,即
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文
就构成一种键值对
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是
就构成一种键值对
例如:
namespace KeyValue
{
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _value;
BSTreeNode(const K& key, const V& value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
, _value(value)
{}
};
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
bool Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key, value);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
Node* 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 cur;
}
}
return nullptr;
}
bool Erase(const K& key)
{
//...
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
void TestBSTree1()
{
BSTree<string, string> dict;
dict.Insert("sort", "排序");
dict.Insert("left", "左");
dict.Insert("right", "右");
dict.Insert("vector", "向量");
dict.Insert("insert", "插入");
string str;
while (cin >> str)
{
BSTreeNode<string, string>* ret = dict.Find(str);
if (ret)
{
cout << "对应的中文:" << ret->_value << endl;
}
else
{
cout << "对应的中文->无此单词" << endl;
}
}
}
void TestBSTree2()
{
string arr[] = {"苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉"};
BSTree<string, int> countTree;
for (auto& str : arr)
{
auto ret = countTree.Find(str);
if (ret)
{
ret->_value++;
}
else
{
countTree.Insert(str, 1);
}
}
countTree.InOrder();
}
}
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log_2 N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:frac{N}{2}
优点:
缺点:
总结来说,BST在平均情况下具有较好的性能,特别适用于有序数据的查找和动态数据的插入/删除操作。然而,在最坏情况下,BST的性能可能变得不稳定,因此为了确保性能稳定,可以使用平衡二叉搜索树或其他更复杂的数据结构。选择适当的数据结构取决于应用的需求和数据的特点。