一、与二叉排序树有关的定义:
1、二叉树是一种每个结点最多只能有两个孩子的树。
2、每一个子集本身又是一棵符合定义的树,叫做根节点的子树。
3、每一棵子树的根叫做根 r 的孩子,而 r 是每一棵子树的根的双亲。
4、结点的度是指结点具有的子树(孩子)的个数。度为0的结点称为叶结点。
5、二叉树排序树除满足二叉树的所有属性外,还满足:
(1)若左子树非空,则左子树上所有结点的值均小于根结点的值;
(2)若右子树非空,则右子树上所有结点的值均大于根结点的值;
(3)左、右子树本身也都分别是二叉排序树。
下图就是一个二叉排序树:
二、二叉排序树的基本操作
1、基本数据结构
typedef struct node {
int data;
struct node* left, * right;
}BSTNode;
2、 二叉排序树的查找
在二叉排序树上进行查找的过程为:若二叉排序树非空,则将 n(要查找的数据)与根结点的数进行比较。若二者相等,则查找成功;若当前结点数值小于 n ,则进入根结点的右子树继续查找;若当前结点数值大于 n ,则进入根结点的左子树继续查找;若当前结点为空,则查找不成功。下面是查找的两种具体方法:
(1)递归方法:
//二叉排序树的查找(递归)
BSTNode* BSTFind1(BSTNode* T, int n)
{
if (T == NULL) {
//查找不成功
return NULL;
}
else if (T->data == n) {
//查找成功
return T;
}
else if (T->data < n) {
//进入右子树继续查找
return BSTFind1(T->right, n);
}
else {
//进入左子树继续查找
return BSTFind1(T->left, n);
}
}
(2)非递归方法:
//二叉排序树的查找(非递归)
BSTNode* BSTFind2(BSTNode* T, int n)
{
BSTNode* p = T;
while (p != NULL) {
if (p->data == n) {
//查找成功
return p;
}
else if (p->data < n) {
//如果当前访问结点小于 n
p = p->right; //继续到右分支查找
}
else {
//如果当前访问结点大于 n
p = p->left; //继续到左分支查找
}
}
return NULL; //查找不成功
}
2、二叉排序树的插入
当二叉排序树中不存在某个给定值的结点时,可以进行插入操作。新插入的结点一定是叶结点,而且是查找不成功时的查找路径上访问的最后一个结点的左孩子或右孩子结点。因此插入是在查找的过程中进行的。下面是插入的两种具体方法:
(1)递归方法
//二叉排序树的插入(递归)
BSTNode* BSTInsert1(BSTNode* T, int n)
{
if (T == NULL) {
//遇到空指针,说明查找不成功,可以进行插入
T = (BSTNode*)malloc(sizeof(BSTNode));
T->data = n;
//新结点一定是叶子结点,左右子树均为空
T->left = NULL;
T->right = NULL;
return T;
}
else if (T->data == n) {
cout << "该数据已存在,插入失败" << endl;
return T;
}
else if (T->data < n) {
//如果n大于根结点
T->right = BSTInsert1(T->right, n); //进入右分支进行插入
}
else {
//如果n小于根结点
T->left = BSTInsert1(T->left, n); //进入左分支进行插入
}
return T;
}
(2)非递归方法
//二叉排序树的插入(非递归)
BSTNode* BSTInsert2(BSTNode* T, int n)
{
BSTNode* p = T, * q = NULL;
//1、找到待插入的位置
while (p != NULL) {
if (p->data == n) {
cout << "该数据已存在,插入失败" << endl;
return T;
}
else if (p->data < n) {
//如果当前访问结点小于 n
q = p; //更新q
p = p->right; //继续到右分支查找
}
else {
//如果当前访问结点大于 n
q = p; //更新q
p = p->left; //继续到左分支查找
}
}
//2、插入新结点
p = (BSTNode*)malloc(sizeof(BSTNode));
p->data = n;
//新结点一定是叶子结点,左右子树均为空
p->left = NULL;
p->right = NULL;
if (q == NULL) {
//如果原先的树为空,则新插入的结点就是根结点
return p;
}
if (q->data < n) {
//如果原树非空,且n大于查找路径上访问的最后一个结点
q->right = p; //将新结点插入最后一个结点右孩子位置
}
else {
//如果原树非空,且n小于查找路径上访问的最后一个结点
q->left = p; //将新结点插入最后一个结点左孩子位置
}
return T;
}
3、二叉排序树的删除
在二叉排序树中删除一个结点 p 要分为三种情况来讨论:(1)结点 p 的度等于零,即 p 是叶结点。这种情况我们采取直接删除的方法;(2)结点 p 的度等于1,即结点 p 只有左孩子或只有右孩子。这种情况我们用结点 p 的子树顶替 p 的位置,再将 p 删除;(3)结点 p 的度等于2。这种情况下有两种删除方式,一种是寻找待删除结点 p 左子树的最右端(最大)结点,用它覆盖 p ,再将这个最右端结点删除。另一种是寻找待删除结点 p 右子树的最左端(最小)结点,用它覆盖 p ,再将这个最左端结点删除。下面是具体代码:(最后一种情况,这里采用了第一个方案)
//二叉排序树的删除
BSTNode* BSTDelete(BSTNode* T, int n)
{
//p1指向待删除的结点,q1是其父结点
BSTNode* p1 = T, * q1 = NULL;
//1、查找n所在结点,确定待删除结点及其父结点指向
while (p1 != NULL) {
if (p1->data == n) {
break;
}
else if (p1->data < n) {
q1 = p1;
p1 = p1->right;
}
else {
q1 = p1;
p1 = p1->left;
}
}
if (p1 == NULL) {
cout << "该数据不存在,删除失败" << endl;
return T;
}
//情况1:待删除结点p1为叶结点
if (p1->left == NULL && p1->right == NULL)
{
if (q1 != NULL) {
//如果待删除的结点不是根结点
if (q1->data < n) {
q1->right = NULL; //直接删除
}
else {
q1->left = NULL; //直接删除
}
free(p1);
return T;
}
else {
//如果待删除的结点是根结点
free(p1);
return NULL;
}
}
//情况2、待删除结点p1只有左孩子
if (p1->left != NULL && p1->right == NULL)
{
if (q1 != NULL) {
//如果待删除的结点不是根结点
if (q1->data < n) {
q1->right = p1->left; //p1的左孩子上移顶替
}
else {
q1->left = p1->left; //p1的左孩子上移顶替
}
free(p1);
return T;
}
else {
//如果待删除的结点是根结点
T = p1->left;
free(p1);
return NULL;
}
}
//情况3、待删除结点p1只有右孩子
if (p1->left == NULL && p1->right != NULL)
{
if (q1 != NULL) {
//如果待删除的结点不是根结点
if (q1->data < n) {
q1->right = p1->right; //p1的右孩子上移顶替
}
else {
q1->left = p1->right; //p1的右孩子上移顶替
}
free(p1);
return T;
}
else {
//如果待删除的结点是根结点
T = p1->right;
free(p1);
return NULL;
}
}
//情况4、待删除结点p1既有左孩子也有右孩子
if (p1->left != NULL && p1->right != NULL)
{
//p2指向p1左子树的最右端(最大)结点,q2是其父结点
BSTNode* p2 = p1->left, * q2 = p1;
while (p2->right != NULL) {
//寻找待删除结点左子树的最右端(最大)结点
q2 = p2;
p2 = p2->right;
}
p1->data = p2->data; //用最右端结点覆盖待删除的结点
//删除该最右端结点p2
if (p2->left == NULL && p2->right == NULL) {
//如果p2是叶结点
if (q2->data < n) {
q2->right = NULL; //直接删除
}
else {
q2->left = NULL; //直接删除
}
free(p2);
}
else {
//如果最右端结点只有左孩子
if (q2->data < n) {
q2->right = p2->left; //p2的左孩子上移顶替
}
else {
q2->left = p2->left; //p2的左孩子上移顶替
}
free(p2);
}
return T;
}
}
4、二叉排序树的遍历输出
这里我们直接采用二叉树的中序遍历(不知道的小伙伴可以去看我之前的文章),即可将数据按从小到大的顺序输出。
//打印二叉排序树
void PrintBST(BSTNode* T)
{
//中序遍历,可将数据按从小到大的顺序输出
if (T != NULL) {
PrintBST(T->left); //中序遍历左子树
cout << T->data << " "; //访问根结点
PrintBST(T->right); //中序遍历右子树
}
}
三、小结:
对于二叉排序树的操作,通常都有递归和非递归两种,但在大多数情况下建议使用非递归,因为非递归方法运行更快,占用的时间更少。此外,二叉排序树的删除,要充分考虑各种情况,以避免出现Bug。
全部代码:
# include <iostream>
# include <stdlib.h>
using namespace std;
typedef struct node {
int data;
struct node* left, * right;
}BSTNode;
//二叉排序树的查找(递归)
BSTNode* BSTFind1(BSTNode* T, int n);
//二叉排序树的查找(非递归)
BSTNode* BSTFind2(BSTNode* T, int n);
//二叉排序树的插入(递归)
BSTNode* BSTInsert1(BSTNode* T, int n);
//二叉排序树的插入(非递归)
BSTNode* BSTInsert2(BSTNode* T, int n);
//二叉排序树的删除
BSTNode* BSTDelete(BSTNode* T, int n);
//打印二叉排序树
void PrintBST(BSTNode* T);
//销毁(基于后序顺序)
void DestroyBST(BSTNode* T);
int main()
{
int N, n;
BSTNode* T = NULL, * p;
cout << "请输入初始数据的个数:";
cin >> N;
cout << "请输入初始数据:";
for (int i = 0; i < N; i++) {
cin >> n;
T = BSTInsert2(T, n);
}
PrintBST(T);
cout << endl;
cout << "\n(递归)请输入要查找的数据:";
cin >> n;
p = BSTFind1(T, n);
if (p == NULL) {
cout << "数据不存在,查找失败" << endl;
}
else {
cout << p->data << "查找成功" << endl;
}
cout << "\n(非递归)请输入要查找的数据:";
cin >> n;
p = BSTFind2(T, n);
if (p == NULL) {
cout << "数据不存在,查找失败" << endl;
}
else {
cout << p->data << "查找成功" << endl;
}
cout << "\n请输入要插入的数据:";
cin >> n;
T = BSTInsert1(T, n);
cout << "递归插入后的二叉排序树:";
PrintBST(T);
cout << endl;
cout << "\n请输入要插入的数据:";
cin >> n;
T = BSTInsert2(T, n);
cout << "非递归插入后的二叉排序树:";
PrintBST(T);
cout << endl;
cout << "\n请输入要删除的数据:";
cin >> n;
T = BSTDelete(T, n);
cout << "删除后的二叉排序树:";
PrintBST(T);
cout << endl;
DestroyBST(T);
return 0;
}
//二叉排序树的查找(递归)
BSTNode* BSTFind1(BSTNode* T, int n)
{
if (T == NULL) {
//查找不成功
return NULL;
}
else if (T->data == n) {
//查找成功
return T;
}
else if (T->data < n) {
//进入右子树继续查找
return BSTFind1(T->right, n);
}
else {
//进入左子树继续查找
return BSTFind1(T->left, n);
}
}
//二叉排序树的查找(非递归)
BSTNode* BSTFind2(BSTNode* T, int n)
{
BSTNode* p = T;
while (p != NULL) {
if (p->data == n) {
//查找成功
return p;
}
else if (p->data < n) {
//如果当前访问结点小于 n
p = p->right; //继续到右分支查找
}
else {
//如果当前访问结点大于 n
p = p->left; //继续到左分支查找
}
}
return NULL; //查找不成功
}
//二叉排序树的插入(递归)
BSTNode* BSTInsert1(BSTNode* T, int n)
{
if (T == NULL) {
//遇到空指针,说明查找不成功,可以进行插入
T = (BSTNode*)malloc(sizeof(BSTNode));
T->data = n;
//新结点一定是叶子结点,左右子树均为空
T->left = NULL;
T->right = NULL;
return T;
}
else if (T->data == n) {
cout << "该数据已存在,插入失败" << endl;
return T;
}
else if (T->data < n) {
//如果n大于根结点
T->right = BSTInsert1(T->right, n); //进入右分支进行插入
}
else {
//如果n小于根结点
T->left = BSTInsert1(T->left, n); //进入左分支进行插入
}
return T;
}
//二叉排序树的插入(非递归)
BSTNode* BSTInsert2(BSTNode* T, int n)
{
BSTNode* p = T, * q = NULL;
//1、找到待插入的位置
while (p != NULL) {
if (p->data == n) {
cout << "该数据已存在,插入失败" << endl;
return T;
}
else if (p->data < n) {
//如果当前访问结点小于 n
q = p; //更新q
p = p->right; //继续到右分支查找
}
else {
//如果当前访问结点大于 n
q = p; //更新q
p = p->left; //继续到左分支查找
}
}
//2、插入新结点
p = (BSTNode*)malloc(sizeof(BSTNode));
p->data = n;
//新结点一定是叶子结点,左右子树均为空
p->left = NULL;
p->right = NULL;
if (q == NULL) {
//如果原先的树为空,则新插入的结点就是根结点
return p;
}
if (q->data < n) {
//如果原树非空,且n大于查找路径上访问的最后一个结点
q->right = p; //将新结点插入最后一个结点右孩子位置
}
else {
//如果原树非空,且n小于查找路径上访问的最后一个结点
q->left = p; //将新结点插入最后一个结点左孩子位置
}
return T;
}
//二叉排序树的删除
BSTNode* BSTDelete(BSTNode* T, int n)
{
//p1指向待删除的结点,q1是其父结点
BSTNode* p1 = T, * q1 = NULL;
//1、查找n所在结点,确定待删除结点及其父结点指向
while (p1 != NULL) {
if (p1->data == n) {
break;
}
else if (p1->data < n) {
q1 = p1;
p1 = p1->right;
}
else {
q1 = p1;
p1 = p1->left;
}
}
if (p1 == NULL) {
cout << "该数据不存在,删除失败" << endl;
return T;
}
//情况1:待删除结点p1为叶结点
if (p1->left == NULL && p1->right == NULL)
{
if (q1 != NULL) {
//如果待删除的结点不是根结点
if (q1->data < n) {
q1->right = NULL; //直接删除
}
else {
q1->left = NULL; //直接删除
}
free(p1);
return T;
}
else {
//如果待删除的结点是根结点
free(p1);
return NULL;
}
}
//情况2、待删除结点p1只有左孩子
if (p1->left != NULL && p1->right == NULL)
{
if (q1 != NULL) {
//如果待删除的结点不是根结点
if (q1->data < n) {
q1->right = p1->left; //p1的左孩子上移顶替
}
else {
q1->left = p1->left; //p1的左孩子上移顶替
}
free(p1);
return T;
}
else {
//如果待删除的结点是根结点
T = p1->left;
free(p1);
return NULL;
}
}
//情况3、待删除结点p1只有右孩子
if (p1->left == NULL && p1->right != NULL)
{
if (q1 != NULL) {
//如果待删除的结点不是根结点
if (q1->data < n) {
q1->right = p1->right; //p1的右孩子上移顶替
}
else {
q1->left = p1->right; //p1的右孩子上移顶替
}
free(p1);
return T;
}
else {
//如果待删除的结点是根结点
T = p1->right;
free(p1);
return NULL;
}
}
//情况4、待删除结点p1既有左孩子也有右孩子
if (p1->left != NULL && p1->right != NULL)
{
//p2指向p1左子树的最右端(最大)结点,q2是其父结点
BSTNode* p2 = p1->left, * q2 = p1;
while (p2->right != NULL) {
//寻找待删除结点左子树的最右端(最大)结点
q2 = p2;
p2 = p2->right;
}
p1->data = p2->data; //用最右端结点覆盖待删除的结点
//删除该最右端结点p2
if (p2->left == NULL && p2->right == NULL) {
//如果p2是叶结点
if (q2->data < n) {
q2->right = NULL; //直接删除
}
else {
q2->left = NULL; //直接删除
}
free(p2);
}
else {
//如果最右端结点只有左孩子
if (q2->data < n) {
q2->right = p2->left; //p2的左孩子上移顶替
}
else {
q2->left = p2->left; //p2的左孩子上移顶替
}
free(p2);
}
return T;
}
}
//打印二叉排序树
void PrintBST(BSTNode* T)
{
//中序遍历,可将数据按从小到大的顺序输出
if (T != NULL) {
PrintBST(T->left); //中序遍历左子树
cout << T->data << " "; //访问根结点
PrintBST(T->right); //中序遍历右子树
}
}
//销毁(基于后序顺序)
void DestroyBST(BSTNode* T)
{
if (T) {
DestroyBST(T->left); //销毁左子树
DestroyBST(T->right); //销毁右子树
free(T);
}
}
运行结果:
请输入初始数据的个数:6
请输入初始数据:10 12 6 9 3 7
3 6 7 9 10 12
(递归)请输入要查找的数据:9
9查找成功
(非递归)请输入要查找的数据:11
数据不存在,查找失败
请输入要插入的数据:8
递归插入后的二叉排序树:3 6 7 8 9 10 12
请输入要插入的数据:13
非递归插入后的二叉排序树:3 6 7 8 9 10 12 13
请输入要删除的数据:10
删除后的二叉排序树:3 6 7 8 9 12 13