目录
二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是一棵空树
搜索二叉树的性质:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
二叉搜索树操作
基本框架
#pragma once
#include <iostream>
using namespace std;
// 定义二叉搜索树的节点结构
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() : _root(nullptr) {}
private:
Node *_root; // 根节点指针
};
default:强制编译器生成默认的构造——C++11的用法
BSTree()=default;
插入
插入的具体过程如下:
树为空,则直接新增节点,赋值给root指针
树不为空,按二叉搜索树性质查找插入的位置,插入新节点(记录父节点,判断插入的节点应该在父节点的左子树还是右子树)
/**
* 插入节点到搜索二叉树中
* @key 要插入的节点的键值
* @return 如果成功插入节点,则返回true;如果节点已经存在相同值,则返回false。
*/
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; // 节点已经存在相同值,返回false
}
// 创建新节点
cur = new Node(key);
// 将新节点链接到父节点的相应位置
if (parent->_key < key)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
二叉搜索树的删除(重点)
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情 况: a. 要删除的结点无孩子结点 b. 要删除的结点只有左孩子结点 c. 要删除的结点只有右孩子结点 d. 要删除的结点有左、右孩子结点
看似删除节点有4种情况,但实际上a和b和c可以合并,这样就只有2种情况了: a:待删除的结点无孩子/只有一个孩子:删除结点并使父亲结点指向被删除结点的孩子结点(无孩子视为孩子是空结点,任意指向一个即可) b:待删除的结点有左右孩子:采用替换法,寻找删除结点右子树的最小结点(右子树最左结点),将最小结点的值和删除结点的值替换,然后删除最小结点(此时最小结点,要么没有孩子,要么只有一个孩子,符合a情况可以直接删除)
/**
* 从搜索二叉树中删除指定键值的节点
* @key 要删除的节点的键值
* @return 如果成功删除节点,则返回true;如果未找到指定节点,则返回false。
*/
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. 左子树为空
if (cur->_left == nullptr)
{
// 若待删除节点为根节点
if (cur == _root)
_root = cur->_right;
else
{
// 更新父节点的指针,使其指向待删除节点的右子树
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
}
// 2. 右子树为空
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;
}
}
else
{
// 找到左子树中最右边的节点或右子树中最左边的节点,用于替换当前节点
Node *pminRight = cur;
Node *minRight = cur->_right;
while (minRight->_left)
{
pminRight = minRight;
minRight = minRight->_left;
}
// 将最小右子树或最大左子树的键值替换到当前节点
cur->_key = minRight->_key;
// 删除替换节点
if (pminRight->_left == minRight)
pminRight->_left = minRight->_right;
else
pminRight->_right = minRight->_right;
delete minRight;
}
return true;
}
}
// 未找到指定节点
return false;
}
二叉搜索树的查找
-
从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
-
最多查找高度次,走到空,还没找到,则这个值不存在。
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;
}
拷贝构造
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()
{
Destory(_root);
}
void Destory(Node*& root)
{
if (root==nullptr)
return;
Destory(root->_left);
Destory(root->_right);
delete root;
root=nullptr;
}
operator =
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root,t._root);
return *this;
}
遍历
void InOrder()
{
_InOrder(_root);
cout<<endl<<"==="<< endl;
}
void _InOrder(Node *root)
{
if (root == nullptr)
return;
// 中序遍历
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
递归构造搜索二叉树
插入
bool InsertR(const K& key)
{
// 调用递归插入函数,从根节点开始插入
return _InsertR(_root, key);
}
/**
* 递归插入节点到搜索二叉树中
* @param root 当前子树的根节点(通过引用传递,可以修改指针)
* @param key 要插入的键值
* @return 如果成功插入节点,则返回true;如果节点已存在,则返回false。
*/
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 EraseR(const K& key)
{
// 调用递归删除函数,从根节点开始删除
return _EraseR(_root, key);
}
/**
* 递归删除搜索二叉树中指定键值的节点
* @param root 当前子树的根节点(通过引用传递,可以修改指针)
* @param key 要删除的键值
* @return 如果成功删除节点,则返回true;如果节点不存在,则返回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
{
// 找到了要删除的节点
Node* del = root;
if (root->_right == nullptr) // 如果右子树为空,直接删除当前节点并调整指针
root = root->_left;
else if (root->_left == nullptr) // 如果左子树为空,直接删除当前节点并调整指针
root = root->_right;
else
{
// 如果左右子树都不为空,找到左子树中的最大节点来替换当前节点
Node* maxLeft = root->_left;
while (maxLeft->_right)
maxLeft = maxLeft->_right;
// 将左子树中的最大节点的键值与当前节点的键值交换
swap(root->_key, maxLeft->_key);
// 在左子树中递归删除已经被交换的节点
return _EraseR(root->_left, key);
}
// 删除当前节点
delete del;
return true;
}
return true;
}
查找
//递归
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(Node* root,const K& key)
{
if(root==nullptr)
return false;
if(root->_key == key)
return true;
if(root->_key<key)
return _FindR(root->_right,key);
else
return _FindR(root->_left,key);
}
二叉树的应用
-
K模型 :K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下: 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
-
KV模型 :每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方 式在现实生活中非常常见: 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对; 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是<word, count>就构成一种键值对。
二叉搜索树的性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下:二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log(N)
最差情况下:二叉搜索树退化为单支树(或者类似单支),其平均比较次数为 N
如果退化为单支树,二叉搜索树的性能就失去了。那能否进行改进?无论按照什么次序插入关键码,都能达到最优?这就需要AVL树和红黑树了。
代码示例
BSTree.h
#pragma once
#include <iostream>
using namespace std;
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() : _root(nullptr) {}
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); // new节点
// 链接-链接新节点
if (parent->_key < key)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
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 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
{
/*
若右/左子树为NUll,则可直接用左/右的第一个节点当头节点
或用左子树最右(大)节点or左子树最左(小)节点
*/
// 删除
// 1.左为空
if (cur->_left == nullptr)
{
if (cur == _root)
_root = cur->_right;
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
} // 2.右为空
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;
}
}
else
{
// 找左子树最右(大)节点or左子树最左(小)节点
Node *pminRight = cur;
Node *minRight = cur->_right;
while (minRight->_left)
{
pminRight = minRight;
minRight = minRight->_left;
}
cur->_key = minRight->_key;
if (pminRight->_left == minRight)
pminRight->_left = minRight->_right;
else
pminRight->_right = minRight->_right;
delete minRight;
}
return true;
}
}
return false;
}
// 递归
bool FindR(const K &key)
{
return _FindR(_root, key);
}
bool InsertR(const K &key)
{
return _InsertR(_root, key);
}
bool EraseR(const K &key)
{
return _EraseR(_root, key);
}
void InOrder()
{
_InOrder(_root);
cout << endl
<< "===" << endl;
}
// BSTree(){}
// BSTree()=default;
BSTree(const BSTree<K> &t)
{
_root = Copy(t._root);
}
~BSTree()
{
Destory(_root);
}
BSTree<K> &operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
private:
// Node*& root [*&] 利用引用进行链接
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 _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
{
Node *del = root;
// 删除
if (root->_right == nullptr)
root = root->_left;
else if (root->_left == nullptr)
root = root->_right;
else
{
Node *maxleft = root->_left;
while (maxleft->_right)
maxleft = maxleft->_right;
swap(root->_key, maxleft->_key);
return _EraseR(root->_left, key);
}
delete del;
return true;
}
return true;
}
bool _FindR(Node *root, const K &key)
{
if (root == nullptr)
return false;
if (root->_key == key)
return true;
if (root->_key < key)
return _FindR(root->_right, key);
else
return _FindR(root->_left, key);
}
void _InOrder(Node *root)
{
if (root == nullptr)
return;
// 中序遍历
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
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;
}
void Destory(Node *&root)
{
if (root == nullptr)
return;
Destory(root->_left);
Destory(root->_right);
delete root;
root = nullptr;
}
private:
// Node *_root = nullptr;
Node *_root;
};
Test.cpp
#include "BSTree.h"
void TestBSTree1()
{
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 19};
BSTree<int> t1;
for (auto e : a)
t1.Insert(e);
t1.InOrder();
}
void TestBSTree2()
{
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 19};
BSTree<int> t1;
for (auto e : a)
t1.Insert(e);
t1.InOrder();
t1.Erase(10);
t1.Erase(14);
t1.Erase(13);
t1.InOrder();
for (auto e : a)
{
t1.Erase(e);
t1.InOrder();
}
//t1.InOrder();
}
void TestBSTree3()
{
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 19};
BSTree<int> t1;
for (auto e : a)
t1.InsertR(e);
t1.InOrder();
t1.EraseR(10);
t1.EraseR(14);
t1.Erase(13);
t1.InOrder();
for (auto e : a)
{
t1.Erase(e);
t1.InOrder();
}
//t1.InOrder();
}
void TestBSTree4()
{
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 19};
BSTree<int> t1;
for (auto e : a)
t1.InsertR(e);
t1.InOrder();
cout<<"===COPY==="<<endl;
BSTree<int> copy(t1);
BSTree<int> copy2=t1;
// t1.EraseR(10);
// t1.EraseR(14);
// t1.Erase(13);
copy.InOrder();
copy2.InOrder();
// for (auto e : a)
// {
// t1.Erase(e);
// t1.InOrder();
// }
//t1.InOrder();
}
int main()
{
TestBSTree4();
return 0;
}