二叉排序树基本操作(链表实现)(有错误)
目录
一、二叉排序树的定义
对于一棵空的二叉树或者具有如下性质的二叉树:
1.若其左子树不为空,则左子树所有结点的值均小于根结点的值。
2.若其右子树不为空,则右子树所有结点的值均大于根结点的值。
3.其左右子树也是二叉排序树。
例如:
二、基本操作:
1、插入
当root为空时直接插入。
若root不为空,模拟查找的过程找到其该插入的位置,然后插入。
BiNode* BiSortTree::InsertBST(BiNode* bt, datatype x)
{
if (bt == NULL)
{
BiNode* s = new BiNode;
s->data = x;
s->lchild = s->rchild = NULL;
bt = s;
return bt;
}
else if (bt->data > x)
{
if (bt->lchild == NULL)bt->lchild = InsertBST(bt->lchild, x);
else InsertBST(bt->lchild, x);
}
else
{
if (bt->rchild == NULL)bt->rchild = InsertBST(bt->rchild, x);
else InsertBST(bt->rchild, x);
}
}
2、创建
其实就是一个不断插入的过程。
BiSortTree::BiSortTree(int a[], int n)
{
root = NULL;
for (int i = 0;i < n;i++)
{
if (i == 0) root = InsertBST(root, a[i]);//在插入操作时,当root节点为空时分配根节点空间,并且root的地址不会改变。
else InsertBST(root, a[i]);
}
}
3、删除
1.当删除的结点是叶子结点,直接删除即可。
下图中需要删除的结点为p所指,f为其双亲结点。
2.被删除的结点只有左子树或右子树,让当前结点的左孩子或右孩子(不为空的那个)指向其左孩子或右孩子的儿子。
3.被删除的结点既有左子树又有右子树,找到左子树的最大值,与当前要删除的结点替换值,并删除之前找到的左子树的最大值。
有一个特殊情况,当左子树中的最大值结点是被删除结点的孩子。
步骤:找到要删除节点 -> 根据该节点的左右子树情况判断如何删除
bool Delete(Tree &p) //删除该节点
{
/* 从二叉排序树中删除节点p, 并重接它的左或右子树 */
Tree q, s; /* q是辅助指针,s指向直接前驱 */
if( !p->left && !p->right ) /* p为叶子节点 */
p = NULL;
else if( !p->left ) /* 左子树为空,重接右子树 */
{
q = p;
p = p->right;
free(q);
}
else if( !p->right ) /* 右子树为空,重接左子树 */
{
q = p;
p = p->left;
free(q);
}
else /* 左右子树均不为空 */
{
q = p;
s = p->left;
while(s->right) /* 转左,然后向右走到尽头 */
{
q = s;
s = s->right;
}
p->val = s->val;
if( q != p ) /* 判断是否执行上述while循环 */
q->right = s->left; /* 执行上述while循环,重接右子树 */
else
q->left = s->left; /* 未执行上述while循环,重接左子树 */
free(s);
}
return true;
}
bool DeleteBST(Tree &T, int key) //找到要删除的节点
{
/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素节点 */
/* 并返回TRUE;否则返回FALSE */
if( T == NULL)
{
cout << "要删除节点不存在" << endl;
return false; /* 不存在关键字等于key的数据元素 */
}
else
{
if( key == T->val )
Delete(T);
else if( key < T->val)
return DeleteBST(T->left, key);
else
return DeleteBST(T->right, key);
}
return true;
}
4、获得某一个节点所在的层次
根节点的层次为1
Tree getLevel(Tree &t, int key, int &level) //查找某个节点所在的层次
{
if(t == NULL)
{
cout << "没找到该节点!" << endl;
return NULL;
}
if(t->val == key) return t;
else
{
level ++;
if(key < t->val) return getLevel(t->left, key, level);
else return getLevel(t->right, key, level);
}
}
总结
二叉排序树
一、创建
不断插入的过程,但要注意在创建开始把树置为NULL
因为插入一个新节点就是找到一个NULL节点插入二、插入 -- 递归
1.如果树为空,直接创建新节点插入
2.树不为空
(1).如果要插入节点值小于根节点,递归插入左子树
(2).如果要插入节点值大于根节点,递归插入右子树三、查找 -- 递归
1.如果树为空,返回NULL
2.树不为空
(1)如果查找节点值等于根节点,返回
(2)如果查找节点值小于根节点,递归查找左子树
(3)如果查找节点值大于根节点,递归查找右子树四、删除 -- 递归
1.查找要删除节点
(1).如果节点不存在,返回false
(2).如果节点值等于根节点值,删除根节点
(3).如果节点值大于根节点值,递归右子树
(4).如果节点值小于根节点值,递归左子树
2.删除节点 -- 三指针p,q,s
p指向删除节点,s指向删除节点的前驱节点,q是辅助指针
(1)如果该节点没有左右孩子,即是叶子节点,直接置为NULL
(2)如果该节点没有左孩子或右孩子,上移子树即可
①如果没有左孩子,让该节点的右孩子上移代替该节点
②如果没有右孩子,让该节点的左孩子上移代替该节点
(3)如果该节点有左右孩子,用该节点的直接前驱或者直接后继替换该节点
我们这里使用直接前驱,左子树的递归右子树
①如果该该节点的左子树有右子树,用右子树的值替换该节点
显而易见,该直接前驱肯定没有右子树,但是可能有左子树
所以我们需要把s的左子树给q的右子树,因为s的左子树的值肯定比q的值要大
②如果该节点的左子树没有右子树,直接让该节点的左子树替换该节点
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef struct tree
{
int val;
struct tree *left, *right;
}*Tree, TreeNode;
//声明
void Create(Tree &t);
void InOderTraverse(Tree &t);
Tree Search(Tree t, int key);
void Insert(Tree &t, int key);
bool Delete(Tree &p);
bool DeleteBST(Tree &T, int key);
//实现
void InOderTraverse(Tree &t) //中序递归遍历二叉树
{
if(t != NULL)
{
InOderTraverse(t->left);
printf("%d ", t->val);
InOderTraverse(t->right);
}
}
Tree Search(Tree t, int key) //递归查找
{
if(t == NULL) return NULL;
if(key == t->val) return t;
else if(key < t->val) return Search(t->left, key);
else Search(t->right, key);
}
void Insert(Tree &t, int key)
{
//如果树为空直接插入
if(t == NULL)
{
Tree newNode = new TreeNode;
newNode->val = key;
newNode->left = newNode->right = NULL;
t = newNode;
}
//查找插入位置
else if(key < t->val)
Insert(t->left, key);
else
Insert(t->right, key);
}
void Create(Tree &t) //创建 -- 不断插入的过程
{
t = NULL; //初始化树为空, 必须要有, 对应插入操作的直接插入这一种情况
int cnt;
cout << "请输入节点个数: ";
cin >> cnt;
for(int i = 0; i < cnt; i ++ )
{
int val;
cin >> val;
Insert(t, val);
}
}
Tree find_Father(Tree t, int key) //查找父节点
{
Tree cur = t;
while(cur != NULL && cur->val != key)
{
if(key > cur->val) cur = cur->right;
else cur = cur->left;
}
if(cur == NULL) return NULL;
return cur;
}
bool Delete(Tree &p) //删除该节点
{
/* 从二叉排序树中删除节点p, 并重接它的左或右子树 */
Tree q, s; /* q是辅助指针,s指向直接前驱 */
if( !p->left && !p->right ) /* p为叶子节点 */
p = NULL;
else if( !p->left ) /* 左子树为空,重接右子树 */
{
q = p;
p = p->right;
free(q);
}
else if( !p->right ) /* 右子树为空,重接左子树 */
{
q = p;
p = p->left;
free(q);
}
else /* 左右子树均不为空 */
{
q = p;
s = p->left;
while(s->right) /* 转左,然后向右走到尽头 */
{
q = s;
s = s->right;
}
p->val = s->val;
if( q != p ) /* 判断是否执行上述while循环 */
q->right = s->left; /* 执行上述while循环,重接右子树 */
else
q->left = s->left; /* 未执行上述while循环,重接左子树 */
free(s);
}
return true;
}
bool DeleteBST(Tree &T, int key) //找到要删除的节点
{
/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素节点 */
/* 并返回TRUE;否则返回FALSE */
if( T == NULL)
{
cout << "要删除节点不存在" << endl;
return false; /* 不存在关键字等于key的数据元素 */
}
else
{
if( key == T->val )
Delete(T);
else if( key < T->val)
return DeleteBST(T->left, key);
else
return DeleteBST(T->right, key);
}
return true;
}
Tree getLevel(Tree &t, int key, int &level) //查找某个节点所在的层次
{
if(t == NULL)
{
cout << "没找到该节点!" << endl;
return NULL;
}
if(t->val == key) return t;
else
{
level ++;
if(key < t->val) return getLevel(t->left, key, level);
else return getLevel(t->right, key, level);
}
}
void getHeight(Tree t)
{
if(t == NULL) return ;
if(t->left == NULL && t->right == NULL)
{
t = max()
}
}
int main()
{
Tree t;
Create(t);
InOderTraverse(t);
cout << endl;
return 0;
}
说明
对二叉树的删除操作:左右孩子都有的情况
我们通过让该节点的直接前驱替换该节点然后删除该直接前驱节点实现删除该节点的目的
已知一个节点的直接前驱是它的左子树的递归右子树
证明:一个节点的直接前驱是它左子树中最大的那个值
左子树最大的那个值肯定在该子树的右子树
现在我们用s标记的该节点直接前驱节点,q节点是s节点的父亲节点
当递归结束时,s节点肯定没有右子树了,但是可能有左子树
并且s节点的左子树的值一定大于q节点的值
证明:因为s节点是q节点的右子树,所以s节点以及s节点的子树的值都是大于等于根节点的值
所以,删除直接前驱节点s只需要让 q->right = s->left;
如图:
但是,如果要删除节点的左子树没有右子树
此时q节点直接要删除节点,s节点指向要删除节点的左子树也就是q节点的左子树
那么s节点包括他的左子树的值就小于q节点
如图:
此时 q->left = s->left;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术