文章目录
二叉搜索树
是什么:
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
例子:

对二叉搜索树的操作:
节点定义
//K -> key
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
//构造函数
BSTreeNode(const K& key)
:_key(key)
,_left(nullptr)
,_right(nullptr)
{}
};
二叉搜索树的框架
template<class K>
struct BSTree
{
typedef BSTreeNode<K> Node; //将二叉树节点的类型重命名为Node,方便使用
BSTree()
:_root(nullptr)
{}
private:
Node* _root;
};
查找
查找一个值,可以返回bool值 ,也可也返回节点的指针
非递归查找
根据:左节点的值<根节点的值<右节点的值 ,对应去左树和右树中寻找
//查找返回bool值
bool Find(const K& key)
{
//空树不用查找
if(_root == nullptr)
{
return false;
}
//左 < 根 < 右
Node* cur = _root;//从根节点位置开始往下找
//结束条件:cur走到空 || 提前找到返回
while (cur)
{
if (cur->_key < key)
{
//去右树找
cur = cur->_right;
}
else if (cur->_key > key)
{
//去左树找
cur = cur->_left;
}
else
{
//找到了
return true;
}
}
//找不到
return false;
}
返回节点指针
Node* Find(const K& key)
{
//空树不用查找
if(_root == nullptr)
{
return nullptr;
}
//左 < 根 < 右
Node* cur = _root;//从根节点位置开始往下找
//结束条件:cur走到空 || 提前找到返回
while (cur)
{
if (cur->_key < key)
{
//去右树找
cur = cur->_right;
}
else if (cur->_key > key)
{
//去左树找
cur = cur->_left;
}
else
{
//找到了
return cur;
}
}
//找不到
return nullptr;
}
递归查找
根据二叉搜索树的性质进行查找即可
//递归查找,public函数:给外界提供一个接口查找谁
Node* FindR(const K& key)
{
return _FindR(_root, key);
}
private:
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return root;//空树直接返回空
}
//如果要找的值比根节点的值大 -> 去右树找
//如果要找的值比根节点的值小 -> 去左树找
if (root->_key < key)
{
_FindR(root->_right, key);//递归去右树找
}
else if(root->_key > key)
{
_FindR(root->_left, key);//递归去左树找
}
else
{
return root;//找到了
}
}
删除
非递归删除
case1:删除的是没有孩子的节点
case2:删除的是只有一个孩子的节点
本质是case1和case2可以划分为同一类型的情况 没有孩子可以认为只有一个孩子的情况
case3:删除的是有两个孩子的节点
替换删除法-和左树的最右节点||右树的最左节点
这里选择和右树的最左节点交换:(只需值替换即可)
注意点:
bool Erase(const K& key)
{
//空树
if (_root == nullptr)
{
return false;
}
//从根位置开始遍历找到删除位置,要保存其父亲的位置
Node* cur = _root;
Node* parent = nullptr;//初始化为空,因为根节点没有父亲
while (cur)
{
if (cur->_key > key)
{
//去左树找
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
//去右树找
parent = cur;
cur = cur->_right;
}
else
{
//找到了,开始删除,此时cur就是要删除的节点
//case1:只有一个孩子(没有孩子也会划分进这种情况)
//cur没有左孩子
if (cur->_left == nullptr)
{
//要防止删除的是头节点 且 头节点只有右孩子的情况
//头节点的父亲为空
if (parent == nullptr)
{
//说明此时cur为头节点,且左树为空,则把整棵树的头换成cur的右树节点
_root = cur->_right;
}
else
{
//cur不是头节点,且左树为空
//判断此时cur是其父节点的左孩子还是右孩子
//让父节点的左指针 / 右孩子链接cur的右树(因为左树为空)
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;//删除这个节点
}
//cur没有右孩子
else if (cur->_right == nullptr)
{
//要防止删除的是头节点 且 头节点只有左孩子的情况
//头节点的父亲为空
if (parent == nullptr)
{
//说明此时cur为头节点,且右树为空,则把整棵树的头换成cur的左树节点
_root = cur->_left;
}
else
{
//cur不是头节点,且右树为空
//判断此时cur是其父节点的左孩子还是右孩子
//让父节点的左指针 / 右孩子链接cur的左树(因为右树为空)
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;//删除这个节点
}
else
{
//cur的左右孩子双全 ->保证了cur是有左右孩子的
//替换删除法:用删除节点的右树的最左节点 || 左树的最右节点替换删除节点
//这里我们选择删除节点和右树的最左节点替换 (把值覆盖即可,然后删除右树的最左节点)
//即使删的是根节点也是这么左,拿其左树的最右节点值交换,然后删除左树最右节点
Node* minParent = cur;//不能置为空,防止cur的右孩子就是右树的最左节点,此时下面对minParent解引用就会崩溃(对空指针解引用)
Node* min = cur->_right;//去cur的右树找最左节点
while (min->_left)
{
minParent = min;//用于记录cur右树最左节点的父亲
min = min->_left;
}
//此时min就是右树的最左节点位置
//用该值覆盖要删除节点的值
cur->_key = min->_key;
//删除min这个节点,要用min的父节点链接min的孩子
//判断min是其父节点的左孩子还是右孩子,然后让父节点对应的指针链接min的右孩子
//因为min是cur右树的最左节点,所以min是没有左孩子的,让min的父亲链接min的右孩子
if (minParent->_left == min)
{
minParent->_left = min->_right;
}
else
{
minParent->_right = min->_right;
}
delete min;//然后删除min这个节点
}
//只有找到了,才执行删除
return true;//删除成功
}
}
//没有找到删除的节点
return false;
}
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == nullptr)
{
return root;
}
//从根节点开始找删除节点位置
TreeNode* cur = root;
TreeNode* parent = nullptr;
while(cur)
{
if(cur->val >key)
{
parent = cur;
cur = cur->left;//去左树找
}
else if(cur->val < key)
{
parent = cur;
cur = cur->right;//去右树找
}
else
{
//找到了删除节点的位置
//case1:只有一个孩子
if(cur->left == nullptr)
{
//说明要删的是节点cur是根节点
if(parent == nullptr)
{
root = cur->right;//换头
delete cur;
return root;
}
else
{
//判断删除节点是其父亲的左孩子还是右孩子
if(parent->left == cur)
{
parent->left = cur->right;
}
else if(parent->right == cur)
{
parent->right = cur->right;
}
delete cur;
return root;
}
}
//右孩子为空
else if(cur->right == nullptr)
{
//说明要删的是节点cur是根节点
if(parent == nullptr)
{
root = cur->left;//换头
delete cur;
return root;
}
else
{
//判断删除节点是其父亲的左孩子还是右孩子
if(parent->left == cur)
{
parent->left = cur->left;
}
else if(parent->right == cur)
{
parent->right = cur->left;
}
delete cur;
return root;
}
}
else //左右孩子双全
{
//替换删除法,和右树的最左孩子交换
TreeNode* minParent = cur;
TreeNode* min = cur->right;//去cur的右树找最左孩子
while(min->left)
{
minParent = min;
min = min->left;
}
cur->val = min->val;
//minParent链接min的右树 (因为min是最左孩子,所以没有左孩子)
if(minParent->left == min)
{
minParent->left = min->right;
}
else
{
minParent->right = min->right;
}
delete min;//删除左树最右节点
return root;
}
}
}
return root;
}
};
递归删除
此处的精华也是传引用
注意:此时删除的是头节点也没有问题,因为root就是_root的别名,直接root = root->right,本质就是修改了根节点的位置了, 然后释放原来的头节点
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
private:
//传根节点位置的引用
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;//空树没有该节点,返回false
}
//先找到删除节点的位置
//如果要删除的值比根节点的值大 -> 到左树查找
//如果要删除的值比根节点的值小 -> 到左树查找
if (root->_key > key)
{
_EraseR(root->_left, key);
}
else if (root->_key < key)
{
_EraseR(root->_right, key);
}
else //找到了,开始删除
{
//因为此时是递归,并没有删除位置的父节点的指针
Node* del = root;// 记录要删除的节点
//分情况讨论:要删除的节点有几个孩子,没有孩子也可划分为只有一个孩子的情况
if (root->_left == nullptr) //没有左孩子
{
//因为root传的是引用,它不仅是删除节点的地址,也是其父节点左指针/右指针的别名
root = root->_right;//所以我们直接更改root的指向,就是更改了它父节点左指针/右指针的指向
}
else if (root->_right == nullptr) //没有右孩子
{
//因为root传的是引用,它不仅是删除节点的地址,也是其父节点左树/右树的别名
root = root->_left;//所以我们直接更改root的指向,就是更改了它父节点左指针/右指针的指向
}
else //左右孩子都有
{
//替换删除法:和右树的最左节点交换值
Node* min = root->_right;//去右树找最左节点
while (min->_left)
{
min = min->_left;
}
swap(min->_key, root->_key);//右树最左节点的值和删除节点的值进行替换
//转化成去root的右树删除最左节点的值(上面已经和root替换了值了),所以删的值是key
return _EraseR(root->_right, key);//注意:这里转化为去右树删除!注意:在这里return
}
//释放删除节点
delete del;
return true;
}
}
插入
Leetcode701
非递归插入
情况1:空树:直接使用val的值新建一个节点,该节点成为新的头
情况2:不是空树:查找到插入节点的位置,当cur走到空位置就可以插入,要保存其父节点用于链接
bool Insert(const K& key)
{
Node* cur = nullptr;
//case1:空树
if (_root == nullptr)
{
cur = new Node(key);//新建一个节点插入
_root = cur;//新节点成为头
return true;
}
//case2:已经存在节点
//找到插入位置,要保存父亲节点方便链接
Node* parent = nullptr;//用于标志父亲节点的位置
cur = _root;//从根位置开始找插入位置
//根据插入的值的大小往左树和右树走,走到空就是插入的位置
while (cur)
{
if (cur->_key > key)
{
parent = cur;//保存父节点位置
cur = cur->_left;//去左树
}
else if (cur->_key < key)
{
parent = cur;//保存父节点位置
cur = cur->_right;//去右树
}
else
{
//已经存在val这个数据,所以不插入,防止数据冗余
return false;
}
}
//cur走到空,此时应该插入了,用cur标志新节点,直接用于插入
cur = new Node(key);
//插入在父节点的左树还是右树需要判断
if (parent->_key > key)
{
parent->_left = cur;//插入在左树
}
else
{
parent->_right = cur;//插入在右树
}
return true;
}
递归插入
精华:root节点传引用
此时是空树也可以完美解决,root就是_root的别名,直接为_root开辟一个节点
//递归插入
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
private:
//注意:这里传的是引用
bool _InsertR(Node*& root, const K& key)
{
//注意:此时root传的是引用,当root走到空时,让root自成一个节点
if (root == nullptr)
{
//root也是其上级父亲的左孩子指针/右孩子指针的别名
root = new Node(key);
return true;
}
//判断插入到哪里
//如果要插入的值比根节点的值大 -> 插入到右树
//如果要插入的值比根节点的值小 -> 插入到左树
if (root->_key > key)
{
_InsertR(root->_left, key);//递归到左树插入
}
else if (root->_key < key)
{
_InsertR(root->_right, key);// 递归到右树插入
}
else
{
//root->_key == key 不允许插入
return false;
}
}
后序遍历验证是否正确
要调用函数 -> 需要传根节点,而成员变量一般是私有的,我们拿不到
方法1:所以我们可以提供一个成员函数拿到根节点
//将头节点返回
Node* GetNode()
{
return _root;
}
//子函数
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_key <<" ";
_Inorder(root->_right);
}
//外部调用:
int main()
{
int arr[] = { 326,3,532,6,5,2,32,6,547,6 };
BSTree<int> bst;
for (auto x : arr)
{
bst.Insert(x);
}
bst._Inorder(bst.GetNode());
return 0;
}
方法2:使用一个函数来套子函数(递归函数)
//中序遍历的函数
void Inorder()
{
_Inorder(_root);
}
//子函数
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_key <<" ";
_Inorder(root->_right);
}
搜索二叉树的排序 本质是排序+去重,因为不可以数据冗余(重复的值会被去掉),即重复的值会插入失败
BSTree.h
#pragma once
#include<iostream>
using namespace std;
//K -> key
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
//构造函数
BSTreeNode(const K& key)
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
template<class K>
struct BSTree
{
typedef BSTreeNode<K> Node; //将二叉树节点的类型重命名为Node,方便使用
BSTree()
:_root(nullptr)
{}
bool find(const K& key)
{
//空树不用找
if (_root == nullptr)
{
return false;
}
Node* cur = _root;//从根位置开始找
//结束条件:cur走到空 || 提前返回
while (cur)
{
if (cur->_key > key)
{
//去左树找
cur = cur->_left;
}
else if (cur->_key < key)
{
//去右树找
cur = cur->_right;
}
else
{
return true;
}
}
//说明没有找到
return false;
}
bool Insert(const K& key)
{
//空树:
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;//从根节点位置开始
Node* parent = nullptr;
//当cur走到空可以插入
while (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);
//判断插入在父亲的哪里
if (parent->_key > key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
bool Erase(const K& key)
{
//空树
if (_root == nullptr)
{
return false;
}
//从根位置开始遍历找到删除位置,要保存其父亲的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
//去左树找
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
//去右树找
parent = cur;
cur = cur->_right;
}
else
{
// 找到了,开始删除,此时cur就是要删除的节点
//case1:只有一个孩子(没有孩子也划分进这种情况)
//cur没有左孩子
if (cur->_left == nullptr)
{
//要防止删除的是头节点 且 头节点只有右孩子的情况
//头节点的父亲为空
if (parent == nullptr)
{
//说明此时cur为头节点,且左树为空,则把整棵树的头换成cur的右树节点
_root = cur->_right;
}
else
{
//cur不是头节点,且左树为空
//判断此时cur是其父节点的左孩子还是右孩子
//让父节点的左指针 / 右孩子链接cur的右树(因为左树为空)
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;//删除这个节点
}
//cur没有右孩子
else if (cur->_right == nullptr)
{
//要防止删除的是头节点 且 头节点只有左孩子的情况
//头节点的父亲为空
if (parent == nullptr)
{
//说明此时cur为头节点,且右树为空,则把整棵树的头换成cur的左树节点
_root = cur->_left;
}
else
{
//cur不是头节点,且右树为空
//判断此时cur是其父节点的左孩子还是右孩子
//让父节点的左指针 / 右孩子链接cur的左树(因为右树为空)
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;//删除这个节点
}
else
{
//cur的左右孩子双全 ->保证了cur是有左右孩子的
//替换删除法:用删除节点的右树的最左节点 || 左树的最右节点替换删除节点
//这里我们选择右树的最左节点替换
Node* minParent = cur;//不能置为空,防止cur的右孩子就是右树的最左节点,此时下面父亲链接就会出错
Node* min = cur->_right;//找cur的右树,找最左节点
while (min->_left)
{
minParent = min;//用于记录cur右树最左节点的父亲
min = min->_left;
}
//此时min就是右树的最左节点位置
//用该值覆盖要删除节点的值
cur->_key = min->_key;
//删除min这个节点,要用min的父节点链接min的孩子
//判断min是其父节点的左孩子还是右孩子,然后让父节点对应的指针链接min的右孩子
//因为min是cur右树的最左节点,所以min是没有右孩子的,让min的父亲链接min的右孩子
if (minParent->_left == min)
{
minParent->_left = min->_right;
}
else
{
minParent->_right = min->_right;
}
delete min;//然后删除min这个节点
}
//只有找到了,才执行删除
return true;//删除成功
}
}
//没有找到删除的节点
return false;
}
void inOrder()
{
return _inOrder(_root);
}
//递归插入
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
//递归查找
Node* FindR(const K& key)
{
return _FindR(_root, key);
}
//递归删除
Node* EraseR(const K& key)
{
return _EraseR(_root, key);
}
private:
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;//空树没有该节点,返回false
}
//先找到删除节点的位置
//如果要删除的值比根节点的值大 -> 到左树查找
//如果要删除的值比根节点的值小 -> 到左树查找
if (root->_key > key)
{
_EraseR(root->_left, key);
}
else if (root->_key < key)
{
_EraseR(root->_right, key);
}
else //找到了,开始删除
{
//因为此时是递归,并没有父节点的指针
Node* del = root;// 记录要删除的节点
//分情况讨论:要删除的节点有几个孩子,没有孩子也可划分为只有一个孩子的情况
if (root->_left == nullptr) //没有左孩子
{
root = root->_right;//因为我们的root传的是引用,所以我们直接更改root的指向
}
else if (root->_right == nullptr) //没有右孩子
{
root = root->_left;//因为我们的root传的是引用,所以我们直接更改root的指向
}
else //左右孩子都有
{
//替换删除法:和右树的最左节点交换
Node* min = root->_right;//去左树找最右节点
while (min->_left)
{
min = min->_left;
}
swap(min->_key, root->_key);//右树最左节点的值和删除节点的值进行替换
//转化成去右树删除最左节点的值(上面已经替换了值了),也就是key
return _EraseR(root->_right, key);
}
//释放删除节点
delete del;
return true;
}
}
bool _InsertR(Node*& root, const K& key)
{
//注意:此时root传的是引用,当root走到空时,就可以开辟节点进行插入了
if (root == nullptr)
{
root = new Node(key);
return true;
}
//判断插入到哪里
//如果要插入的值比根节点的值大 -> 插入到右树
//如果要插入的值比根节点的值小 -> 插入到左树
if (root->_key > key)
{
_InsertR(root->_left, key);//递归到左树插入
}
else if (root->_key < key)
{
_InsertR(root->_right, key);// 递归到右树插入
}
else
{
//root->_key == key 不允许插入
return false;
}
}
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return root;//空树直接返回空
}
//如果要找的值比根节点的值大 -> 去右树找
//如果要找的值比根节点的值小 -> 去左树找
if (root->_key < key)
{
_FindR(root->_right, key);//递归去右树找
}
else if(root->_key > key)
{
_FindR(root->_left, key);//递归去左树找
}
else
{
return root;//找到了
}
}
void _inOrder(Node* root)
{
if (root == nullptr) return;
_inOrder(root->_left);
cout << root->_key << " ";
_inOrder(root->_right);
}
Node* _root;
};
搜索树的应用
1.搜索: key搜索模型 + key/value搜索模型 2.排序+去重
K模型
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
即key在不在搜索二叉树
- 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下
- 以单词集合中的每个单词作为key,构建一棵二叉搜索树
- 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误
KV模型
KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对,和搜索二叉树的代码差不多,只不过是多了一个模板参数V
查找的时候
:此时返回的是Node*,而不是bool,因为要得到对应的value值
删除的时候
:替换法删除时,不仅要替换key,还要替换value值
打印的时候
:还要把对应的value打印出来
//K -> key V->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)
:_key(key)
,_value(value)
, _left(nullptr)
, _right(nullptr)
{}
};
搜索二叉树的框架:
template<class K,class V>
struct BSTree
{
typedef BSTreeNode<K,V> Node; //将二叉树节点的类型重命名为Node,方便使用
BSTree()
:_root(nullptr)
{}
Node* _root;//节点指针,整棵树的头节点
};
查找:
Node* find(const K& key)
{
//空树不用找
if (_root == nullptr)
{
return nullptr;
}
Node* cur = _root;//从根位置开始找
//结束条件:cur走到空 || 提前返回
while (cur)
{
if (cur->_key > key)
{
//去左树找
cur = cur->_left;
}
else if (cur->_key < key)
{
//去右树找
cur = cur->_right;
}
else
{
return cur;
}
}
//说明没有找到
return false;
}
删除:
替换法删除的时候,把value也替换,其余的和二叉搜索树一样
else
{
Node* minParent = cur;
Node* min = cur->_right;
while (min->_left)
{
minParent = min;
min = min->_left;
}
cur->_key = min->_key;
cur->_value = min->_value;//_val也替换
if (minParent->_left == min)
minParent->_left = min->_right;
else
minParent->_right = min->_right;
delete min;
}
打印: 把value也对应的打出来
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
1.英汉词典就是英文与中文的对应关系:通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对
void TestBSTree1()
{
// 字典KV模型
BSTree<string, string> dict;
dict.Insert("sort", "排序");
dict.Insert("left", "左边");
dict.Insert("right", "右边");
dict.Insert("map", "地图、映射");
//...
string str;
while (cin>>str)
{
BSTreeNode<string, string>* ret = dict.Find(str);//在类外面不能直接用Node,因为Node是在里面typedef
if (ret)
{
cout << "对应中文解释:" << ret->_value << endl;
}
else
{
cout << "无此单词" << endl;
}
}
}
注意:二叉搜索树需要比较,键值对比较时只比较Key
2.统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对
void TestBSTree2()
{
// 统计水果出现次数
string arr[] = { "苹果", "西瓜","草莓", "苹果", "西瓜", "苹果",
"苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
//key:水果名称 value:次数
BSTree<string, int> countTree;
//水果没有出现过->新建水果词频,次数置1
//出现过->出现次数++
for (auto& str : arr)
{
//BSTreeNode<string, int>* ret = countTree.Find(str);
auto ret = countTree.Find(str);//用auto更方便
if (ret != nullptr)
{
ret->_value++;//说明出现过,次数+1
}
else
{
countTree.Insert(str, 1);//新建水果词频,次数置1
}
}
countTree.InOrder();
}
注意:二叉搜索树需要比较,键值对比较时只比较Key
性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的
深度的函数,即结点越深,则比较次数越多 (最多查找高度次)
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:O(log N)
2^30 = 10亿 2^31 = 20亿
最差情况下,二叉搜索树退化为单支树(相当于链表),其查找效率变为:O(N)
问题:如果退化成单支树,二叉搜索树的性能就失去了 那能否进行改进,不论按照什么次序插入关键码,都可以是二叉搜索树的性能最佳?