相关博客
个人博客主页
目录
前言
二叉搜索树(Binary Search Tree,简称BST),在本篇博客我们用(BSTree)又称为二叉排序
总的来说,二叉搜索树是一种高效的数据结构,适用于需要进行频繁查找、插入和删除操作的场景。通过合理的设计和实现,可以充分发挥其性能优势。
一、pandas是什么?
树或二叉查找树,是一种特殊的二叉树数据结构。它或者是一颗空树,或者具有如下性质:
有序性:对于二叉搜索树的任意节点N,其左子树中的所有节点的值都小于节点N的值,其右子树中的所有节点的值都大于节点N的值。同时,N的左子树和右子树也分别为二叉搜索树。这一性质使得二叉搜索树在查找、插入和删除操作上具有较高的效率。
唯一性:二叉搜索树中不允许存在值相同的节点。若尝试插入与树中已存在节点值相同的节点,则插入操作将失败或返回错误提示。
应用广泛:二叉搜索树在数据结构和算法领域有着广泛的应用,是许多高级数据结构和算法(如AVL树、红黑树、B树、B+树等)的基础。同时,它也是实现如C++ STL中的map、set等容器的基础数据结构之一。
中序遍历:二叉搜索树的中序遍历(左子树-根节点-右子树)将得到一个按升序排列的节点值序列。这一性质使得二叉搜索树在需要快速排序或查找的场景下特别有用。
实现方式:二叉搜索树通常通过模板类或结构体来实现,其中包含节点的值、指向左子节点的指针和指向右子节点的指针。在此基础上,可以实现查找、插入和删除等操作。
性能分析:二叉搜索树的性能与其形状密切相关。在理想情况下(即树为完全二叉树或接近完全二叉树时),查找、插入和删除操作的时间复杂度均为O(logN),其中N为树中节点的数量。然而,在最坏情况下(即树退化为链表时),这些操作的时间复杂度将退化为O(N)。因此,在实际应用中,通常会采用一些平衡二叉树算法(如AVL树、红黑树等)来保持树的平衡性,从而提高性能。
二、二叉树的结构
1.二叉树节点的结构
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{
}
};
我们在学习二叉树树,我们的二叉树节点中存放的都是二叉树左右子树的地址,所以我们节点类里面放的树左子树地址和右子树的地址和一个key,我们的key运用了一个模板参数;
2.Node节点
代码如下(示例):
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree() = default;
BSTree(const BSTree<K>& k)
{
_root =Copy(k._root);
}
~BSTree()
{
Destroy(_root);
}
BSTree<K>& operator=(BSTree<K>& t )
{
swap(t._root, _root);
return *this;
}
private:
Node* _root = nullptr;
};
这里我们只写了构造函数和拷贝构造,还有析构函数,因为我们的是非内置类型,所以我们其他的可以不用写,因为我们的拷贝构造存在浅拷贝和深拷贝,所以我们需要自己写;构造函数数时,我们只用一个_root ,我们这里用了一个关键字default,它的意思是强制构造,我们也可以写一个构造将_root制空就可以了。
由于我们的拷贝构造和析构涉及到树的递归和删除,所以我们后面在展开说明。
三、增删查改
在二叉搜索树中我们研究的二叉搜索树的增删查改
1.增加节点创建树
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key == key)
{
parent = cur;
return false;
}
}
cur = new Node(key);
if (key < parent->_key)
{
parent->_left = cur;
}
else if (key > parent->_key)
{
parent->_right = cur;
}
return true;
}
递归代码实现
bool InsertR(Node*& root, const K& key)//*&是这个指针的别名可以修改这个地址,所对应的左指针或者右指针的别名
//如果是指针;就是变成了root指向一个新创建的Node,而root传值传过来的是空,
{
if (root == nullptr)
{
//等于空说明找到插入的位置了
//只需要修改root所指向的节点,也就是说只修了这里root的值,
//*&是修改了root原来所传递过来的左右指针
root = new Node(key);
return true;
}
if (root->_key > key)
{
return InsrtR(root->_left, key);
}
else if (root->_key < key)
{
return InsertR(root->_right, key);
}
else
{
return false;
}
}
这里的root是根节点,如果我们的root等于空,那么就说这颗树是空,那么我们就需要创建一个节点,这个节点里面放入key就可以了,然后返回true说明插入成功;
如果这棵树不是空那么我们插入的值比根小,那么就需要往左走,如果比根大,那么往右走,直到新的根左或者右为空,然后将我们创建的新的节点放链接上;
比如说我们这里是增加0进入,我们这个cur一直走到1的位置,然后如果0第一小我们就在这棵树的左边链接上0,比1大就在1的右边链接上0;
当我们用中序遍历这棵树的时候,我们会发现,这棵树是有序的,也就是 1 3 4 6 7 8 10 13 14;
2.树的遍历
void InOrder()
{
_InOrder(_root);
cout << endl;
}
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;
}
拷贝构造的Copy我们也就是将树遍历一遍,一个一个节点的拷贝给新的节点就可以了;
3.删除节点
我们在观察树中的节点时,发现我们删除节点有很多种情况:
a. 要删除的结点无孩子结点
1 .删除枝叶上的节点时,可以直接把它与父亲节点断开,然后delete,将它在父亲节的左或者右节点制空就可以
b. 要删除的结点只有左孩子结点
2.删除的节点上只有左节点或者右节点,当只有左节点时,我们将它删除,然后它的父亲节点所对应分支指向要删除的节点下一个指向的节点;
c. 要删除的结点只有右孩子结点
2.删除的节点上只有左节点或者右节点,当只有左节点时,我们将它删除,然后它的父亲节点所对应分支指向要删除的节点下一个指向的节点;
d. 要删除的结点有左、右孩子结点
3,删除的节点左右子树都有,那么就找到左子树中最大的数,或者右子树最小的数,然后将它的值赋值给要删除的节点,然后将左子树中最大的数,或者右子树最小的数的节点删除就可以了;
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点 中,再来处理该结点的删除问题--替换法删除
了;
循环代码实现:
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
//找到了删除
if (cur->_left == nullptr)
{
if (parent->_right == cur)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
delete cur;
}
else
{
Node* Max = cur->_left;
Node* temp = cur;
if (Max->_right == nullptr)
{
cur->_key = Max->_key;
cur->_left = Max->_left;
}
else
{
while (Max->_right)
{
temp = Max;
Max = Max->_right;
}
cur->_key = Max->_key;
temp->_right = Max->_left;
}
delete Max;
}
return true;
}
}
return false;
}
递归实现:
bool EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key > key)
{
return EraseR(root->_left, key);
}
else if (root->_key < key)
{
return EraseR(root->_right, key);
}
else
{
//删除
Node* temp = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* Max = root->_left;
//左右都不为空,那么就找到root左边最小的数,或者右边最大的数
while (Max->_right)
{
Max = Max->_right;
}
swap(root->_key, Max->_key);
return EraseR(root->_left, key);
}
delete temp;
return true;
}
}
我们学习完删除的概念后,我们就可以将析构函数了,我们就只需要将这颗树种的节点一个一个的删除就可以了;
void Destroy(Node*& root)
{
if (root == nullptr)
{
return ;
}
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
4.查找
这里的查找和以前不一样了,以前我们要查找一个数是遍历整棵树,现在我们只需要通过我们现学的规律,不节根节点大,我们我们就在右支找,比根小,就在左支找;
代码实现
bool Find(const Node& 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 FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key == key)
{
return true;
}
else if (root->_key > key)
{
return FindR(root->_left, key);
}
else
{
return FindR(root->_right, key);
}
}
5.修改节点的值
bool Edit(const K& key,const K& Newkey)
{
Node* root=Find(key);
if (root == nullptr)
{
return false;
}
else
{
root->_key = Newkey;
return true;
}
}
四、代码总结
#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() = default;
BSTree(const BSTree<K>& k)
{
_root =Copy(k._root);
}
~BSTree()
{
Destroy(_root);
}
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key == key)
{
parent = cur;
return false;
}
}
cur = new Node(key);
if (key < parent->_key)
{
parent->_left = cur;
}
else if (key > parent->_key)
{
parent->_right = cur;
}
return true;
}
bool Find(const Node& 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;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
bool Edit(const K& key,const K& Newkey)
{
Node* root=Find(key);
if (root == nullptr)
{
return false;
}
else
{
root->_key = Newkey;
return true;
}
}
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
//找到了删除
if (cur->_left == nullptr)
{
if (parent->_right == cur)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
delete cur;
}
else
{
Node* Max = cur->_left;
Node* temp = cur;
if (Max->_right == nullptr)
{
cur->_key = Max->_key;
cur->_left = Max->_left;
}
else
{
while (Max->_right)
{
temp = Max;
Max = Max->_right;
}
cur->_key = Max->_key;
temp->_right = Max->_left;
}
delete Max;
}
return true;
}
}
return false;
}
protected:
bool FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key == key)
{
return true;
}
else if (root->_key > key)
{
return FindR(root->_left, key);
}
else
{
return FindR(root->_right, key);
}
}
bool EraseR(const K& key)
{
return EraseR(_root, key);
}
bool EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key > key)
{
return EraseR(root->_left, key);
}
else if (root->_key < key)
{
return EraseR(root->_right, key);
}
else
{
//删除
Node* temp = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* Max = root->_left;
//左右都不为空,那么就找到root左边最小的数,或者右边最大的数
while (Max->_right)
{
Max = Max->_right;
}
swap(root->_key, Max->_key);
return EraseR(root->_left, key);
}
delete temp;
return true;
}
}
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<K>& operator=(BSTree<K>& t )
{
swap(t._root, _root);
return *this;
}
void Destroy(Node*& root)
{
if (root == nullptr)
{
return ;
}
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
bool InsertR(const K& key)
{
return InsertR(_root, key);
}
bool InsertR(Node*& root, const K& key)//*&是这个指针的别名可以修改这个地址,所对应的左指针或者右指针的别名
//如果是指针;就是变成了root指向一个新创建的Node,而root传值传过来的是空,
{
if (root == nullptr)
{
//等于空说明找到插入的位置了
//只需要修改root所指向的节点,也就是说只修了这里root的值,
//*&是修改了root原来所传递过来的左右指针
root = new Node(key);
return true;
}
if (root->_key > key)
{
return InsrtR(root->_left, key);
}
else if (root->_key < key)
{
return InsertR(root->_right, key);
}
else
{
return false;
}
}
private:
Node* _root = nullptr;
};
五、二叉搜索树的应用
1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到 的值。 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下: 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。该种方 式在现实生活中非常常见: 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文就构成一种键值对; 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是就构成一种键值对。
1.kv模型代码
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
BSTree()
:_root(nullptr)
{}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
Node* FindR(const K& key)
{
return _FindR(_root, key);
}
bool InsertR(const K& key, const V& value)
{
return _InsertR(_root, key, value);
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
private:
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;
// 1、左为空
// 2、右为空
// 3、左右都不为空
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* leftMax = root->_left;
while (leftMax->_right)
{
leftMax = leftMax->_right;
}
swap(root->_key, leftMax->_key);
return _EraseR(root->_left, key);
}
delete del;
return true;
}
}
bool _InsertR(Node*& root, const K& key, const V& value)
{
if (root == nullptr)
{
root = new Node(key, value);
return true;
}
if (root->_key < key)
{
return _InsertR(root->_right, key, value);
}
else if (root->_key > key)
{
return _InsertR(root->_left, key, value);
}
else
{
return false;
}
}
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
return nullptr;
if (root->_key < key)
{
return _FindR(root->_right, key);
}
else if (root->_key > key)
{
return _FindR(root->_left, key);
}
else
{
return root;
}
}
void _InOrder(Node* root)
{
if (root == NULL)
{
return;
}
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
private:
Node* _root;
};
总结
1.二叉搜索的增删查改
2.二叉树的应用