參考書籍:數據結構(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;
}
}
演示:
void 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++;
}
}
注意: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;
}
演示:
void 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");
//查找成功則刪除
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++;
}
}
2.4二叉排序樹的查找分析
含有n個結點的二叉排序樹不唯一,ASL也不相同。最差情況是(n+1)/2(退化成順序查找,例如:單支樹的情形);最好情況是和 log2n成正比(類似折半查找的判定樹)。
爲了保證樹型查找有較高的查找速度,我們希望二叉樹的每一個結點的左、右子樹高度儘量接近平衡,即使按任意次序不斷地插入結點,也不要使此樹成爲退化單支樹。