何为二叉排序树
二叉排序树(Binary Sort Tree),又称二叉搜索树。它是一棵具有以下性质的二叉树:
- 若根结点的左子树非空,则其左子树上所有结点的值都小于它。
- 若根结点的右子树非空,则其右子树上所有结点的值都大于它。
- 根结点的左,右子树也分别都为二叉排序树。
例如:
二叉排序树的操作
二叉排序树的查找
二叉排序树的结点结构定义与普通二叉树相同。
struct BSTNode {
Data data ;
struct BSTNode *Lchild ;
struct BSTNode *Rchild ;
} ;
当我们需要在二叉排序树中查找一个关键值时,我们可以利用二叉排序树的性质,从根结点开始,每次用关键值与当前结点比较,若关键值小于当前结点,则进入当前结点的左孩子结点,若大于则进入其右孩子结点。直到关键字匹配成功。 具体代码如下:
/*Now指向当前遍历的结点,key为关键值*/
/*若查找成功,Result指向目标结点,parent指向目标结点的双亲结点,否则parent指向最后一次遍历的结点*/
status SerchBST(struct BSTNode *Now, Data key, struct BSTNode *Result, struct BSTNode *Parent)
{
Parent = NULL ;
Result = NULL ;
/*若查找不成功*/
if(Now == NULL) {
return FALSE ;
}
else if(key == Now -> data) {
Result = Now ;
return TRUE ;
}
else if(key < Now -> data) {
Parent = Now ;
return SearchBST(Now -> Lchild, key, Result, parent) ;
}
else {
Parent = Now ;
return SearchBST(Now -> Rchild, key, Result, parent) ;
}
}
此函数为递归运行的函数,每次先检查当前结点是否为空,若为空说明未查找到关键字匹配的结点,返回FALSE。若不为空,则比较当前结点与关键字的大小,若关键字与当前结点匹配成功,则返回TRUE。若当前结点小于关键字,则进入当前结点的左孩子结点,否则进入当前结点的右孩子结点。
例如,寻找关键字75的过程如下:
二叉排序树的插入
二叉排序树的插入操作,可以看作是在查找操作的基础上多加了一个步骤而已。
例如,想要在查找操作的例图中插入80这个数据,则首先执行以80为关键字的查找操作,当查找到75这个结点时,由于80 > 75, 而当前结点75的右孩子域为空,则将要插入的80结点赋到75结点的右孩子域。过程如下图:
具体实现代码如下:
status InsertBST(struct BSTNode *Now, struct BSTNode *key)
{
/*若当前结点为空,则直接插入关键字结点*/
if(Now == NULL) {
Now = key ;
return TRUE ;
}
/*若有结点值与关键字相同,则返回FALSE,不重复插入*/
if(Now -> data == key -> data) {
return FALSE ;
}
else if(key -> data < Now -> data) {
/*若关键字小于当前结点,且当前结点的左孩子结点为空,则插入关键字并返回TRUE*/
if(Now -> Lchild == NULL) {
Now -> Lchild = key ;
return TURE ;
}
else {
return InsertBST(Now -> Lchild, key) ;
}
}
else {
/*若关键字大于当前结点,且当前结点的右孩子结点为空,则插入关键字并返回TRUE,*/
if(Now -> Rchild == NULL) {
Now -> Rchild = key ;
return TURE ;
}
else {
return InsertBST(Now -> Rchild, key) ;
}
}
}
多次调用二叉排序树的插入函数,可以根据不同的关键字序列来创建一个二叉排序树。
二叉排序树的删除
二叉排序树的删除操作也是基于查找操作的,被删除结点可能有三种情况:
- 被删除结点是叶子结点。
- 被删除结点只有左子树或只有右子树。
- 被删除结点左右子树都存在。
现在分析三种情况的结点。
- 如果被删除结点是叶子结点,则直接将其删除,并将其双亲结点相应孩子域置空。
- 如果被删除结点只有左子树或只有有子树,则将其删除后,还需要把它的左子树或右子树移动到被删除位置。
- 如果被删除结点左右子树都存在,则将其删除后,还需对其左右子树做一些调整。有两种方案:1.取其左子树“最右边的结点”,即是左子树值最大的结点。2.取其右子树“最左边的结点”,即是右子树值最小的结点。
例如要删除图中的根结点57,则需要在其左右子树中找到一个结点来替代它的位置,以实现对其整体结构变动最小,而这个替代它的结点需要满足二叉排序树的性质,所以就必须找到最接近根结点的结点,也就是其左子树中最大的结点44,或其右子树中最小的结点60。删除替代后就变成:
具体实现代码如下:
Status DeleteBST(BSTNode *Root, Data key)
{
BSTNode *temp = NULL, *temp_parent = NULL ;
BSTNode *Result = NULL ;
BSTNode *Parent = NULL ;
if(SerchBST(Root, key -> data, Result, Parent) != TRUE) {
retrun FALSE ;
}
/*要删除的结点左子树为空*/
if(Result -> Lchild == NULL) {
/*删除结点为根结点*/
if(Parent == NULL) {
Root = NULL ;
free(Result) ;
return TRUE ;
}
else {
if(Parent -> Lchild == Result) {
Parent -> Lchild = Result -> Rchild ;
}
else if(Parent -> Rchild == Result) {
Parent -> Rchild = Result -> Rchild ;
}
free(Result) ;
return TRUE ;
}
}
/*要删除的结点右子树为空*/
if(Result -> Rchild == NULL) {
/*删除结点为根结点*/
if(Parent == NULL) {
Root = NULL ;
free(Result) ;
return TRUE ;
}
else {
if(Parent -> Lchild == Result) {
Parent -> Lchild = Result -> Lchild ;
}
else if(Parent -> Rchild == Result) {
Parent -> Rchild = Result -> Lchild ;
}
free(Result) ;
return TRUE ;
}
}
/*要删除结点左右子树都存在,采取右子树最小结点方案*/
else {
/*进入要删除的结点的右子树*/
temp = Parent -> Rchild ;
temp_parent = Parent ;
/*循环查找右子树中最小的结点和最小结点的双亲结点*/
while(temp -> Lchild != NULL) {
temp_parent = temp ;
temp = temp -> Lchild ;
}
/*如果替换结点有右子树,则采用单侧子树结点的处理方式*/
if(temp -> Rchild != NULL) {
temp_parent -> Lchild = temp -> Rchild ;
}
/*如果删除结点是根结点*/
if(Parent == NULL) {
temp -> Lchild = Root -> Lchild ;
temp -> Rchild = Root -> Rchild ;
Root = temp ;
free(Result) ;
return TRUE ;
}
else {
temp -> Lchild = Result -> Lchild ;
temp -> Rchild = Result -> Rchild ;
if(Parent -> Lchild == Result) {
Parent -> Lchild = temp ;
}
else if(Parent -> Rchild == Result) {
Parent -> Rchild = temp ;
}
free(Result) ;
return TRUE ;
}
}
}
总结
二叉排序树就像栈至于线性表一样,也是在普通的二叉树基础上,对其操作方式等加以限制,从而达到快速从二叉树中搜索关键数据的目的。在二叉排序树创建的过程中,序列的不同顺序会导致最后生成的树有差别。