参考书籍:数据结构(C语言版)严蔚敏吴伟民编著清华大学出版社
本文中的代码可从这里下载:https://github.com/qingyujean/data-structure
1.动态查找表
特点:表结构在查找过程中动态生成。
要求:对于给定值 key, 若表中存在其关键字等于 key的记录,则查找成功返回,并且对查找成功的关键字可做删除操作;查找失败则可以做插入关键字等于 key的记录的操作。
动态查找表主要有二叉树结构和树结构两种类型。二叉树结构有二叉排序树、平衡二叉树等。树结构有B-树、B+树等。
2.二叉排序树
二叉排序树,又叫二叉查找树或二叉搜索树。它或是一棵空树;或是具有下列性质的二叉树:
(1)若它的左子树不空,则左子树上所有结 的值均小于它的根结点的值;
(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)它的左、右子树也分别为二叉排序树。
2.1二叉排序树的查找
将给定值与根节点的值进行比较,然后递归进行。
代码实现:
#include<stdio.h>
#include<stdlib.h>
#define NULL 0
#define TRUE 1
#define FALSE 0
typedef int status;
typedef int TElemType;
typedef int KeyType;
//动态二叉链表
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
bool isFind = false;//标识是否查找到
//在根指针T所指的二叉排序树中递归的查找其关键字等于key的数据元素,若查找成功,则指针p指向该数据元素的结点,并返回True,,否则返回False
//指针f总指向p的双亲,其初始值为NULL
status searchBST(BiTree T, KeyType key, BiTree &f, BiTree &p){
if(!T){
p = T;//使P指向NULL
isFind = false;//标识没有查找到
return FALSE;
}
if(T->data == key){
p = T;
isFind = true;//标识已经查找到
return TRUE;
}else if(T->data > key){
f = T;
return searchBST(T->lchild, key, f, p);
}else{
f = T;
return searchBST(T->rchild, key, f, p);
}
}
//中序遍历打印二叉树的递归算法(左、根、右)
void inOrderPrint(BiTree T){
if(T){
inOrderPrint(T->lchild);
printf("%d ", T->data);
inOrderPrint(T->rchild);
}
}
2.2二叉排序树的插入
若二叉排序树为空,则作为根结点插入,否则,若待插入的值小于根结点值,则作为左子树插入,否则作为右子树插入。将二叉排序树的查找算法改写,以便能在查找不成功时返回插入位置。
二叉排序树的建立:反复调用二叉排序树的插入算法即可。
示例:结定关键字序列79,62,68,90,88,89,17,5,100,120,生成二叉排序树
代码实现:
status insertBST(BiTree &T, KeyType key){
BiTNode *p;
BiTNode *f = NULL;
if(searchBST(T, key, f, p)){//找到了
return TRUE;
}else{//没找到,即不存在,则要插入该值
//printf("查找&d失败,则在原二叉排序树中插入该值\n");
p = (BiTNode *)malloc(sizeof(BiTNode));
p->data = key;
p->lchild = p->rchild = NULL;
if(!f){//f为NULL,说明二叉排序树还是一棵空树
T = p;
}else if(key > f->data){
f->rchild = p;
}else{
f->lchild = p;
}
return TRUE;
}
}
演示:
int main(){
BiTree T;
T = NULL;//对树初始化:重要!!!
//测试,按结定关键字序列79,62,68,90,88,89,17,5,100,120生成二叉排序树
KeyType keyArray[] = {79, 62, 68, 90, 88, 89, 17, 5, 100, 120};
for(int i = 0; i < 10; i++){
insertBST(T, keyArray[i]);
}
printf("中序遍历该二叉树为:");
inOrderPrint(T);
printf("\n");
//查找失败则插入
int test = 1;
while(test <= 3){//做2次测试
printf("\n请输入要查找的关键字key=");
KeyType key;
scanf("%d", &key);
insertBST(T, key);
if(isFind){
printf("查找%d成功\n", key);
}else{
printf("查找%d失败,则在原二叉排序树中插入该值\n", key);
printf("插入关键字%d后,中序遍历该二叉树为:", key);
inOrderPrint(T);
printf("\n");
}
test++;
}
return 0;
}
注意:1.二叉排序树与关键字排列顺序有关,排列顺序不一样,得到的二叉排序树也不一样。
2. 中序遍历二叉排序树可得到一个关键字的有序序列;
3. 进行插入操作时,不必移动其它结点,仅需改动某个结点的指针。表明,二叉排序树既拥有类似于折半查找的特性,又采用了链表作存储结构,因此是动态查找表的一种适宜表示。
2.3二叉排序树的删除
对于二叉排序树,删去树上一个结点相当于删去有序序列中的一个记录,在删除某个结点之后依旧要保持二叉排序树的特性。
如何删除?
设在二叉排序树上被删结点为*p(指向结点的指针为p),其双亲结点为*f,设*p是*f的左孩子。 如图:
分三种情况进行讨论(注意:下面的讨论假设p不是根节点,如果要删除的结点p是根节点,还需另外讨论,但也非常简单,代码里有实现,这里就不赘述了):
(1)若*p结点为叶子结点,即PL和PR均为空树。
由于删去叶子结点不破坏整棵树的结构,则只需修改其双亲结点的指针。
(2)若*p结点只有左子树PL或者只有右子树PR。
只需令PL或PR直接成为其双亲结点*f的左子树即可。
(3)若*p结点的左子树和右子树均不空。下面2种方法均可以,后面我的实现中使用第一种方法:
①令*p左子树为*f的左子树,而*p右子树为*s的右子树
②是令*p的直接前驱(或直接后继)替代*p,然后再从二叉排序树中删去它的直接前驱(或直接后继)。
代码实现:
status deleteBST(BiTree &T, KeyType key){
BiTNode *p, *q;//q将代替p在双亲f下的位置
BiTNode *f = NULL;
if(!searchBST(T, key, f, p)){//没找到
return FALSE;
}else{//找到了,则要执行删除操作
if(!f){//f为NULL,说明要删除的是二叉排序树的根节点,示例:79
if(p->lchild == NULL && p->rchild == NULL){//p是叶子结点,示例:5,68, 89,120
T = NULL;
}else if(p->lchild == NULL){//p只有右子树,示例:100,88
T = p->rchild;
}else if(p->rchild == NULL){//p只有左子树,示例:17
T = p->lchild;
}else{//P的两棵子树均不为空
//让p的左子树的根节点为新的根节点,右子树的根节点链接到左子树的最右下端
T = p->lchild;
q = T;
while(q && q->rchild)
q = q->rchild;
//q指向p的左子树的最右下端
if(q)
q->rchild = p->rchild;
}
}else{
if(p->lchild == NULL && p->rchild == NULL){//p是叶子结点,示例:5,68, 89,120
q = NULL;
}else if(p->lchild == NULL){//p只有右子树,示例:100,88
q = p->rchild;
}else if(p->rchild == NULL){//p只有左子树,示例:17
q = p->lchild;
}else{//P的两棵子树均不为空,示例:62,90,
//将p的左子树的根节点代替p,p的右子树到p的左子树的最右下端
q = p->lchild;
while(q->rchild){
q = q->rchild;
}
q->rchild = p->rchild;//p的右子树移到左子树的最右下端
}
//重新指派p的双亲的孩子,并删除p结点
if(f->lchild == p)
f->lchild = q;
else if(f->rchild == p)
f->rchild = q;
free(p);
}//end else
}//end else
return TRUE;
}
演示:
int main(){
BiTree T;
T = NULL;//对树初始化:重要!!!
//测试,按结定关键字序列79,62,68,90,88,89,17,5,100,120生成二叉排序树
KeyType keyArray[] = {79, 62, 68, 90, 88, 89, 17, 5, 100, 120};
for(int i = 0; i < 10; i++){
insertBST(T, keyArray[i]);
}
printf("中序遍历该二叉树为:");
inOrderPrint(T);
printf("\n");
//查找成功则删除
int test = 1;
while(test <= 3){//做3次测试
printf("\n请输入要查找的关键字key=");
KeyType key;
scanf("%d", &key);
deleteBST(T, key);
if(isFind){
printf("查找%d成功,则删除该关键字\n", key);
printf("删除关键字%d后,中序遍历该二叉树为:", key);
inOrderPrint(T);
printf("\n");
}else{
printf("查找%d失败\n", key);
}
test++;
}
return 0;
}
2.4二叉排序树的查找分析
含有n个结点的二叉排序树不唯一,ASL也不相同。最差情况是(n+1)/2(退化成顺序查找,例如:单支树的情形);最好情况是和 log2n成正比(类似折半查找的判定树)。
为了保证树型查找有较高的查找速度,我们希望二叉树的每一个结点的左、右子树高度尽量接近平衡,即使按任意次序不断地插入结点,也不要使此树成为退化单支树。
本文中的代码可从这里下载:https://github.com/qingyujean/data-structure