前言
把二叉搜索树的相关内容又复习了一遍,但是对于代码的理解还是不到位,决定整理一篇文章,把代码部分吃透。主要代码实现来源于浙大MOOC何钦铭老师的视频,本文还参考了csdn博主wwxy261的文章,比较巧的是这位博主也采用了浙大MOOC的代码。
1.二叉搜索树(Binary Search Tree)介绍
二叉搜索树,可以为空,如果不空,应该满足以下性质:
非空左子树所有键值小于其根节点的键值,
非空右子树所有键值大于其根节点的键值
左右子树都是二叉搜索树
2.二叉搜索树的结构定义
二叉搜索树的结构在这里采用二叉链表实现。为了便于比较结点键值大小,我们将结点Data类型设置为int型。本次实验主要采用指针操作,注意BinTree是定义好的结点TNode类型指针。
typedef int ElementType;
typedef struct TNode *Position;
typedef Position BinTree;
struct TNode
{
ElementType Data;
BinTree Left;
BinTree Right;
};
复制代码
3.创建二叉搜索树
创建二叉搜索树的基本操作是插入操作,
其接口定义为BinTree Insert( BinTree BST, ElementType X );
其核心步骤是:如果传入的是空指针,我们就申请空间,添加数据,将新结点赋给根指针并返回;如果传入的元素比根元素小,我们就将根元素的左孩子作为新的根节点,递归调用Insert到新结点去寻找位置插入并返回给新根;同理,传入元素比根元素大,就把根元素的右孩子作为新根结点。
插入操作代码如下所示:
/*--------将X插入二叉搜索树BST并返回结果树的根结点指针-------*/
BinTree Insert( BinTree BST, ElementType X ){
if( !BST ){ /* 若原树为空,生成并返回一个结点的二叉搜索树 */
BST = (BinTree)malloc(sizeof(struct TNode));
BST->Data = X;
BST->Left = BST->Right = NULL;
}
else { /* 开始找要插入元素的位置 */
if( X < BST->Data )
BST->Left = Insert( BST->Left, X ); /*递归插入左子树*/
else if( X > BST->Data )
BST->Right = Insert( BST->Right, X ); /*递归插入右子树*/
/* else X已经存在,什么都不做 */
}
return BST;
}
复制代码
下面展示三个结点的插入过程,注意在此过程中根节点是不发生变化的(画图前我犯的错误就是误以为BST会发生变化),事实上每次递归会让BST指向下一层的孩子,如图中波浪线所示,这个会随着递归的深度增加而不断变长。所以我们以后有能力时,不得不考虑这所造成的性能问题。
创建二叉搜索树代码如下所示:
int a[10] = {5, 8, 6, 2, 4, 1, 0, 10, 9, 7};
BinTree BST;
BST = NULL;
for (i = 0; i < 10; i++)
BST = Insert(BST, a[i]);
复制代码
4.删除二叉搜索树中的一个结点
删除接口定义为BinTree Delete( BinTree BST, ElementType X ),将从二叉搜索树中删除键值为X的元素,做好调整,返回树的根结点指针;如果X不在树中,则打印Not Found,返回树的根结点指针。
其核心思想分为三种情况:
删除的是叶子结点,直接删除,再修改其父结点指针指向NULL
删除的结点只有一个孩子结点,将指向被删除结点的指针置为指向被删除结点的孩子
删除的结点有左右两颗子树:用一个结点替代被删除元素,如右子树的最小元素或左子树的最大元素。特点:右子树的最小元素或左子树的最大元素一定不是有两个儿子
代码如下所示:
BinTree Delete( BinTree BST, ElementType X ){
Position Tmp;
if( !BST )
printf("Not Found!");
else {
if( X < BST->Data )
BST->Left = Delete( BST->Left, X ); /* 从左子树递归删除 */
else if( X > BST->Data )
BST->Right = Delete( BST->Right, X ); /* 从右子树递归删除 */
else { /* BST就是要删除的结点 */
/* 如果被删除结点有左右两个子结点 */
if( BST->Left && BST->Right ) {
/* 从右子树中找最小的元素填充删除结点 */
Tmp = FindMin( BST->Right );
BST->Data = Tmp->Data;
/* 从右子树中删除最小元素 */
BST->Right = Delete( BST->Right, BST->Data );
}
else { /* 被删除结点有一个或无子结点 */
Tmp = BST;
if( !BST->Left ) /* 只有右孩子或无子结点 */
BST = BST->Right;
else if ( !BST->Right ) /* 只有左孩子 */
BST = BST->Left;
free( Tmp );
}
}
}
return BST;
}复制代码
下面花了一个简单的示意图,但是画的不太好,恐怕不能很快理解。但是画图确实可以提高理解能力,建议自己动手画一下。
Delete
5.二叉搜索树其他操作
接口定义:
Position FindMax (BinTree BST)找到最大元素的位置并返回该指针
Position FindMin (BinTree BST)找到最小元素的位置并返回该指针
Position Find (ElementType x, BinTree BST)递归查找元素,返回指针
Position iterFind (ElementType x, BinTree BST)迭代查找元素,返回指针
代码如下:
//迭代查找最大值的位置
Position FindMax (BinTree BST){
if (BST) {
while (BST->Right) { //沿着右分支查找 直到右结点
BST = BST->Right;
}
}
return BST;
}
//递归查找最小值
Position FindMin (BinTree BST){
if (!BST) return NULL; //空二叉树,返回空
else if (!BST->Left)
return BST; //找到最左叶结点,并返回
else
return FindMin(BST->Left); //沿着左分支继续查找
}
//尾递归实现二叉搜索树查找,效率不高,尾递归都可以改为循环迭代实现
Position Find (ElementType x, BinTree BST){
if ( !BST ) return NULL; //查找失败
if ( x > BST->Data ) //x比当前元素大,到右子树去查找
return Find(x, BST->Right);
else if ( x < BST->Data) //x比当前元素小,到左子树去查找
return Find(x, BST->Left);
else
return BST; //查找成功
}
//迭代实现查找
//查找效率取决于树的高度
Position iterFind (ElementType x, BinTree BST){
while (BST) {
if ( x > BST->Data ) //x比当前元素大,到右子树去查找
BST = BST->Right;
else if ( x < BST->Data) //x比当前元素小,到左子树去查找
BST = BST->Left;
else
return BST; //查找成功
}
return NULL;
}复制代码
6.测试
测试代码如下:
int main(int argc, char const *argv[]){
//测试数据:
int a[10] = {5, 8, 6, 2, 4, 1, 0, 10, 9, 7};
BinTree BST, MinPos, MaxPos, Temp;
int i;
BST = NULL;
for (i = 0; i < 10; i++)
BST = Insert(BST, a[i]);
printf("\nPreOrder:\n");
PreOrderTraversal(BST);
printf("\nInOrder:\n");
InOrderTraversal(BST);
MaxPos = FindMax(BST);
MinPos = FindMin(BST);
printf("\nThe Largest number in BST is %d \n",MaxPos->Data);
printf("\nThe Smallest number in BST is %d \n",MinPos->Data);
int Y = 6;
Temp = Find(Y,BST);
if( Temp == NULL )
printf("%d is Not Found!\n",Y);
else
printf("Find %d!\n",Y);
int Z = 9;
BST = Delete(BST,Z);
printf("\nInOrder:\n");
InOrderTraversal(BST);
return 0;
}复制代码
7.总结
二叉搜索树的时间复杂度取决于二叉树的形状,但二叉树的形状是不可确定的。我们可以知道最坏时间复杂度是O(n),最坏情况的二叉搜索树是一颗斜树,甚至可以看成一个链表。
为了方便读者快速上手代码,决定贴出自己的渣渣github库.希望考研复试顺利,有时间可以继续更下去ヾ(◍°∇°◍)ノ゙!
本文使用 mdnice 排版