18. 二叉搜索树
一、什么是二叉搜索树
二叉搜索树 是一颗特殊的二叉树。它可以为空,如果不为空,它的 非空左子树 的 所有键值小于其根结点的键值,非空右子树 的 所有键值大于其根结点的键值,并且它的 左、右子树都是二叉搜索树。
二叉搜索树在二叉树的基础上扩展了一些方法。
ADT BinarySearchTree
{
Data:
二叉树T∈BinarySearchTree, Item∈ElementType,
Operation:
void PreOrderTraverse(BinarySearchTree T); // 先序遍历
void InOrderTraverse(BinarySearchTree T); // 中序遍历
void PostOrderTraverse(BinarySearchTree T); // 后序遍历
void LevelOrderTraverse(BinarySearchTree T); // 层次遍历
Posotion Find(BinarySearchTree T, ElementType X); // 查找元素X
Posotion FindMin(BinarySearchTree T); // 查找最小元素
Posotion FindMax(BinarySearchTree T); // 查找最大元素
void Insert(BinarySearchTree T, ElementType X); // 插入元素X
ElementType Delete(BinarySearchTree T, ElementType X); // 删除元素X
} ADT BinarySearchTree;
二、二叉搜索树的表示
typedef int ElementType;
typedef struct TreeNode
{
ElementType Data;
struct TreeNode * Left;
struct TreeNode * Right;
} TreeNode, * BinarySearchTree, * Position;
三、查找元素
/**
* @brief 查找元素
*
* @param T 二叉搜索树
* @param X 要查找的元素
* @return Position 如果找到返回结点的位置,否则返回NULL
*/
Position Find(BinarySearchTree T, ElementType X)
{
if (T == NULL)
{
return NULL;
}
while (T)
{
if (X > T->Data)
{
T = T->Right; // 往右子树查找
}
else if (X < T->Data)
{
T = T->Left; // 往左子树查找
}
else
{
return T; // 找到返回结点的位置
}
}
return NULL;
}
/**
* @brief 查找最小值
*
* @param T 二叉搜索树
* @return Position 最小值结点的位置
*/
Position FindMin(BinarySearchTree T)
{
if (T)
{
while (T->Left)
{
T = T->Left;
}
}
return T;
}
/**
* @brief 查找最大值
*
* @param T 二叉搜索树
* @return Position 最大值结点的位置
*/
Position FindMax(BinarySearchTree T)
{
if (T)
{
while (T->Right)
{
T = T->Right;
}
}
return T;
}
四、插入元素
/**
* @brief 插入元素
*
* @param T 二叉搜索树
* @param X 要插入的元素
* @return BinarySearchTree 指向二叉搜索树的指针
*/
BinarySearchTree Insert(BinarySearchTree T, ElementType X)
{
if (T == NULL)
{
T = (BinarySearchTree)malloc(sizeof(TreeNode));
T->Data = X;
T->Left = T->Right = NULL;
}
else
{
if (X < T->Data)
{
T->Left = Insert(T->Left, X); // 左子树递归插入
}
else if (X > T->Data)
{
T->Right = Insert(T->Right, X); // 右子树递归插入
}
else
{
T->Data = X; // 找到则更新元素
}
}
return T;
}
我们也可以使用非递归的方式实现插入元素。
/**
* @brief 插入元素
*
* @param T 二叉搜索树
* @param X 要插入的元素
* @return BinarySearchTree 指向二叉搜索树的指针
*/
BinarySearchTree Insert(BinarySearchTree T, ElementType X)
{
Position Node = T, Parent = T;
if (T == NULL)
{
T = (BinarySearchTree)malloc(sizeof(TreeNode));
T->Data = X;
T->Left = T->Right = NULL;
}
else
{
while (Node)
{
Parent = Node;
if (X < Node->Data) // 往左子树查找
{
Node = Node->Left;
}
else if (X > Node->Data) // 往右子树查找
{
Node = Node->Right;
}
else // 找到则更新元素
{
Node->Data = X;
return T;
}
}
Node = (BinarySearchTree)malloc(sizeof(TreeNode));
Node->Data = X;
Node->Left = Node->Right = NULL;
if (X < Parent->Data) // 插入左子树
{
Parent->Left = Node;
}
else // 插入右子树
{
Parent->Right = Node;
}
}
return T;
}
五、删除元素
二叉搜索树的删除元素分为三种情况:
- 要删除的结点是 叶结点:直接删除,并修改其父结点指针置为 NULL。
- 要删除的结点 只有一个孩子:将其父结点的指针指向要删除结点的孩子结点。
- 要删除的结点 有左、右两颗子树:用另一结点(右子树的最小元素或者左子树的最大元素)替代被删除结点。
/**
* @brief 删除元素
*
* @param T 二叉搜索树
* @param X 要删除的元素
* @return BinarySearchTree 指向二叉搜索树的指针
*/
BinarySearchTree Delete(BinarySearchTree T, ElementType X)
{
Position P = NULL;
if (T == NULL)
{
return NULL;
}
if (X < T->Data)
{
T->Left = Delete(T->Left, X); // 左子树递归删除
}
else if (X > T->Data)
{
T->Right = Delete(T->Right, X); // 右子树递归删除
}
else
{
if (T->Left && T->Right) // 被删除的结点存在左右子树
{
P = FindMax(T->Left); // 找到左子树最大值
T->Data = P->Data; // 删除右子树最小值
T->Left = Delete(T->Left, T->Data); // 删除左子树最大值
}
else // 被删除结点没有子结点或者只有一个子树
{
P = T;
if (T->Left == NULL) // 有右孩子或者无子结点
{
T = T->Right;
}
else if (T->Right == NULL) // 有左孩子或者无子结点
{
T = T->Left;
}
free(P);
}
}
return T;
}
我们也可以使用非递归的方式实现:
/**
* @brief 删除元素
*
* @param T 二叉搜索树
* @param X 要删除的元素
* @return BinarySearchTree 指向二叉搜索树的指针
*/
BinarySearchTree Delete(BinarySearchTree T, ElementType X)
{
Position P = T;
Position Parent = NULL;
if (T == NULL)
{
return NULL;
}
while (P != NULL)
{
if (X < P->Data)
{
Parent = P;
P = P->Left;
}
else if (X > P->Data)
{
Parent = P;
P = P->Right;
}
else
{
break;
}
}
if (P == NULL)
{
printf("要删除的元素不存在\n");
return T;
}
if (P->Left == NULL)
{
T = Shift(T, Parent, P, P->Right);
}
else if (P->Right == NULL)
{
T = Shift(T, Parent, P, P->Left);
}
else
{
Position S = P->Right; // 要删除结点的后继结点
Position sParent = P; // 要删除结点的后继结点的父结点
while (S->Left != NULL) // 循环结束后,S指向要删除结点的右子树的最小值
{
sParent = S;
S = S->Left;
}
ElementType Data = S->Data;
T = Shift(T, sParent, S, S->Right); // 将要删除结点的右子树的最小值的右孩子托孤
P->Data = Data; // 要删除结点的数据改为其右子树的最小值
}
return T;
}
/**
* @brief 托孤方法
*
* @param T 二叉搜索树
* @param Parent 要删除结点的父结点
* @param Node 要删除的结点
* @param Child 要删除结点的子结点
* @return BinarySearchTree 指向二叉搜索树的指针
*/
BinarySearchTree Shift(BinarySearchTree T, Position Parent, Position Node, Position Child)
{
if (Parent == NULL)
{
T = Child;
}
else if (Node == Parent->Left)
{
Parent->Left = Child;
}
else if (Node == Parent->Right)
{
Parent->Right = Child;
}
free(Node);
return T;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报