數據結構(22)--動態查找之二叉排序樹(二叉查找樹)

參考書籍:數據結構(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成正比(類似折半查找的判定樹)
    爲了保證樹型查找有較高的查找速度,我們希望二叉樹的每一個結點的左、右子樹高度儘量接近平衡,即使按任意次序不斷地插入結點,也不要使此樹成爲退化單支樹。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章