【資料結構樹表的查詢】二叉排序樹詳解和程式碼(生成、插入、查詢、最大值、最小值、刪除、中序遍歷、銷燬)
阿新 • • 發佈:2019-01-09
二叉排序樹(簡稱BST)又稱二叉查詢(搜尋)樹,其定義為:二叉排序樹或者是空樹,或者是滿足如下性質的二叉樹:
(1)若它的左子樹非空,則左子樹上所有記錄的值均小於根記錄的值;
(2)若它的右子樹非空,則右子樹上所有記錄的值均大於根記錄的值;
(3)左、右子樹本身又各是一棵二叉排序樹。
注意:二叉排序樹中沒有相同關鍵字的節點。
對二叉排序樹進行中序遍歷,便可得到一個有序序列,該有序序列中的各元素按照從小到大的順序排列,因此一個無序序列可以通過構造一棵二叉排序樹而變成一個有序序列。二叉排序樹相關操作
二叉排序樹的生成、插入、查詢、查詢最大值、查詢最小值、刪除、中序遍歷、銷燬等操作。見下面的程式碼,註釋的非常清楚。直接看程式碼理解吧。/********************************* 樹表的查詢——二叉排序樹的相關操作實現(建立,插入,查詢,刪除) Author:_牧之 Date:2015年4月27日 Email:[email protected] **********************************/ /* 二叉樹的二叉連結串列結點結構定義 */ typedef struct node /* 結點結構 */ { int data; /* 結點資料 */ struct node *lchild,*rchild; /* 左右孩子指標 */ }BSTNode,*BSTree; //插入操作 /*節點插入時需要從根節點開始比較,比根節點的data小,指標移到左子樹,否則移到右子樹,直到指標為空,建立節點,指標指向它,便完成插入。 在以*p為根節點的BST中插入一個關鍵字為key的節點。插入成功返回1,否則返回0*/ int InsertBST(BSTree &p,int key) /*或者寫成int InsertBST(BSTNode *&p,int key),樹空時插入會改變根節點p的值,所以一定要用引用型別*/ { if (p==NULL) /*原樹為空, 新插入的記錄為根節點*/ { p=(BSTNode *)malloc(sizeof(BSTNode)); p->data=key; p->lchild=p->rchild=NULL; return 1; } else if (key < p->data) return InsertBST(p->lchild,key); /*插入到*p的左子樹中*/ else if (key > p->data) return InsertBST(p->rchild,key); /*插入到右子樹中*/ else /*樹中存在相同關鍵字的節點,返回0*/ return 0; } //生成操作 /* 二叉排序樹的生成,是從一個空樹開始,每插入一個關鍵字,就呼叫一次插入演算法將它插入到當前已生成的二叉排序樹中。任何節點插入到二叉排序樹時,都是以葉子節點插入的。 元素插入的先後次序不同,構成的二叉排序樹的形態和深度也可能不同。 CreateBST()返回二叉排序樹的根節點指標*/ BSTree CreateBST(int a[],int n) { BSTree bst=NULL; /*初始時bst為空樹*/ int i=0; while(i<n) { InsertBST(bst,a[i]); /*將關鍵字a[i]插入二叉排序樹bst中*/ i++; } return bst; /*返回建立的二叉排序樹的根指標*/ } //查詢操作 /* 遞迴查詢演算法SearchBST()如下(在二叉排序樹bst上查詢關鍵字為key的記錄,成功時返回該節點指標,否則返回NULL)*/ BSTNode *SearchBST(BSTree bst,int key) { if(bst==NULL||bst->data==key) /*遞迴終結條件*/ return bst; if (key < bst->data) return SearchBST(bst->lchild,key); /*在左子樹中遞迴查詢*/ else return SearchBST(bst->rchild,key); /*在右子樹中遞迴查詢*/ } /* 非遞迴查詢演算法SearchBST1()如下(在二叉排序樹bst上查詢關鍵字為key的記錄,成功時返回該節點指標,否則返回NULL)*/ BSTNode *SearchBST1(BSTree bst,int key) { while (bst!=NULL) { if (key==bst->data) return bst; else if (key<bst->data) bst=bst->lchild; //在左子樹中查詢 else bst=bst->rchild; //在右子樹中查詢 } return NULL; //沒有找到返回NULL } /*查詢關鍵字key,同時找到父節點 遞迴查詢演算法SearchBST2()如下(在二叉排序樹bst上查詢關鍵字為key的記錄,成功時返回該節點指標,f返回其雙親節點;否則返回NULL)*/ BSTNode *SearchBST2(BSTree bst,int key,BSTNode *f1,BSTNode *&f) //f1為中間引數,用於求f,初始設為NULL,其目的是跟蹤查詢路徑上訪問的當前節點的父節點(即上一個訪問節點) { if (bst==NULL) { f=NULL; return NULL; } else if (key==bst->data) { f=f1; return bst; } else if(key < bst->data) return SearchBST2(bst->lchild,key,bst,f); else return SearchBST2(bst->rchild,key,bst,f); } /*查詢二叉排序樹中最大節點關鍵字key 一棵二叉排序樹中的最大節點為根節點的最右下節點 查詢演算法MaxNode()如下(在二叉排序樹bst上查詢最大節點關鍵字,返回該節點關鍵字*/ int MaxNode(BSTree bst) { while(bst->rchild!=NULL) bst=bst->rchild; return bst->data; } /*查詢二叉排序樹中最小節點關鍵字key 一棵二叉排序樹中的最小節點為根節點的最左下節點 查詢演算法MinNode()如下(在二叉排序樹bst上查詢最小節點關鍵字,返回該節點關鍵字*/ int minnode(BSTree bst) { while (bst->lchild!=NULL) bst=bst->lchild; return(bst->data); } //刪除操作 /*(1)被刪除的節點是葉子節點:直接刪去該節點。 (2)被刪除的節點只有左子樹或者只有右子樹,用其左子樹或者右子樹代替它。 (3)被刪除的節點既有左子樹,也有右子樹,以其前驅替代之,然後再刪除該前驅節點。前驅是左子樹中最大的節點。 (也可以用其後繼替代之,然後再刪除該後繼節點。後繼是右子樹中最小的節點。) 刪除會改變bst的值,所以一定要用引用型別 刪除演算法DeleteBST()如下(在二叉排序樹bst上刪除關鍵字為key的記錄,成功時返回1,否則返回0)*/ void Delete(BSTNode *&p); void Delete1(BSTNode *p,BSTNode *&r); int DeleteBST(BSTree &bst,int key) { if(bst==NULL) return 0; //空樹刪除失敗 else { if(key < bst->data) return DeleteBST(bst->lchild,key); //遞迴在左子樹中刪除為k的節點 else if (key > bst->data) return DeleteBST(bst->rchild,key); //遞迴在右子樹中刪除為k的節點 else { Delete(bst); //呼叫Delete(bt)函式刪除*bt節點 return 1; } } } void Delete(BSTNode *&p) //從二叉排序樹中刪除*p節點 { BSTNode *q; if (p->rchild==NULL) //*p節點沒有右子樹的情況 { q=p; p=p->lchild; //直接將其左子樹的根節點放在被刪節點的位置上 free(q); } else if(p->lchild==NULL) //*p節點沒有左子樹 { q=p; p=p->rchild; //直接將其右子樹的根節點放在被刪節點的位置上 free(q); } else Delete1(p,p->lchild); //*p節點既有左子樹又有右子樹的情況 } void Delete1(BSTNode *p,BSTNode *&r) //當被刪*p節點有左右子樹時的刪除過程 { BSTNode *q; if (r->rchild!=NULL) Delete1(p,r->rchild); //遞迴找最右下節點 else //找到了最右下節點*r { p->data=r->data; //將*r的關鍵字值賦給*p q=r; //將左子樹的根節點放在被刪節點的位置上 r=r->lchild; free(q); //釋放最右下節點*r的空間 } } /* 遞迴中序遍歷二叉排序樹,得到元素從小到大有序排列的序列 */ void in_traverse(BSTree bst) { if(bst) { if(bst->lchild) in_traverse(bst->lchild); printf("%d ",bst->data); if(bst->rchild) in_traverse(bst->rchild); } printf("二叉排序樹已銷燬"); } /* 遞迴銷燬二叉排序樹 銷燬時會改變bst的值,所以一定要用引用型別 */ void destroy_BSTree(BSTree &bst) { if(bst) { if(bst->lchild) destroy_BSTree(bst->lchild); if(bst->rchild) destroy_BSTree(bst->rchild); free(bst); bst = NULL; } } int main() { /* int a[8] = {3,1,1,7,2,4,9,62}; print(a,8); cout<<"查詢元素值為7的數(成功返回邏輯序號,否則為0):"<<BinSearch(a,8,7)<<endl; cout<<"查詢元素值為8的數(成功返回邏輯序號,否則為0):"<<BinSearch(a,8,8)<<endl; */ int i; int num; printf("請輸入節點個數:"); scanf("%d",&num); //輸入num個整數 int *arr = (int *)malloc(num*sizeof(int)); printf("請依次輸入這%d個整數(必須互不相等):",num); for(i=0;i<num;i++) scanf("%d",arr+i); //中序遍歷該二叉排序樹,使資料按照從小到大的順序輸出 BSTree bst = CreateBST(arr,num); printf("中序遍歷該二叉排序樹的結果:"); in_traverse(bst); printf("\n"); //查詢給定的整數 int key; printf("請輸入要查詢的整數:"); scanf("%d",&key); if(SearchBST(bst,key)) printf("查詢成功\n"); else printf("查詢不到該整數\n"); //插入給定的整數 printf("請輸入要插入的整數:"); scanf("%d",&key); if(InsertBST(bst,key)) { printf("插入成功,插入後的中序遍歷結果:"); in_traverse(bst); printf("\n"); } else printf("插入失敗,該二叉排序樹中已經存在整數%d\n",key); //刪除給定的整數 printf("請輸入要刪除的整數:"); scanf("%d",&key); if(DeleteBST(bst,key)) { printf("刪除成功,刪除後的中序遍歷結果:"); in_traverse(bst); printf("\n"); } else printf("刪除失敗,該二叉排序樹中不存在整數%d\n",key); //銷燬bst destroy_BSTree(bst); in_traverse(bst); system("pause"); return 0; }
效能分析
每次插入的新的結點都是二叉排序樹上新的葉子結點,在進行插入操作時,不必移動其它結點,只需改動某個結點的指標,由空變為非空即可。搜尋、插入、刪除的時間複雜度等於樹高,期望O(logn),最壞O(n)(數列有序,樹退化成線性表,如右斜樹)。
每個結點的Ci為該結點的層次數。最好的情況是二叉排序樹的形態和折半查詢的判定樹相同,其平均查詢長度和logn成正比(O(log2(n)))。最壞情況下,當先後插入的關鍵字有序時,構成的二叉排序樹為一棵斜樹,樹的深度為n,其平均查詢長度為(n + 1) / 2。也就是時間複雜度為O(n),等同於順序查詢。因此,如果希望對一個集合按二叉排序樹查詢,最好是把它構建成一棵平衡的二叉排序樹(平衡二叉樹
Reference:
[1] 《演算法導論》
[2]資料結構教程(李春葆)
[3] http://www.cnblogs.com/zhuyf87/archive/2012/11/09/2763113.html
[4]http://blog.csdn.net/ns_code/article/details/19823463