目录
1、二叉搜索树的概念
2、二叉搜索树的插入
3、二叉搜索树的查找
4、二叉搜索树的删除
5、二叉搜索树的拷贝构造与析构
前言:
二叉搜索树是一颗二叉树,他跟普通的二叉树的区别在于:二叉搜索树的节点是按照特定规则进行摆放的。二叉搜索树的优势在于:无特殊情况下,其增删查改数据的时间复杂度可以达到O(log N),这个效率相比于线性表的效率,前者明显高出很多。
1、二叉搜索树的概念
二叉搜索树具备搜索功能的同时还可以通过中序遍历进行排序,因此二叉搜索树也称二叉排序树。他的具体性质如下:
1、左子树所有节点的值都小于根节点的值。
2、右子树所有节点的值都大于根节点的值。
3、左子树和右子树本身也同样满足上面两个性质。
4、树中不能出现相同值的节点。
二叉搜索树示意图如下:
2、二叉搜索树的插入
若要插入新的节点,则需遵循二叉树的性质,若插入节点的数值比根结点大则往右子树遍历,若插入节点的数值比根结点小则往左子树遍历,如此反复遍历,直到遇到nullptr,说明已经走到了插入点的位置,插入的时候还需要把该节点和父节点进行连接。若插入的数据和树里数据相同,则不能插入。
插入代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
template<class K>
struct BSTree_node//节点对象
{
BSTree_node<K>* left;
BSTree_node<K>* right;
K key;
BSTree_node(const K& k)
:left(nullptr)
, right(nullptr)
, key(k)
{}
};
template<class K>
class BSTree//树对象
{
public:
typedef BSTree_node<K> node;
void _Insert(const K& k)//子函数,方便外部调用
{
Insert(_root, k);
}
bool Insert(node*& root, const K& k)//递归实现插入函数
{
if (root == nullptr)//找到空处,因为root是引用,因此可以直接插入新节点
{
root = new node(k);
return true;
}
//按照左边找小,右边找大遍历树
if (k > root->key)
{
Insert(root->right, k);
}
else if (k < root->key)
{
Insert(root->left, k);
}
else//相同则不会插入
return false;
}
//打印函数
void _show()
{
show(_root);
}
void show(node* root)//中序遍历打印
{
if (root == nullptr)
{
return;
}
show(root->left);
cout << root->key << " ";
show(root->right);
}
private:
node* _root = nullptr;
};
int main()
{
BSTree<int> bt1;
int arr[] = { 8,8,3,1,10,6,4,7,14,13 };
for (auto num : arr)
{
bt1._Insert(num);
}
bt1._show();
return 0;
}
运行结果:
3、二叉搜索树的查找
二叉搜索树的查找逻辑和插入逻辑相似,从根节点开始找,若要查找的节点数值大于根结点则往右子树遍历,反之往左子树遍历,若找到了则返回真,没找到则返回假。
查找函数代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
template<class K>
struct BSTree_node//节点对象
{
BSTree_node<K>* left;
BSTree_node<K>* right;
K key;
BSTree_node(const K& k)
:left(nullptr)
, right(nullptr)
, key(k)
{}
};
template<class K>
class BSTree//树对象
{
public:
typedef BSTree_node<K> node;
void _Insert(const K& k)//子函数,方便外部调用
{
Insert(_root, k);
}
bool Insert(node*& root, const K& k)//递归实现插入函数
{
if (root == nullptr)//找到空处,因为root是引用,因此可以直接插入新节点
{
root = new node(k);
return true;
}
//按照左边找小,右边找大遍历树
if (k > root->key)
{
Insert(root->right, k);
}
else if (k < root->key)
{
Insert(root->left, k);
}
else//相同则不会插入
return false;
}
//查找函数
bool _find(const K& k)
{
return find(_root, k);
}
bool find(const node* root, const K& k)
{
if (root == nullptr)
{
return false;
}
if (k > root->key)
{
find(root->right, k);
}
else if (k < root->key)
{
find(root->left, k);
}
else
return true;
}
private:
node* _root = nullptr;
};
int main()
{
BSTree<int> bt1;
int arr[] = { 8,8,3,1,10,6,4,7,14,13 };
for (auto num : arr)
{
bt1._Insert(num);
}
cout << bt1._find(10) << endl;//找到了返回1
cout << bt1._find(1) << endl;//找到了返回1
cout << bt1._find(100) << endl;//没找到返回0
return 0;
}
4、二叉搜索树的删除
删除逻辑较插入和查找更复杂,需要具体分析:删除节点的情况无论如何只有以下四种:
1、删除节点没有孩子节点,即要删除的节点是叶子节点。
2、删除节点只有左孩子,即右孩子为空。
3、删除节点只有右孩子,即左孩子为空。
4、删除节点即有左孩子也有右孩子。
一个节点只虽然有以上四种情况,但是删除的时候只需要考虑两种情况:
情况一:上述情况1、2、3可以综合成一种情况,因为删除该类情况的节点时,只需要把该节点中不为空的孩子节点与该节点的父母节点相连接即可,该方法也可以处理没有孩子节点的情况,举例:如果一个节点的左孩子为空,那么不管右孩子是否为空都将右孩子与该节点的父母节点相连接。
情况二:上述情况4可以归成情况二,如果删除的节点同时拥有左孩子和右孩子,那么把该节点当作根节点,去该节点的左子树找到一个最大值,或者去该节点的右子树找到一个最小值,然后与该节点进行交换,交换过后要删除的节点就不再是根节点了,也意味着该节点的情况不再符合情况4,而符合上述的情况1、2、3,这时候可以用情况一的处理方式来删除该节点。
情况一示意图:
情况二示意图:
删除函数代码实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
template<class K>
struct BSTree_node//节点对象
{
BSTree_node<K>* left;
BSTree_node<K>* right;
K key;
BSTree_node(const K& k)
:left(nullptr)
, right(nullptr)
, key(k)
{}
};
template<class K>
class BSTree//树对象
{
public:
typedef BSTree_node<K> node;
void _Insert(const K& k)//子函数,方便外部调用
{
Insert(_root, k);
}
bool Insert(node*& root, const K& k)//递归实现插入函数
{
if (root == nullptr)//找到空处,因为root是引用,因此可以直接插入新节点
{
root = new node(k);
return true;
}
//按照左边找小,右边找大遍历树
if (k > root->key)
{
Insert(root->right, k);
}
else if (k < root->key)
{
Insert(root->left, k);
}
else//相同则不会插入
return false;
}
//删除函数
bool _erase(const K& k)
{
return erase(_root, k);
}
bool erase(node*& root, const K& k)
{
if (root == nullptr)
{
return false;
}
if (k > root->key)//遍历找到要删除的节点
return erase(root->right, k);
else if (k < root->key)//遍历找到要删除的节点
return erase(root->left, k);
else
{
node* del = root;//记录要删除节点的位置
//情况一:可以直接做删除处理
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(leftmax->key, root->key);//交换
return erase(root->left, leftmax->key);//这里递归的作用是为了让被删除节点的父节点和其孩子相连
}
delete del;
}
return true;
}
void _show()
{
show(_root);
}
void show(node* root)
{
if (root == nullptr)
{
return;
}
show(root->left);
cout << root->key << " ";
show(root->right);
}
private:
node* _root = nullptr;
};
int main()
{
BSTree<int> bt1;
int arr[] = { 8,8,3,1,10,6,4,7,14,13 };
for (auto num : arr)
{
bt1._Insert(num);
}
bt1._show();
cout << endl;
bt1._erase(7);
bt1._erase(13);
bt1._show();
return 0;
}
运行结果:
5、二叉搜索树的拷贝构造与析构
二叉树涉及到空间资源管理,因此拷贝时要采用深拷贝,具体逻辑:先创建好节点,最后返回的时候再将他们连接。析构也同理,需要使用递归一层一层的将各个节点释放。
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
template<class K>
struct BSTree_node//节点对象
{
BSTree_node<K>* left;
BSTree_node<K>* right;
K key;
BSTree_node(const K& k)
:left(nullptr)
, right(nullptr)
, key(k)
{}
};
template<class K>
class BSTree//树对象
{
public:
typedef BSTree_node<K> node;
BSTree() = default;//强行生成默认构造
BSTree(const BSTree& bt)//拷贝构造
{
_root = copy(bt._root);
}
node* copy(const node* root)
{
if (root == nullptr)
{
return nullptr;
}
node* newnode = new node(root->key);//new一个和root一样值的节点
newnode->left = copy(root->left);
newnode->right = copy(root->right);
return newnode;//返回节点达到链接效果
}
void _Insert(const K& k)//子函数,方便外部调用
{
Insert(_root, k);
}
bool Insert(node*& root, const K& k)//递归实现插入函数
{
if (root == nullptr)//找到空处,因为root是引用,因此可以直接插入新节点
{
root = new node(k);
return true;
}
//按照左边找小,右边找大遍历树
if (k > root->key)
{
Insert(root->right, k);
}
else if (k < root->key)
{
Insert(root->left, k);
}
else//相同则不会插入
return false;
}
void _show()
{
show(_root);
}
void show(node* root)
{
if (root == nullptr)
{
return;
}
show(root->left);
cout << root->key << " ";
show(root->right);
}
//析构
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
void Destroy(node* root)
{
if (root == nullptr)
return;
Destroy(root->left);
Destroy(root->right);
delete root;
}
private:
node* _root = nullptr;
};
int main()
{
BSTree<int> bt1;
int arr[] = { 8,8,3,1,10,6,4,7,14,13 };
for (auto num : arr)
{
bt1._Insert(num);
}
BSTree<int> bt2(bt1);//拷贝构造bt2
bt2._show();//打印bt2的内容
return 0;
}
运行结果: