二叉排序树:
二叉排序树(二叉查找树)是一种特殊结构的二叉树,其定义为:
是一棵空树,或者是具有如下性质的二叉树:
1)若左子树非空,则左子树上所有结点的值均小于根结点的值;
2)若右子树非空,则右子树上所有结点的值均大于根结点的值;
3)它的左右子树也分别为二叉排序树。
重要性质:中序遍历一个二叉排序树时可以得到一个递增有序序列。
结构定义:
typedef struct node
{ KeyType key ; /*关键字的值*/
struct node *lchild, *rchild;/*左右指针*/
}BSTNode,*BSTree;
二叉排序树的插入与创建
二叉排序树的插入:
已知一个关键字值为Key的结点s,插入的方法:
①若二叉排序树是空树,则Key 成为二叉排序树的根;
②若二叉树排序树非空,则将key与二叉树排序树的根进行比较:
- 如果key的值等于根结点的值,则停止插入;
- 如果key的值小于根结点的值,则将key插入左子树;
- 如果key的值大于根结点的值,则将key插入右子树。
void InsertBST(BSTree *bst, KeyType key)
/*若在二叉排序树中不存在关键字等于key的元素,插入该元素*/
{ BiTree s;
if (*bst == NULL)
{/*递归结束条件*/
s = (BSTree)malloc(sizeof(BSTNode));/*申请新的结点s*/
s-> key = key;
s->lchild = NULL;
s->rchild = NULL;
*bst = s;
}
else if(key < (*bst)->key)
InsertBST(&((*bst)->lchild), key);/*将s插入左子树*/
else if (key > (*bst)->key)
InsertBST(&((*bst)->rchild), key); /*将s插入右子树*/
}
插入结点作为叶结点,不涉及结点移动.
创建二叉排序树:
将二叉排序树初始化为一棵空树,然后逐个读入元素:
① 每读入一个元素,就建立一个新的结点插入到当前已生成的二叉排序树中;
② 调用上述二叉排序树的插入算法将新结点插入。
void CreateBST(BSTree *bst)
/*从键盘输入元素的值,创建二叉排序树*/
{ KeyType key;
*bst = NULL;
scanf("%d", &key);
while(key != ENDKEY) /*ENDKEY为自定义常数*/
{
InsertBST(bst, key);
scanf("%d", &key);
}
}
设关键字的输入顺序为:45,24,53,12,28,90,按上述算法生成的二叉排序树的过程:
同样的元素序列,如果输入顺序不同,则所建的二叉树形态也不同。
二叉排序树的查找
与折半查找类似,首先将待查关键字k与根结点关键字t进行比较,如果:
1)k = t:则返回根结点地址;
2)k < t:则进一步查左子树;
3)k > t:则进一步查右子树。
递归算法:
BSTree SearchBST(BSTree bst, KeyType key)
/*在二叉排序树中递归查找关键字为key的元素*/
{ if(!bst) return NULL;
else if(bst->key == key)
return bst;/*查找成功*/
else if (key < bst-> key)
return SearchBST(bst->lchild, key);/*在左子树继续查找*/
else
return SearchBST(bst->rchild, key);/*在右子树继续查找*/
}
非递归算法:
BSTree SearchBST(BSTree bst, KeyType key)
{ BSTree q = bst;
while(q)
{ if (q->key == k) return q;/*查找成功*/
if (key < q->key) q = q->lchild;/*在左子树中查找*/
else q = q->rchild; /*在右子树中查找*/
}
return NULL;/*查找失败*/
}/*SearchBST*/
二叉排序树的删除
必须保证删除后所得的二叉树仍然满足二叉排序树的性质(排序)不变。
删除操作:
① 首先确定被删除的结点是否在二叉排序树中。若不在,则不做任何操作;
② 否则,假设要删除的结点为p,结点p的父结点为f,并假设结点p是其父f的左孩子(右孩子的情况类似)。
下面分三种情况讨论:
1)若p为叶结点,则可直接将其删除:
f->lchild = NULL;free(p)
;
2)若p结点只有一孩(只有左子树或右子树),则接班
将p左子树或右子树直接改为其父f的左子树
f->lchild=p->lchild
(或f->lchild=p->rchild
);free(p)
;
3)若p既有左子树,又有右子树, 则处理的方法有两种:
-
找到p结点在中序序列中的直接前驱s结点,
将p左子树改为f左子树,右子树改为s的右子树:
-
找到p结点在中序序列中的直接前驱s结点,
然后用s结点的值,替代p结点的值,再将s结点删除,
原s结点的左子树改为s的父结点q的右子树:
两个父子追针用法s:p左孩的右孩的右孩…
删除结点的算法(方法2):
BSTNode * DelBST(BSTree t, KeyType k)
/*在二叉排序树t中删去关键字为k的节点*/
{ BSTNode *p,*f,*s,*q;
p = t; f = NULL;
while(p)/*查找关键字为k的待删结点p*/
{ if(p->key == k) break; /*找到则跳出查找循环*/
f = p; /*f指向p结点的双亲结点*/
if(p->key > k) p = p->lchild;
else p = p->rchild;
}
if(p == NULL) return t; /*找不到,返回原树*/
if(p->lchild == NULL) /*p无左子树*/
{ if(f == NULL) t = p->rchild; /*p是根,右孩继位成根*/
else if(f->lchild == p) /*p是f的左孩子*/
f->lchild = p->rchild; /*右子树链到f的左链上*/
else
f->rchild = p->rchild; /*p右孩接班到f右链*/
free(p);/*释放被删除的节点p*/
}
else /*p有左子树*/
{ q = p; s = p->lchild;
while(s->rchild)/*p左子树的最右下结点*/
{ q = s; s = s->rchild; }
if(q == p) q->lchild = s->lchild; /*没进while,s是p的左孩,s直接接班,左子树链到q上*/
else q->rchild = s->lchild;//托付左孩去接班
p->key=s->key;/*将s的值赋给p*/
free(s);
}
return t;
}
二叉排序树的查找性能分析
同样关键字序列的一组结点,结点插入的次序不同,构成的二叉排序树的形态和深度不同——先到为根。
二叉排序树的平均查找长度ASL与树的形态有关
各分支越均衡,树的深度浅,其平均查找长度ASL越小。
ASL(a) = (1+2+2+3+3+3)/6=14/6;
ASL(b) = (1+2+3+4+5+6)/6=21/6.
若考虑把n个结点,按各种可能的次序插入到二叉排序树中,则有n!颗二叉排序树(其中有的形态相同)。
可以证明,对这些二叉排序树进行平均,得到的平均查找长度仍然是O(log2n)。