1. 二叉搜索树
1.1 二叉搜索树概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 3.它的左右子树也分别为二叉搜索树
1.2 二叉搜索树操作
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
- 二叉搜索树的查找
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。- 二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
// 插入节点
// 返回值是布尔型,来判断是否插入成功
// 满足如果key和节点数据相比,小于走左子树,大于走右子树,等于则不插入,返回false
// 而最后结束的时插入到叶子节点
bool Insert(const K& key)
{
//判断空树时的情况,直接开辟根节点
if (_root == nullptr)
{
// 开辟对象节点空间
_root = new Node(key);
return true;
}
// 寻找节点位置,从头结点位置开始寻找
Node* cur = _root;
// 记录cur的父亲节点
Node* parent = nullptr;
// 从头结点开始寻找插入的适当位置
// 搜索二叉树的原则是满足如果key和节点数据相比
// 小于走左子树,大于走右子树,等于则不插入,返回false
// 结束条件找到叶子节点的左子树或者右子树(nullptr)
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
parent = cur;
cur = cur->left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->right;
}
else
{
return false;
}
}
// 开辟节点空间插入
cur = new Node(key);
if (key < parent->_key)
{
parent->left = cur;
}
else
{
parent->right = cur;
}
return true;
}
- 二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面3种情况:
- 一.删除叶子节点(要删除的节点无孩子节点)
- 二.删除左子树或者右子树为空的节点(要删除的结点只有左孩子结点或者只有右孩子节点)
- 三.删除的节点左右子树都不为空(要删除的节点有左、右孩子节点)
首先找到要删除的元素
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
parent = cur;
cur = cur->left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->right;
}
else//相等的时候,找到了要删除的位置
{
...
}
之后,在else的情况中
实质上在删除的时候的情况:
- 一情况的处理可以与二情况合在一起:
- cur的左子树为空,如果cur在parent左子树,将cur的右子树给parent的左子树,否则cur在parent的右子树,则将cur的右子树付parent的右子树。
- cur的右子树为空,如果cur在parent得到左子树,将cur的左子树付给parent的左子树,否则cur在parent的右子树,则将cur的左子树赋给parent的右子树。
else中也要分为要删除节点的左孩子为空或右孩子为空的情况:
- a.cur的左孩子为空
(1).其中,也要判断是否是头节点,另外判断
(2).cur不是头节点的情况
之后判断cur是parent的哪个孩子
直接将cur的右孩子变为头节点,相当于删除10
根据上面的描述,代码如下
// 左孩子为空
if (cur->left == nullptr)
{
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
// 直接将cur的右孩子当作头节点
_root = cur->right;
}
else
{
// 判断cur是parent的哪个孩子
//cur是parent左孩子
if (cur == parent->left)
{
//cur的右子树赋给parent的左子树
parent->left = cur->right;
}
else// cur是parent右孩子时
{
//cur的右子树赋给parent的右子树
parent->right = cur->right;
}
}
// 删除节点,释放空间
delete cur;
}
- b.cur的左孩子为空的情况与a情况类似
(1).其中,也要判断是否是头节点,另外判断
(2).cur不是头节点的情况
之后判断cur是parent的哪个孩子
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
// 直接将cur的左孩子当作头节点
_root = cur->left;
}
else
{
// 2.不是头节点
// 判断cur是parent的哪个孩子
//cur是parent左孩子
if (cur == parent->left)
{
//cur的右子树赋给parent的左子树
parent->left = cur->left;
}
else// cur是parent右孩子时
{
//cur的右子树赋给parent的右子树
parent->right = cur->left;
}
}
// 删除节点,释放空间
delete cur;
- 2.三情况的解决方式:
删除cur,找一个节点来替换,替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换,直接删除,这种没有问题,在删除头节点会出现问题
所以要更改为交换之后,再要判断rightMin也要分为两种情况,rightMin在rightMinParent左孩子还是右孩子。
else//二.删除的节点左右子树都不为空
{
// 删除cur,找一个节点来替换
// 替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换
// 这里用查找右子树的最左节点
Node* rightMin = cur->right;
Node* rightMinParent = cur;
// 开始查找,结束条件左孩子为空,再去找自己,之后右子树
while (rightMin->left)
{
rightMinParent = rightMin;
rightMin = rightMin->left;
}
// 交换
// 数值交换
swap(cur->_key, rightMin->_key);
// rightMin也要分为两种情况
// 一种是rightMin在rightMinParent左孩子,也就是rightMin左孩子为空
if (rightMinParent->left == rightMin)
//将rightMin右孩子赋值给父亲节点的左子树
rightMinParent->left = rightMin->right;
else//另外一种是rightMin在rightMinParent右孩子
rightMinParent->right = rightMin->right;
delete rightMin;
}
return true;
}
完成的删除的代码如下:
// 删除:有着三种情况
// 三种情况:1.删除叶子节点 2.删除左子树或者右子树为空的节点 3.删除的节点左右子树都不为空
//一情况的处理可以与二情况合在一起:
//cur的左子树为空,如果cur在parent左子树,将cur的右子树给parent的左子树,否则cur在parent的右子树,则将cur的右子树付parent的右子树
//cur的右子树为空,如果cur在parent得到左子树,将cur的左子树付给parent的左子树,否则cur在parent的右子树,则将cur的左子树赋给parent的右子树
bool erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
// 首先找到需要删除的节点
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
parent = cur;
cur = cur->left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->right;
}
else//相等的时候,找到了要删除的位置
{
//综合结合为两种情况:
//一.删除的节点有单个左子树或者右子树为空,或者全为空
// 左孩子为空
if (cur->left == nullptr)
{
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
// 直接将cur的右孩子当作头节点
_root = cur->right;
}
else
{
// 判断cur是parent的哪个孩子
//cur是parent左孩子
if (cur == parent->left)
{
//cur的右子树赋给parent的左子树
parent->left = cur->right;
}
else// cur是parent右孩子时
{
//cur的右子树赋给parent的右子树
parent->right = cur->right;
}
}
// 删除节点,释放空间
delete cur;
}
else if (cur->right == nullptr)
{
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
// 直接将cur的左孩子当作头节点
_root = cur->left;
}
else
{
// 2.不是头节点
// 判断cur是parent的哪个孩子
//cur是parent左孩子
if (cur == parent->left)
{
//cur的右子树赋给parent的左子树
parent->left = cur->left;
}
else// cur是parent右孩子时
{
//cur的右子树赋给parent的右子树
parent->right = cur->left;
}
}
// 删除节点,释放空间
delete cur;
}
else//二.删除的节点左右子树都不为空
{
// 删除cur,找一个节点来替换
// 替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换
// 这里用查找右子树的最左节点
Node* rightMin = cur->right;
Node* rightMinParent = cur;
// 开始查找,结束条件左孩子为空,再去找自己,之后右子树
while (rightMin->left)
{
rightMinParent = rightMin;
rightMin = rightMin->left;
}
// 交换
// 数值交换
swap(cur->_key, rightMin->_key);
// rightMin也要分为两种情况
// 一种是rightMin在rightMinParent左孩子,也就是rightMin左孩子为空
if (rightMinParent->left == rightMin)
//将rightMin右孩子赋值给父亲节点的左子树
rightMinParent->left = rightMin->right;
else//另外一种是rightMin在rightMinParent右孩子
rightMinParent->right = rightMin->right;
delete rightMin;
}
return true;
}
}
return false;
}
find的查找代码:
// 查找
bool find(const K& key)
{
// 判断为空树时
if (_root == nullptr)
{
return false;
}
Node* cur = _root;
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
cur = cur->left;
}
else if (key > cur->_key)
{
cur = cur->right;
}
else
{
return true;
}
}
return false;
}
输出:中序遍历:
这种写法,类外无法访问类内私有成员
更改代码如下:
可进行无参的访问:private中定义有参的,就可以调用私有成员的_root,在类内的public中重载方法InOrder(),在方法内调用有参的。
void InOrder()
{
InOrder(_root);
cout << endl;
}
private:
void InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
InOrder(root->left);
cout << root->_key << " ";
InOrder(root->right);
}
Node* _root = nullptr;//对象指针
};
1.3 二叉搜索树的具体实现
1.3.1 K模型
#pragma once
#include<iostream>
using namespace std;
// K模型
namespace key
{
// 二叉搜索树的实现形式与list类似
// 先创建一个节点的类,类中有_key(节点的数据值)、*left(当前节点的左孩子地址)、*right(当前节点的右孩子地址)
//节点类
template <class K>
struct BSTreeNode
{
K _key;
BSTreeNode* left;
BSTreeNode* right;
//构造函数
BSTreeNode(const K& key)
:_key(key),
left(nullptr),
right(nullptr)
{}
};
// 之后用创建的的节点类,来构造二叉搜索树,每一个节点都是一个节点指针
// 二叉搜索树要保证,左孩子值小于父亲节点,右孩子节点大于父亲阶段,数据大小顺序(由小到大):左孩子,父亲,右孩子
// 默认定义搜索树不允许冗余
// 成员变量为节点指针
template<class K>
class BSTree
{
public:
// 重命名一下
typedef BSTreeNode<K> Node;
public:
// 构造函数
BSTree() :_root(nullptr)
{}
// 插入节点
// 返回值是布尔型,来判断是否插入成功
// 满足如果key和节点数据相比,小于走左子树,大于走右子树,等于则不插入,返回false
// 而最后结束的时插入到叶子节点
bool Insert(const K& key)
{
//判断空树时的情况,直接开辟根节点
if (_root == nullptr)
{
// 开辟对象节点空间
_root = new Node(key);
return true;
}
// 寻找节点位置,从头结点位置开始寻找
Node* cur = _root;
// 记录cur的父亲节点
Node* parent = nullptr;
// 从头结点开始寻找插入的适当位置
// 搜索二叉树的原则是满足如果key和节点数据相比
// 小于走左子树,大于走右子树,等于则不插入,返回false
// 结束条件找到叶子节点的左子树或者右子树(nullptr)
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
parent = cur;
cur = cur->left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->right;
}
else
{
return false;
}
}
// 开辟节点空间插入
cur = new Node(key);
if (key < parent->_key)
{
parent->left = cur;
}
else
{
parent->right = cur;
}
return true;
}
// 删除:有着三种情况
// 三种情况:1.删除叶子节点 2.删除左子树或者右子树为空的节点 3.删除的节点左右子树都不为空
//一情况的处理可以与二情况合在一起:
//cur的左子树为空,如果cur在parent左子树,将cur的右子树给parent的左子树,否则cur在parent的右子树,则将cur的右子树付parent的右子树
//cur的右子树为空,如果cur在parent得到左子树,将cur的左子树付给parent的左子树,否则cur在parent的右子树,则将cur的左子树赋给parent的右子树
bool erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
// 首先找到需要删除的节点
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
parent = cur;
cur = cur->left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->right;
}
else//相等的时候,找到了要删除的位置
{
//综合结合为两种情况:
//一.删除的节点有单个左子树或者右子树为空,或者全为空
// 左孩子为空
if (cur->left == nullptr)
{
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
// 直接将cur的右孩子当作头节点
_root = cur->right;
}
else
{
// 判断cur是parent的哪个孩子
//cur是parent左孩子
if (cur == parent->left)
{
//cur的右子树赋给parent的左子树
parent->left = cur->right;
}
else// cur是parent右孩子时
{
//cur的右子树赋给parent的右子树
parent->right = cur->right;
}
}
// 删除节点,释放空间
delete cur;
}
else if (cur->right == nullptr)
{
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
// 直接将cur的左孩子当作头节点
_root = cur->left;
}
else
{
// 2.不是头节点
// 判断cur是parent的哪个孩子
//cur是parent左孩子
if (cur == parent->left)
{
//cur的右子树赋给parent的左子树
parent->left = cur->left;
}
else// cur是parent右孩子时
{
//cur的右子树赋给parent的右子树
parent->right = cur->left;
}
}
// 删除节点,释放空间
delete cur;
}
else//二.删除的节点左右子树都不为空
{
// 删除cur,找一个节点来替换
// 替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换
// 这里用查找右子树的最左节点
Node* rightMin = cur->right;
Node* rightMinParent = cur;
// 开始查找,结束条件左孩子为空,再去找自己,之后右子树
while (rightMin->left)
{
rightMinParent = rightMin;
rightMin = rightMin->left;
}
// 交换
// 数值交换
swap(cur->_key, rightMin->_key);
// rightMin也要分为两种情况
// 一种是rightMin在rightMinParent左孩子,也就是rightMin左孩子为空
if (rightMinParent->left == rightMin)
//将rightMin右孩子赋值给父亲节点的左子树
rightMinParent->left = rightMin->right;
else//另外一种是rightMin在rightMinParent右孩子
rightMinParent->right = rightMin->right;
delete rightMin;
}
return true;
}
}
return false;
}
// 查找
bool find(const K& key)
{
// 判断为空树时
if (_root == nullptr)
{
return false;
}
Node* cur = _root;
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
cur = cur->left;
}
else if (key > cur->_key)
{
cur = cur->right;
}
else
{
return true;
}
}
return false;
}
// 中序输出(由小到大排序)
//类外不能访问私有成员 t1.InOrder(t1._root);
/*void InOrder(Node *root)
{
判断是否空树
if (root == nullptr)
{
return;
}
InOrder(root->left);
cout << root._key << " ";
InOrder(root->right);
}*/
void InOrder()
{
InOrder(_root);
cout << endl;
}
private:
void InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
InOrder(root->left);
cout << root->_key << " ";
InOrder(root->right);
}
Node* _root = nullptr;//对象指针
};
}
1.3.2 KV模型
#pragma once
#include<iostream>
//KV模型(key_value模型)
namespace key_value
{
//节点类
template <class K, class V>
struct BSTreeNode
{
K _key;
BSTreeNode<K, V>* left;
BSTreeNode<K, V>* right;
V _value;
//构造函数
BSTreeNode(const K& key, const V& value)
:_key(key),
left(nullptr),
right(nullptr),
_value(value)
{}
};
// 之后用创建的的节点类,来构造二叉搜索树,每一个节点都是一个节点指针
// 二叉搜索树要保证,左孩子值小于父亲节点,右孩子节点大于父亲阶段,数据大小顺序(由小到大):左孩子,父亲,右孩子
// 默认定义搜索树不允许冗余
// 成员变量为节点指针
template<class K,class V>
class BSTree
{
public:
// 重命名一下
typedef BSTreeNode<K,V> Node;
public:
// 构造函数
BSTree() :_root(nullptr)
{}
// 插入节点
// 返回值是布尔型,来判断是否插入成功
// 满足如果key和节点数据相比,小于走左子树,大于走右子树,等于则不插入,返回false
// 而最后结束的时插入到叶子节点
bool Insert(const K& key, const V& value)
{
//判断空树时的情况,直接开辟根节点
if (_root == nullptr)
{
// 开辟对象节点空间
_root = new Node(key, value);
return true;
}
// 寻找节点位置,从头结点位置开始寻找
Node* cur = _root;
// 记录cur的父亲节点
Node* parent = nullptr;
// 从头结点开始寻找插入的适当位置
// 搜索二叉树的原则是满足如果key和节点数据相比
// 小于走左子树,大于走右子树,等于则不插入,返回false
// 结束条件找到叶子节点的左子树或者右子树(nullptr)
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
parent = cur;
cur = cur->left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->right;
}
else
{
return false;
}
}
// 开辟节点空间插入
cur = new Node(key, value);
if (key < parent->_key)
{
parent->left = cur;
}
else
{
parent->right = cur;
}
return true;
}
// 删除:有着三种情况
// 三种情况:1.删除叶子节点 2.删除左子树或者右子树为空的节点 3.删除的节点左右子树都不为空
//一情况的处理可以与二情况合在一起:
//cur的左子树为空,如果cur在parent左子树,将cur的右子树给parent的左子树,否则cur在parent的右子树,则将cur的右子树付parent的右子树
//cur的右子树为空,如果cur在parent得到左子树,将cur的左子树付给parent的左子树,否则cur在parent的右子树,则将cur的左子树赋给parent的右子树
bool erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
// 首先找到需要删除的节点
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
parent = cur;
cur = cur->left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->right;
}
else//相等的时候,找到了要删除的位置
{
//综合结合为两种情况:
//一.删除的节点有单个左子树或者右子树为空,或者全为空
// 左孩子为空
if (cur->left == nullptr)
{
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
// 直接将cur的右孩子当作头节点
_root = cur->right;
}
else
{
// 判断cur是parent的哪个孩子
//cur是parent左孩子
if (cur == parent->left)
{
//cur的右子树赋给parent的左子树
parent->left = cur->right;
}
else// cur是parent右孩子时
{
//cur的右子树赋给parent的右子树
parent->right = cur->right;
}
}
// 删除节点,释放空间
delete cur;
}
else if (cur->right == nullptr)
{
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
// 直接将cur的左孩子当作头节点
_root = cur->left;
}
else
{
// 2.不是头节点
// 判断cur是parent的哪个孩子
//cur是parent左孩子
if (cur == parent->left)
{
//cur的右子树赋给parent的左子树
parent->left = cur->left;
}
else// cur是parent右孩子时
{
//cur的右子树赋给parent的右子树
parent->right = cur->left;
}
}
// 删除节点,释放空间
delete cur;
}
else//二.删除的节点左右子树都不为空
{
// 删除cur,找一个节点来替换
// 替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换
// 这里用查找右子树的最左节点
Node* rightMin = cur->right;
Node* rightMinParent = cur;
// 开始查找,结束条件左孩子为空,再去找自己,之后右子树
while (rightMin->left)
{
rightMinParent = rightMin;
rightMin = rightMin->left;
}
// 交换
// 数值交换
swap(cur->_key, rightMin->_key);
// rightMin也要分为两种情况
// 一种是rightMin在rightMinParent左孩子,也就是rightMin左孩子为空
if (rightMinParent->left == rightMin)
//将rightMin右孩子赋值给父亲节点的左子树
rightMinParent->left = rightMin->right;
else//另外一种是rightMin在rightMinParent右孩子
rightMinParent->right = rightMin->right;
delete rightMin;
}
return true;
}
}
return false;
}
// 查找
Node* find(const K& key)
{
Node* cur = _root;
while (cur)
{
//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
cur = cur->left;
}
else if (key > cur->_key)
{
cur = cur->right;
}
else
{
// 找到了返回节点
return cur;
}
}
//没找到,返回节点,此时节点为空
return cur;
}
// 中序输出(由小到大排序)
// 类外不能访问私有成员 t1.InOrder(t1._root);
//void InOrder(Node *root)
//{
// // 判断是否空树
// if (root == nullptr)
// {
// return;
// }
// InOrder(root->left);
// cout << root._key << " ";
// InOrder(root->right);
//}
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);
}
Node* _root = nullptr;//对象指针
};
void TestBSTree2()
{
BSTree<string, string> dict;
dict.Insert("string", "字符串");
dict.Insert("left", "左边");
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 TestBSTree3()
{
// 统计次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
BSTree<string, int> countTree;
for (const auto& str : arr)
{
auto ret = countTree.find(str);
if (ret == nullptr)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}
}
1.4 二叉搜索树的应用
- K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。- KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
该种方式在现实生活中非常常见:
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
1.5 二叉搜索树的性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
- 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
- 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上场了。