目录
在左节点为空,parent指向cur的右节点(修复bug版) :
1.二叉搜索树的概念
二叉搜索树( BST, Binary Search Tree ),又称二叉排序树 或 二叉查找树 , 它可以是一棵空树, 或者是具有以下性质的二叉树 :
1. 非空左子树的所有键值小于其根节点的键值;
2. 非空右子树的所有键值大于其根节点的键值;
3. 左右子树都是二叉搜索树.
eg.
int a[] = { 8,3,1,10,6,4,7,14,13 };
2.二叉搜索树的基本操作
2.1 二叉搜索树的查找
steps:
1. 从根节点开始比较, 查找, 比根节点大则往右边走进行查找; 比根节点小则往左边走进行查找;
2. 最多查找高度次, 走到空还没找到,这个值不存在
template<class T>
struct BSTNode
{
BSTNode(const T& data = T())
: left(nullptr),rigth(nullptr),data(data)
{}
BSTNode<T>* left;
BSTNode<T>* right;
T data;
};
template<class T>
class BSTree
{
typedef BSTNode<T> Node;
typedef Node* PNode;
public:
BSTree(): root(nullptr)
{}
~BSTree();
bool Find(const T& data) {
Node* cur = root;
while (cur) {
if (cur->data < key)
cur = cur->right;
else if (cur->data > key)
cur = cur->left;
else
return true;
}
return false;
}
};
2.2 二叉搜索树的中序遍历
中序遍历 : 根节点 -- >> 左子树 -->> 右子树
// InOrder
template<class T>
struct BSTNode
{
BSTNode(const T& data = T())
: left(nullptr), rigth(nullptr), data(data)
{}
BSTNode<T>* left;
BSTNode<T>* right;
T data;
};
template<class T>
class BSTree
{
typedef BSTNode<T> Node;
typedef Node* PNode;
public:
BSTree() : root(nullptr)
{}
~BSTree();
void InOrder(Node* root) {
if (root == = nullptr)
return;
InOder(root->left);
cout << root->data << endl;
InOrder(root->right);
}
};
2.3 二叉搜索树的插入
steps:
1. 树为空, 新增节点,赋值给 root 指针;
2. 树不空, 依据二叉搜索树性质查找插入位置, 插入新节点
review : 二叉搜索树的性质:
1. 非空左子树的所有键值小于其根节点的键值;
2. 非空右子树的所有键值大于其根节点的键值;
3. 左右子树都是二叉搜索树.
eg. 依次插入16, 0 :
// Insert
template<class T>
struct BSTNode
{
BSTNode(const T& data = T())
: left(nullptr), rigth(nullptr), data(data)
{}
BSTNode<T>* left;
BSTNode<T>* right;
T data;
};
template<class T>
class BSTree
{
typedef BSTNode<T> Node;
public:
BSTree() : root(nullptr)
{}
~BSTree();
bool Insert(const T& data)
{
// 如果树为空,直接插入
if (nullptr == root)
{
root = new Node(data);
return true;
}
//按照二叉搜索树的性质查找data在树中的插入位置
// cur : data ; 记录cur的父节点, 最终判断cur是左节点还是右节点
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (data < cur->data)
cur = cur->left;
else if (data > cur->data)
cur = cur->right;
else
return false; // 元素已经存在
}
cur = new Node(data);
if (data < parent->data)
parent->left = cur;
else
parent->right = cur;
return true;
}
};
2.4 二叉搜索树的删除
steps:
1. 查找元素是否在二叉搜索树中,如果不在则返回; 否则可能分4种情况进行讨论:
1) 需要删除的节点没有子节点
2) 需要删除的节点只有左孩子节点
3) 需要删除的节点只有右孩子节点
4) 需要删除的节点有左右孩子节点
图解 : eg.
注意 4)情况 ( 图解中的<3>情况 ), 在要删除的节点的右子树下寻找中序的第一个最小值的节点 或 在要删除的节点的左子树下寻找中序下的第一个最大值的节点, 用它的值覆盖要删除节点的值,再删除寻找的节点.
( 先来删除的前置工作: 重点讨论 <2> <3> 的删除情况
// Erase
bool Erase(const T& data)
{
// 树为空, 删除失败
if (root == nullptr)
return false;
// 查找data在树中的位置
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
if (data == cur->data)
break;
else if (data < cur->data)
{
parent = cur;
cur = cur->left;
}
else {
parent = cur;
cur = cur->right;
}
}
// data不在树中, 删除失败
if (nullptr == cur)
return false;
//左节点为空,parent指向cur的右节点
if (nullptr == cur->left)
{
//
}
//右节点为空, parent指向cur的左节点
else if (nullptr == cur->right)
{
//
}
// cur的左右节点都有
else
{
// ...
}
};
<2> 当左节点为空, parent指向cur的右节点:
if (nullptr == cur->left)
{
if (cur->left == nullptr) {
if (cur == parent->left)
parent->left = cur->right;
else
{
parent->right = cur->right;
}
}
delete cur;
}
这个代码对吗 ?
在大部分时候是可以进行删除的, 然而在删除二叉搜索树的最后一个结点的时候代码会崩溃.
在左节点为空,parent指向cur的右节点(修复bug版) :
//左节点为空,parent指向cur的右节点
if (nullptr == cur->left)
{
if (cur == root)
root == cur->right;
else {
if (cur->left == nullptr) {
if (cur == parent->left)
parent->left = cur->right;
else
{
parent->right = cur->left;
}
}
}
delete cur;
}
<3> cur的左右节点都不为空 :
---->>>> 查找右子树的最左节点替代删除
Node* rightMinParent = cur;
Node* rightMin = cur->right;
while (rightMin->left) {
rightMinParent = rightMin;
rightMin = rightMin->left;
}
swap(cur->data, rightMin->data);
rightMinParent->left = rightMin->right;
delete rightMin;
注意 :
1. 找到右子树的最左节点, swap之后 不能通过递归查找 ==>> 可能找不到要删的值
2. 在rightMin的左子树为空时,代码崩溃
图解 : 在rightMin的左子树为空时,代码崩溃
右侧二叉搜索树结构图的旁边的笔记颜色对应左侧相应颜色代码快执行结束后的结果.
cur的左右节点都不为空(修复bug版) :
Node* rightMinParent = cur;
Node* rightMin = cur->right;
while (rightMin->left) {
rightMinParent = rightMin;
rightMin = rightMin->left;
}
swap(cur->data, rightMin->data);
if (rightMinParent->left == rightMin)
rightMinParent->left = rightMin->right;
else
rightMinParent->right = rightMin->right;
delete rightMin;
2.5 二叉搜索树的模拟实现
template<class T>
struct BSTNode
{
BSTNode(const T& data = T())
: left(nullptr), rigth(nullptr), data(data)
{}
BSTNode<T>* left;
BSTNode<T>* right;
T data;
};
template<class T>
class BSTree
{
typedef BSTNode<T> Node;
public:
BSTree() : root(nullptr)
{}
~BSTree();
// InOrder
void InOrder(Node* root) {
if (root == = nullptr)
return;
InOder(root->left);
cout << root->data << endl;
InOrder(root->right);
}
// Find
bool Find(const T& data) {
Node* cur = root;
while (cur) {
if (cur->data < key)
cur = cur->right;
else if (cur->data > key)
cur = cur->left;
else
return true;
}
return false;
}
// Insert
bool Insert(const T& data)
{
// 如果树为空,直接插入
if (nullptr == root)
{
root = new Node(data);
return true;
}
//按照二叉搜索树的性质查找data在树中的插入位置
// cur : data ; 记录cur的父节点, 最终判断cur是左节点还是右节点
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (data < cur->data)
cur = cur->left;
else if (data > cur->data)
cur = cur->right;
else
return false; // 元素已经存在
}
cur = new Node(data);
if (data < parent->data)
parent->left = cur;
else
parent->right = cur;
return true;
}
// Erase
bool Erase(const T& data)
{
// 树为空, 删除失败
if (root == nullptr)
return false;
// 查找data在树中的位置
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
if (data == cur->data)
break;
else if (data < cur->data)
{
parent = cur;
cur = cur->left;
}
else {
parent = cur;
cur = cur->right;
}
}
// data不在树中, 删除失败
if (nullptr == cur)
return false;
//左节点为空,parent指向cur的右节点
if (nullptr == cur->left)
{
if (cur == root)
root == cur->right;
else {
if (cur->left == nullptr) {
if (cur == parent->left)
parent->left = cur->right;
else
{
parent->right = cur->left;
}
}
}
delete cur;
}
//右节点为空, parent指向cur的左节点
else if (nullptr == cur->right)
{
if (cur == root)
root == cur->left;
else {
if (cur->right == nullptr) {
if (cur == parent->left)
parent->left = cur->left;
else
parent->right = cur->left;
}
}
delete cur;
}
// cur的左右节点都有
else
{
Node* rightMinParent = cur;
Node* rightMin = cur->right;
while (rightMin->left) {
rightMinParent = rightMin;
rightMin = rightMin->left;
}
swap(cur->data, rightMin->data);
if (rightMinParent->left == rightMin)
rightMinParent->left = rightMin->right;
else
rightMinParent->right = rightMin->right;
delete rightMin;
}
}
};
3. 二叉搜索树的应用
3.1 K模型 :
K模型只有key作为关键码,结构中只需要存储key即可, 关键码即为需要搜索的值.
eg. 检查一个单词是否拼写正确 :
1.以词库中所有单词集合中的每个单词作为key, 构建二叉搜索树
2. 在二叉搜索树中检索该单词是否存在,存在则拼写正确;不存在则拼写错误.
3.2 KV模型:
每一个关键码key, 都有与之对应的value, 即 <key, value>键值对.
eg1. 英汉词典: 英汉词典就是中英文对照的关系. 通过英文可以快速找到与其对应的中文.英文单词和其对应的中文释义 <word, Chinese>就构成了一种键值对.
eg2. 统计单词次数 : 统计成功后, 给定单词就可以快速找到其出现的次数, 单词与其出现的次数就是 <word,count>键值对.
// 紧急修改!! ( 之前的实现有误!
K模型,KV模型的实际应用 和 递归的模拟实现二叉搜索树
// 英汉词典
#include <iostream>
#include <string>
#include <utility>
using namespace std;
#pragma once
Binary_Search_Tree
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 < cur->_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 NULL;
}
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 (parent == nullptr)
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (parent == nullptr)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
}
else
{
// 替换法删除 左树的最大节点(最右节点) 或者是右树的最小节点(最左节点)
Node* minNodeParent = cur; // 这里要注意不能初始化给空
Node* minNode = cur->_right;
while (minNode->_left)
{
minNodeParent = minNode;
minNode = minNode->_left;
}
swap(cur->_key, minNode->_key);
// 转换成删除minNode
// 因为minNode是作为空的节点,可以直接删除
if (minNodeParent->_right == minNode)
minNodeParent->_right = minNode->_right;
else
minNodeParent->_left = minNode->_right;
delete minNode;
}
return true;
}
}
return false;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
Node* _root = nullptr;
};
void TestBSTree()
{
BSTree<string, string> dict;
dict.Insert("insert", "插入");
dict.Insert("erase", "删除");
dict.Insert("left", "左边");
dict.Insert("string", "字符串");
string str;
while (cin>>str)
{
auto ret = dict.Find(str);
if (ret)
{
cout << str << ":" << ret->_value << endl;
}
else
{
cout << "单词拼写错误" << endl;
}
}
string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
// 统计水果出现的次
BSTree<string, int> countTree;
for (auto str : strs)
{
auto ret = countTree.Find(str);
if (ret == NULL)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}
//Binary_Search_Tree 递归实现--了解一下
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 < cur->_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 NULL;
}
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 (parent == nullptr)
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (parent == nullptr)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
}
else
{
// 替换法删除 左树的最大节点(最右节点) 或者是右树的最小节点(最左节点)
Node* minNodeParent = cur; // 这里要注意不能初始化给空
Node* minNode = cur->_right;
while (minNode->_left)
{
minNodeParent = minNode;
minNode = minNode->_left;
}
swap(cur->_key, minNode->_key);
// 转换成删除minNode
// 因为minNode是作为空的节点,可以直接删除
if (minNodeParent->_right == minNode)
minNodeParent->_right = minNode->_right;
else
minNodeParent->_left = minNode->_right;
delete minNode;
}
return true;
}
}
return false;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
Node* _root = nullptr;
};
void TestBSTree()
{
int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
BSTree<int> bst;
for (auto e : a)
{
//bst.Insert(e);
bst.InsertR(e);
}
}
4. 二叉搜索树的性能分析
插入和删除操作都必须先查找,查找效率代表二叉搜索树中各个操作的性能.
最优情况下 : 二叉搜索树是完全二叉树, 查找平均次数 : log2(n)
最差情况下 : 二叉搜索树退化为单支树( 接近有序插入,退化厉害,效率下降 ), 查找平均次数 : n
为了解决退化的情况, 解决方案 ===>>> AVL树 : 平衡搜索二叉树