二叉排序树(二叉查找树)基本操作_20230417
- 前言
二叉排序树首先是一颗二叉树,它不同于常规二叉树的地方在于,如果左子树不为空,那么左子树上所有结点的值都不大于根节点的值,如果右子树不为空,那么右子树上所有的值不小于根节点的值,而且它的左右子树本身也属于二叉排序树。
二叉排序树的形式和元素的输入顺序相关,它最坏的情况下可能退化为有序线性表。大多数条件下,二叉排序树既具备二叉树的折半查找行者,又采用了链表作为储存结构,加强了数据储存的灵活性,不失为一种优秀的数据储存结构。
下面的二叉排序树通过中序遍历,就得到一组有序表。
- 二叉排序树的基本操作
2.1 查找操作
二叉排序树的查找操作操作可通过递归实现,由于二叉排序树当中的每个元素都包含有数据域、左孩子指针和右孩子指针,通过递归可以定位到是在左孩子还是右孩子区域进行查找。如果元素比对成功,则返回 true;如果查找失败,则返回false. 如果查找成功,其中的某个递归变量保留查找成功的结点,如果没有找到目标元素,则某个递归变量保留此元素的根节点(父节点)的位置。
查找的实际上是沿着根节点往下遍历的过程,它会形成一颗合适的遍历路径,如果配对成功,路径上的结点都是目标结点的父节点。
看一个具体的例子。给点上述二叉排序树,要求查找元素的值为30,那么遍历形成路径用绿色虚线表示,遍历经过了左–>左–>右的路径。
元素查找的实现, 如果发现递归结点已经为NULL,意识是查询失败,此二叉排序树当中不含有目标元素,此时查找目标赋值为待查找元素的父节点,同时返回查询失败标记false, false会在退栈过程中不断传递给当前的栈,最终查找函数返回false.
同时,如果当前结点的值和待查找的值相等,意味着本次查询成功,递归可以结束(不再入函数栈),同时返回查询成功的标记true,true会在退栈过程中不断传递给上一级函数栈,最终查找函数返回true。
typedef struct BiTNode
{
SElemType data;
struct BiTNode *lchild;
struct BiTNode *rchild;
} BiTNode, *BiTree;
bool find_bst(BiTree T, KeyType key, BiTree parent, BiTree *target_ptr)
{
if(T==NULL)
{
*target_ptr=parent;
return false; //one of termination conditions, traveling with parent
}
else
{
//another condition of termination conditions
//traveling with parent
if(EQ(key,T->data.key))
{
*target_ptr=T;
return true;
}
else if (LT(key, T->data.key))
{
return find_bst(T->lchild,key,T,target_ptr);
}
else
{
return find_bst(T->rchild,key,T,target_ptr);
}
}
}
2.2 插入和创建树操作
二叉排序树是一类动态表,其原因在于,如果树中不含有待插入元素,那么二叉排序树会执行插入操作,从而达到动态更新表的目的。插入和创建实际上可以共用一个过程,插入的过程也是创建树的过程。利用上面的查找函数,可以实现插入的过程。正如前面所述,插入过程需要先判断待插入元素是否在现有的表当中,如果不包含在目前的表当中,则需要执行插入操作,并返回插入成功的标记true,否则则直接返回未执行插入的标记false.
bool insert_bst(BiTree *bt, KeyType key)
{
BiTree ptr;
BiTree new_node;
if(!find_bst(*bt,key,NULL,&ptr))
{
new_node=(BiTree)malloc(sizeof(BiTNode));
new_node->data.key=key;
new_node->data.value=NULL;
new_node->lchild=NULL;
new_node->rchild=NULL;
if(ptr==NULL) // don't leave this condition behind
{
*bt=new_node;
}
else if(LT(key,ptr->data.key))
{
ptr->lchild=new_node;
}
else
{
ptr->rchild=new_node;
}
return true;
}
return false;
}
2.3 二叉排序树删除操作
二叉排序树的结点删除分3种情况讨论,
a.) 若P为叶子结点,既PL和PR均为空树,由于删除叶子结点不破坏树的结点,只需要修改P结点的指针即可,也就是*p=NULL即可。
b.) 上述图,若 P结点只有左子树或只有右子树,此时只要令PL或PR称为父节点的左子树即可(也即是把指针赋值为结点P即可)
c.) 若P结点的左右子树均不为空,如果删除元素P后,需要保持二拆排序树仍然有序,那么就有两种途径,①-a途径,称之为替代法,用p元素的直接前驱元素S里面的值替代P里面的值,P的左右孩子指针保持不变,同时删除S结点,把S结点的左孩子赋值给其双亲结点的右孩子;②-b途径是利用待删除元素的左子树根节点来替代P所在结点,同时把P结点原有的右子树赋值给左子树的最右端元素。
两种类型不同在于①-a利用原有结点的左右孩子指针,只是替代元素;②-b则是直接修改替换原有结点,并更新现有结点的对应指针。
c) 删除的代码实现
二叉排序树的删除过程仍然采用递归函数,如果找到待删除元素,则执行删除操作,并返回删除成功标记,否则返回删除失败标记。
bool delete_bst(BiTree *T, KeyType key)
{
//if deletion is succesfful, it will return true;
//if deletion is not successful, it will return false
if(*T==NULL)
{
return false; // one termination condition
}
else
{
if(EQ(key,(*T)->data.key))
{
delete_action_b(T); //propagate the return value
return true; //the second termination condition
}
else if (LT(key, (*T)->data.key))
{
return delete_bst(&((*T)->lchild),key);
}
else
{
return delete_bst(&((*T)->rchild), key);
}
}
}
分别用两个函数实现不同的删除模式,
//①-a implementation code
void delete_action_a(BiTree *node)
{
//p;
//s;
//list three scenarios of node
BiTree p;
BiTree s;
if((*node)->lchild==NULL)
{
p=*node;
(*node)=(*node)->rchild;
free(p);
}
else if ((*node)->rchild == NULL)
{
p = *node;
(*node) = (*node)->lchild;
free(p);
}
else
{
p= *node;
s=(*node)->lchild; //next one
while(s->rchild!=NULL)
{
p=s;
s=s->rchild;
}
(*node)->data=s->data;
if(p!=(*node))
{
p->rchild=s->lchild;
}
else
{
p->lchild=s->lchild; //no right child and jumpt one node
}
free(s);
}
return;
}
//②-b implementation code
void delete_action_b(BiTree *node)
{
BiTree p;
BiTree s;
if ((*node)->lchild == NULL)
{
p = *node;
(*node) = (*node)->rchild;
free(p);
}
else if ((*node)->rchild == NULL)
{
p = *node;
(*node) = (*node)->lchild;
free(p);
}
else
{
p = *node;
s = (*node)->lchild;
while (s->rchild != NULL)
{
s = s->rchild;
}
s->rchild=(*node)->rchild; // 先后顺序非常重要
(*node)=(*node)->lchild; // 先后顺序非常重要
free(p);
}
return;
}
- 二叉查找树的形式
与静态二叉搜索树不同,静态二叉搜索树的形式是唯一的;对于相同的元素集合,二叉查找树的形式会随着不同的排列顺序呈现不同的树的形态。由于树的形态不同,造成树的深度不同,导致平均查找长度不同(Average Search Length),如果输入有序元素,二叉查找树就退化为有序线性表,导致极端的情况发生。
这就为后面平衡二叉查找树的引入提供了应用场景,本文仅针对二叉排序树,不会对AVL树进一步阐述。
- 小结
本文学习了二叉排序树的不同操作,包括插入、建树和删除等操作,同时阐述了不同形态的二叉查找树会影响查找效率,极端情况下,有序输入会导致二叉排序树蜕变为线性表,严重影查询效率。
参考资料:
《数据结构》严蔚敏,清华大学