目录
二叉搜索树的概念
二叉搜索树中序遍历是有序的,因为二叉搜索树的定义决定了左子树节点值小于根节点值、右子树节点值大于等于根节点值(每一颗子树也满足),而中序遍历先左子树、再根节点、后右子树的方式使得遍历结果自然有序。
二叉搜索树的实现
基本结构
//节点的定义
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left; //左节点
BSTreeNode<K>* _right; //右节点
K _key; //存储 key 值
BSTreeNode(const K& key) //构造函数完成初始化
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
template <class K> //key 关键字,进行比较
class BSTree //Binary Search Tree
{
typedef BSTreeNode<K> Node;
private:
Node* _root = nullptr; //在类内进行成员初始化
};
插入
1,当树是空树的时候
2,当树不为空的时候
#pragma once
//节点的定义
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left; //左节点
BSTreeNode<K>* _right; //右节点
K _key; //存储 key 值
BSTreeNode(const K& key) //构造函数完成初始化
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
template <class K> //key 关键字,进行比较
class BSTree //Binary Search Tree
{
typedef BSTreeNode<K> Node;
public:
bool Insert(const K& key)
{
//1,根为空的时候
if (_root == nullptr)
{
_root = new Node(key);
}
//2,根不为空的时候
Node* cur = _root;
while (cur)
{
if (key > cur->_key) //插入的key比当前节点大就往右边走
{
cur = cur->_right;
}
else if (key < cur->_key) //插入的key比当前节点小就往左边走
{
cur = cur->_left;
}
else
{
return false; //插入的key和当前节点相等,就插入失败
}
}
cur = new Node(key);
return true;
}
//中序遍历
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 Test()
{
BSTree<int> t;
int a[] = { 5,3,4,1,7,8,2,6,0,9 };
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();
}
通过测试我们会发现,这里只有 5 插入成功了,也就是根节点插入成功,那么这段代码存在一定的问题,如何解决呢???
3,纠正后的代码
#pragma once
//节点的定义
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left; //左节点
BSTreeNode<K>* _right; //右节点
K _key; //存储 key 值
BSTreeNode(const K& key) //构造函数完成初始化
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
template <class K> //key 关键字,进行比较
class BSTree //Binary Search Tree
{
typedef BSTreeNode<K> Node;
public:
bool Insert(const K& key)
{
//当树是空树的时候
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
//树不为空的时候
Node* parent = nullptr; //用一个节点来记录cur的父亲
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (key > parent->_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);
cout << endl;
}
private:
Node* _root = nullptr; //在类内进行成员初始化
};
void Test()
{
BSTree<int> t;
int a[] = { 5,3,4,1,7,8,2,6,0,9 };
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();
}
查找
//查找
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if (key < cur->_key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
删除
1,左为空或右为空
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了
//1,左边为空
if (cur->_left == nullptr)
{
if (parent->_right == cur)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
delete cur;
}
//2,右边为空
else if (cur->_right == nullptr)
{
if (parent->_left = cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
}
else //3,左右都不为空
{
}
return true;
}
}
return false;
}
2,左右都不为空
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了
//1,左边为空
if (cur->_left == nullptr)
{
if (parent->_right == cur)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
delete cur;
}
//2,右边为空
else if (cur->_right == nullptr)
{
if (parent->_left = cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
}
else //3,左右都不为空
{
Node* rightMinParent = nullptr;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
//替代
cur->_key = rightMin->_key;
//转换成删除rightMin
rightMinParent->_left = rightMin->_right;
delete rightMin;
}
return true;
}
}
return false;
}
假设我一上来就删除 7 这棵树存在问题
纠正后的代码:
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
//替代
cur->_key = rightMin->_key;
//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)
if (rightMin == rightMinParent->_left)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
如果把这棵树删空也会存在问题
纠正后的代码:
if (cur->_left == nullptr)
{
if (cur == _root) //当删除的是根节点的时候
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root) //当删除的是根节点的时候
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
}
3,删除的完整代码:
//删除
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了,开始删除
// 1、左为空
// 2、右为空
// 3、左右都不为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
}
else
{
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
//替代
cur->_key = rightMin->_key;
//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)
if (rightMin == rightMinParent->_left)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
}
return true;
}
}
return false;
}
二叉搜索树的完整代码
BSTree.h
#pragma once
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> //key 关键字,进行比较
class BSTree //Binary Search Tree
{
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 (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (key > parent->_key)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
//查找
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if (key < cur->_key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
//删除
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了,开始删除
// 1、左为空
// 2、右为空
// 3、左右都不为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
}
else
{
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
//替代
cur->_key = rightMin->_key;
//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)
if (rightMin == rightMinParent->_left)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
}
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()
{
BSTree<int> t;
int a[] = { 5,3,4,1,7,8,2,6,0,9 };
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();
//1.上来我就删除7,有问题
t.Erase(7);
t.InOrder();
t.Erase(8);
t.InOrder();
//2.把这棵树删空,也会存在问题
/*for (auto e : a)
{
t.Erase(e);
}
t.InOrder();*/
叶子
t.Erase(2);
t.InOrder();
左为空或者右为空
t.Erase(8);
t.Erase(1);
t.InOrder();
左右都不为空
t.Erase(5);
t.InOrder();
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include<string>
#include "BSTree.h"
int main()
{
TestBSTree();
return 0;
}
二叉搜索树的应用
Key 模型
Key-Value 模型
改造二叉搜索树为KV结构
BSTree.h
#pragma once
// 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> //key 关键字,进行比较
class BSTree //Binary Search Tree
{
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 (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key, value);
if (key > parent->_key)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
//查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if (key < cur->_key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
//删除
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//找到了,开始删除
// 1、左为空
// 2、右为空
// 3、左右都不为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
}
else
{
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
//替代
cur->_key = rightMin->_key;
//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)
if (rightMin == rightMinParent->_left)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
}
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("sort", "排序");
dict.Insert("string", "字符串");
dict.Insert("tree", "树");
dict.Insert("insert", "插入");
string str;
while (cin >> str)
{
BSTreeNode<string, string>* ret = dict.Find(str);
if (ret)
{
cout << ret->_value << endl;
}
else
{
cout << "无此单词" << endl;
}
}*/
//以后很常用,统计水果的个数
string strArr[] = { "西瓜","西瓜" ,"樱桃","苹果","香蕉","西瓜" ,"西瓜","哈密瓜" ,"西瓜" ,"西瓜" };
BSTree<string, int> countTree;
for (auto str : strArr)
{
BSTreeNode<string, int>* ret = countTree.Find(str);
if (ret == nullptr)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include<string>
#include "BSTree.h"
int main()
{
TestBSTree();
return 0;
}
二叉搜索树的性能分析
最好情况:
- 对于平衡的二叉搜索树,插入操作首先需要找到插入位置。因为树是平衡的,这个查找过程类似于查找操作,时间复杂度为 O(logN)。
- 找到位置后,插入新节点的操作本身时间复杂度为O(1) (只需要修改指针来连接新节点)。所以,整体插入操作在最好情况下的时间复杂度为O(logN)。
最坏情况:
- 当二叉搜索树退化为链表时,插入操作需要先遍历链表找到合适的插入位置。例如,若按照从小到大的顺序插入节点,要插入一个新的最大值,需要遍历到链表的末尾。此时,插入操作的时间复杂度为 O(N)。
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么就有我们后续学习的AVL树和红黑树。