数据结构进阶——二叉搜索树
- 1. 二叉搜索树的概念和定义
- 2. 二叉搜索树的操作
- 2.1 二叉搜索树的插入
- 2.2 二叉搜索树的查找
- 2.3 二叉搜索树的删除
- 3. 二叉搜索树的实际应用
- 3.1 两种模型加二叉搜索树完整代码
- 3.2 KV模型练习
- 4. 二叉搜索树性能分析
1. 二叉搜索树的概念和定义
1. 二叉搜索树又称二叉排序树,它可以是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值;
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值;
- 它的左右子树也分别为二叉搜索树。
2. 二叉搜索树的定义也非常简单,就是普通的二叉树定义方法:
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:
// ... 相关接口实现
protected:
Node* _root = nullptr;
};
- 节点的定义中提供了一个构造函数,方便在后续实现相关接口的时候使用。
2. 二叉搜索树的操作
2.1 二叉搜索树的插入
1. 非递归写法:
- 思路很简单,就是拿要插入的值从根节点开始比较,如果比根节点的值大就继续跟右子树比较;如果比根节点值小就继续跟左节点比较,直到找到空节点,在空节点的位置插入即可。
- 如果根节点就是空节点,说明当前的树是空树,直接在根节点插入即可。
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) // 直到当前节点为空,说明要在此插入
{
parent = cur;
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
// 默认搜索二叉树中,不能出现重复元素
return false;
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
protected:
Node* _root = nullptr;
};
cur
指针指向当前节点,parent
指针指向当前节点的父节点。这里要注意,找到插入位置后,直接把新节点赋值给cur
是不能完成插入的,因为单单这样只是让cur
指向了新节点,无法将新节点和这棵树链接起来。必须借助parent
指针,记录要插入位置的父节点,然后让parent
的孩子指针指向新节点。
2. 递归写法:
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
// 递归插入
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)
{
return _InsertR(_root, key);
}
protected:
Node* _root = nullptr;
};
- C++中,经常使用一个
_
开头的内部函数,和不带_
的外部函数来设计递归。因为如果我们直接在外部使用_InsertR
,我们会找不到根节点。解决方案就是设计一个InsertR
,让InsertR
去调用_InsertR
,这样就可以找到根节点了。 - 这里我用一张图来解释引用的巧妙之处:
- 假设我们要在这棵树中插入一个11,调用
_InsertR
函数接口,root
是引用,所以root
就是_root
。发现11比10大,递归调用右子树插入。此时将root->_right
传给了下一个递归函数的root
,也是传引用,所以此时的root
就是根节点的_right
,发现root
为空,直接创建新节点,然后赋值给root
,完成链接。
3. 中序遍历,验证是否成功建树:
- 不难发现,如果对一颗搜索树进行中序遍历,那么得到的将是一个升序序列,这个规律可以直接拿来使用,亦可以用来验证搜索树是否正确建立。
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
// 中序遍历
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
}
protected:
Node* _root = nullptr;
};
2.2 二叉搜索树的查找
1. 思路:
- 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找;
- 最多查找高度次;
- 走到到空,还没找到,这个值不存在。
2. 非递归实现(循环实现):
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; // 找不到,返回假
}
protected:
Node* _root = nullptr;
};
3. 递归实现:
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
// 递归查找
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)
{
return _FindR(_root, key);
}
protected:
Node* _root = nullptr;
};
2.3 二叉搜索树的删除
// 用下面数组中的数依次插入,即可得到上图的树
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
1. 思路:
-
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
- a.要删除的结点无孩子结点(如1,4,7,13这种叶子结点);
- b.要删除的结点只有左孩子结点(如14);
- c.要删除的结点只有右孩子结点(如10);
- d.要删除的结点有左、右孩子结点(如8,3,6)。
-
看起来有待删除节点有4中情况,实际a情况可以与b或c情况合并起来,因此真正的删除过程如下:
- 情况b:删除该结点,且使被删除节点的双亲结点指向被删除节点的左孩子结点——直接删除;
- 情况c:删除该结点,且使被删除节点的双亲结点指向被删除结点的右孩子结点——直接删除;
- 情况d:在它的右子树中寻找最左节点(最小节点,中序遍历的第一个节点),用它的值填补到要被删除节点中,再删除最左节点;或在它的左子树,寻找最右节点(最大节点),用它的值填补到要被删除节点中,再删除最右节点——替换法删除。
2. 非递归实现:
- 情况a可以归类到情况b和c中,因为左右节点都为空,是左右节点其中有一个为空的子集条件;
- 最左节点不一定是
parent
的左孩子,例如删除节点8,右子树的最左节点是10,是8这个parent
的右节点。
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
// 删除
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
{
// 找到了,进行删除操作
if (cur->_left == nullptr) // 左节点为空的情况
{
if (cur == _root)
{
// 若要删除的节点是根节点,要特殊处理,直接把根给右子树即可
// 因为此时parent==nullptr,不单独处理会发生空指针的解引用
_root = cur->_right;
delete cur;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
delete cur;
}
else
{
parent->_right = cur->_right;
delete cur;
}
}
}
else if (cur->_right == nullptr) // 右节点为空的情况
{
if (cur == _root)
{
// 还是根节点特殊处理
_root = cur->_left;
delete cur;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
delete cur;
}
else
{
parent->_right = cur->_left;
delete cur;
}
}
}
else
{
// 左右节点都不为空
Node* parent = cur; // 这里不要置空,置空在while循环进不去时,会在判断是parent左孩子还是右孩子时解引用空指针
Node* subLeft = cur->_right; // 右树的最小节点(最左节点)
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
// 判断最左节点是parent的左节点还是右节点
if (parent->_left == subLeft)
{
parent->_left = subLeft->_right;
delete subLeft;
}
else
{
parent->_right = subLeft->_right;
delete subLeft;
}
}
return true;
}
}
return false;
}
protected:
Node* _root = nullptr;
};
3. 递归实现:
- 刚开始还是找点,如果
key < root->_key
,就递归删除左子树;如果key > root->_key
就递归删除右子树; - 找到了要删除的点,开始分情况处理:
- b,c情况直接删除;
- d情况要先找到右子树的最左节点,然后交换最左节点和要删除节点的
_key
。要注意,交换后,以root
位置(刚才要删除节点的位置)为根节点的右子树仍然是一颗搜索二叉树(这是一个规律,也很好推,可以直接拿来用),所以可以转换成子问题,递归走右子树的删除。
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
// 递归删除
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
{
// 找到了,开始删除
if (root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete del;
return true;
}
else if (root->_right == nullptr)
{
Node* del = root;
root = root->_left;
delete del;
return true;
}
else
{
// 左右节点都不为空
Node* subLeft = root->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
swap(subLeft->_key, root->_key);
// 转换成在右子树去递归删除
return _EraseR(root->_right, key);
}
}
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
protected:
Node* _root = nullptr;
};
4. 测试:
void Test()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BSTree<int> bt;
for (auto e : a)
{
bt.InsertR(e);
}
bt.InOrder();
cout << endl;
// 测试普通删除
for (auto e : a)
{
bt.Erase(e);
}
for (auto e : a)
{
bt.Insert(e);
}
bt.InOrder();
cout << endl;
// 测试递归删除
for (auto e : a)
{
bt.EraseR(e);
}
}
这段代码如果能跑通,说明基本没有问题,因为这里一旦报错大概率就是崩溃。
3. 二叉搜索树的实际应用
3.1 两种模型加二叉搜索树完整代码
1. K模型:K模型即只有Key
作为关键码,结构中只需要存储Key
即可,关键码即为需要搜索到的值。
- 比如:给一个单词
word
,判断该单词是否拼写正确,具体方式如下:- 以词库中所有单词集合中的每个单词作为
key
,构建一棵二叉搜索树; - 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
- 以词库中所有单词集合中的每个单词作为
- 1,2节中实现的就是K模型二叉搜索树。
2. KV模型:每一个关键码key
,都有与之对应的值Value
,即<Key, Value>
的键值对。
- 该种方式在现实生活中非常常见:
- 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文
<word, chinese>
就构成一种键值对; - 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是
<word, count>
就构成一种键值对。
- 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文
3. 两棵树的完整代码如下:
key_value
相比key
模型,更改了一些细节。- 如二叉树节点的设计,多了一个
_value
; - 查找函数中,如果找到了对应节点,就返回该节点,找不到返回空指针;
- 还对中序遍历做了一些修改,使其可以同时打印键和值。
- 如二叉树节点的设计,多了一个
// key模型
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:
// 默认构造
BSTree() = default; // C++11后新增的语法
// 或
/*BSTree()
{}*/
// 循环插入
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root; // 这里不能用引用
while (cur) // 直到当前节点为空,说明要在此插入
{
parent = cur;
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
// 默认搜索二叉树中,不能出现重复元素
return false;
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
// 中序遍历
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
}
// 查找
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 _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)
{
return _FindR(_root, key);
}
// 递归插入
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)
{
return _InsertR(_root, key);
}
// 删除
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
{
// 找到了,进行删除操作
if (cur->_left == nullptr) // 左节点为空的情况
{
if (cur == _root)
{
// 若要删除的节点是根节点,要特殊处理,直接把根给右子树即可
// 因为此时parent==nullptr,不单独处理会发生空指针的解引用
_root = cur->_right;
delete cur;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
delete cur;
}
else
{
parent->_right = cur->_right;
delete cur;
}
}
}
else if (cur->_right == nullptr) // 右节点为空的情况
{
if (cur == _root)
{
// 还是根节点特殊处理
_root = cur->_left;
delete cur;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
delete cur;
}
else
{
parent->_right = cur->_left;
delete cur;
}
}
}
else
{
// 左右节点都不为空
Node* parent = cur; // 这里不要置空,置空在while循环进不去时,会在判断是parent左孩子还是右孩子时解引用空指针
Node* subLeft = cur->_right; // 右树的最小节点(最左节点)
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
if (parent->_left == subLeft)
{
parent->_left = subLeft->_right;
delete subLeft;
}
else
{
parent->_right = subLeft->_right;
delete subLeft;
}
}
return true;
}
}
return false;
}
// 递归删除
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
{
if (root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete del;
return true;
}
else if (root->_right == nullptr)
{
Node* del = root;
root = root->_left;
delete del;
return true;
}
else
{
// 左右节点都不为空
Node* subLeft = root->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
swap(subLeft->_key, root->_key);
// 转换成在右子树去递归删除
return _EraseR(root->_right, key);
}
}
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
// 后序遍历,销毁树
void Destroy(Node*& root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
// 析构
~BSTree()
{
Destroy(_root);
}
// 拷贝构造函数
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root);
}
// 先序拷贝
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
protected:
Node* _root = nullptr;
};
}
/*--------------------------------------------------------------------------*/
// key_value模型
namespace key_value
{
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:
// 默认构造
BSTree() = default; // C++11后新增的语法
// 或
/*
BSTree()
{}
*/
// 循环插入
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) // 直到当前节点为空,说明要在此插入
{
parent = cur;
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
// 默认搜索二叉树中,不能出现重复元素
return false;
}
}
cur = new Node(key, value);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
// 中序遍历
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
}
// 查找
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; // 找不到,返回空指针
}
// 递归查找
Node* _FindR(Node* root, const K& key)
{
// 走到了空,说明没找到
if (root == nullptr)
{
return nullptr;
}
if (root->_key < key)
return _FindR(root->_right, key);
else if (root->_key > key)
return _FindR(root->_left, key);
else
return root; // 找到了,返回该节点
}
Node* FindR(const K& key)
{
return _FindR(_root, key);
}
// 递归插入
bool _InsertR(Node*& root, const K& key, const V& value) // 这里引用设计的非常巧妙
{
if (root == nullptr)
{
root = new Node(key, value);
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)
{
return _InsertR(_root, key);
}
// 删除
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
{
// 找到了,进行删除操作
if (cur->_left == nullptr) // 左节点为空的情况
{
if (cur == _root)
{
// 若要删除的节点是根节点,要特殊处理,直接把根给右子树即可
// 因为此时parent==nullptr,不单独处理会发生空指针的解引用
_root = cur->_right;
delete cur;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
delete cur;
}
else
{
parent->_right = cur->_right;
delete cur;
}
}
}
else if (cur->_right == nullptr) // 右节点为空的情况
{
if (cur == _root)
{
// 还是根节点特殊处理
_root = cur->_left;
delete cur;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
delete cur;
}
else
{
parent->_right = cur->_left;
delete cur;
}
}
}
else
{
// 左右节点都不为空
Node* parent = cur; // 这里不要置空,置空在while循环进不去时,会在判断是parent左孩子还是右孩子时解引用空指针
Node* subLeft = cur->_right; // 右树的最小节点(最左节点)
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
if (parent->_left == subLeft)
{
parent->_left = subLeft->_right;
delete subLeft;
}
else
{
parent->_right = subLeft->_right;
delete subLeft;
}
}
return true;
}
}
return false;
}
// 递归删除
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
{
if (root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete del;
return true;
}
else if (root->_right == nullptr)
{
Node* del = root;
root = root->_left;
delete del;
return true;
}
else
{
// 左右节点都不为空
Node* subLeft = root->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
swap(subLeft->_key, root->_key);
// 转换成在右子树去递归删除
return _EraseR(root->_right, key);
}
}
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
// 后序遍历,销毁树
void Destroy(Node*& root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
// 析构
~BSTree()
{
Destroy(_root);
}
// 拷贝构造函数
BSTree(const BSTree<K, V>& t)
{
_root = Copy(t._root);
}
// 先序拷贝
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key, root->_value);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
BSTree<K, V>& operator=(BSTree<K, V> t)
{
swap(_root, t._root);
return *this;
}
protected:
Node* _root = nullptr;
};
}
3.2 KV模型练习
1. 输入单词,查找对应的中文翻译:
void findWord()
{
// 输入单词,查找单词对应的中文翻译
key_value::BSTree<string, string> dict;
dict.Insert("string", "字符串");
dict.Insert("tree", "树");
dict.Insert("left", "左边、剩余");
dict.Insert("right", "右边");
dict.Insert("sort", "排序");
dict.InOrder();
// 插入词库中所有单词
string str;
while (cin >> str)
{
key_value::BSTreeNode<string, string>* ret = dict.Find(str);
if (ret == nullptr)
{
cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
}
else
{
cout << str << "中文翻译:" << ret->_value << endl;
}
}
}
2. 统计水果出现的次数:
void fruitCount()
{
// 统计水果出现的次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
key_value::BSTree<string, int> countTree;
for (auto e : arr)
{
key_value::BSTreeNode<string, int>* ret = countTree.Find(e);
if (ret == nullptr)
{
countTree.Insert(e, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}
4. 二叉搜索树性能分析
1. 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
-
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
-
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
-
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
-
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N
2. 问题:
- 如果退化成单支树,二叉搜索树的性能会非常低效。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?
- 我们后面还会学习AVL树和红黑树,他们就是性能优秀的搜索二叉树。