二叉排序树(完整案例与完整C语言代码)
寫在前面:博主是一位普普通通的19屆雙非軟工在讀生,平時(shí)最大的愛好就是聽聽歌,逛逛B站。博主很喜歡的一句話花開堪折直須折,莫待無(wú)花空折枝:博主的理解是頭一次為人,就應(yīng)該做自己想做的事,做自己不后悔的事,做自己以后不會(huì)留有遺憾的事,做自己覺得有意義的事,不浪費(fèi)這大好的青春年華。博主寫博客目的是記錄所學(xué)到的知識(shí)并方便自己復(fù)習(xí),在記錄知識(shí)的同時(shí)獲得部分瀏覽量,得到更多人的認(rèn)可,滿足小小的成就感,同時(shí)在寫博客的途中結(jié)交更多志同道合的朋友,讓自己在技術(shù)的路上并不孤單。
目錄:
1.二叉排序樹定義
2.二叉排序樹查找
3.二叉排序樹的插入
4.二叉排序樹的刪除
5.二叉排序樹全功能完整代碼實(shí)現(xiàn)
6.二叉排序樹小結(jié)
1.二插排序樹定義
二叉排序樹要么是空二叉樹,要么具有如下特點(diǎn):
- 二叉排序樹中,如果其根結(jié)點(diǎn)有左子樹,那么左子樹上所有結(jié)點(diǎn)的值都小于根結(jié)點(diǎn)的值;
- 二叉排序樹中,如果其根結(jié)點(diǎn)有右子樹,那么右子樹上所有結(jié)點(diǎn)的值都大小根結(jié)點(diǎn)的值;
- 二叉排序樹的左右子樹也要求都是二叉排序樹;
如下圖就是一顆二叉排序樹:
2.二叉排序樹查找關(guān)鍵
二叉排序樹中查找某關(guān)鍵字時(shí),查找過程類似于次優(yōu)二叉樹,在二叉排序樹不為空樹的前提下,首先將被查找值同樹的根結(jié)點(diǎn)進(jìn)行比較,會(huì)有 3 種不同的結(jié)果:
- 如果相等,查找成功;
- 如果比較結(jié)果為根結(jié)點(diǎn)的關(guān)鍵字值較大,則說(shuō)明該關(guān)鍵字可能存在其左子樹中;
- 如果比較結(jié)果為根結(jié)點(diǎn)的關(guān)鍵字值較小,則說(shuō)明該關(guān)鍵字可能存在其右子樹中; 實(shí)現(xiàn)函數(shù)為:(運(yùn)用遞歸的方法):
3.二叉排序樹中插入
二叉排序樹本身是動(dòng)態(tài)查找表的一種表示形式,有時(shí)會(huì)在查找過程中插入或者刪除表中元素,當(dāng)因?yàn)椴檎沂《枰迦霐?shù)據(jù)元素時(shí),該數(shù)據(jù)元素的插入位置一定位于二叉排序樹的葉子結(jié)點(diǎn),并且一定是查找失敗時(shí)訪問的最后一個(gè)結(jié)點(diǎn)的左孩子或者右孩子。
例如,在下圖的二叉排序樹中做查找關(guān)鍵字 1 的操作,當(dāng)查找到關(guān)鍵字 3 所在的葉子結(jié)點(diǎn)時(shí),判斷出表中沒有該關(guān)鍵字,此時(shí)關(guān)鍵字 1 的插入位置為關(guān)鍵字 3 的左孩子
所以,二叉排序樹表示動(dòng)態(tài)查找表做插入操作,只需要稍微更改一下上面的代碼就可以實(shí)現(xiàn),具體實(shí)現(xiàn)代碼為:
BOOL SearchBST(BiTree T,KeyType key,BiTree f,BiTree *p){//如果 T 指針為空,說(shuō)明查找失敗,令 p 指針指向查找過程中最后一個(gè)葉子結(jié)點(diǎn),并返回查找失敗的信息if (!T){*p=f;return false;}//如果相等,令 p 指針指向該關(guān)鍵字,并返回查找成功信息else if(key==T->data){*p=T;return true;}//如果 key 值比 T 根結(jié)點(diǎn)的值小,則查找其左子樹;反之,查找其右子樹else if(key<T->data){return SearchBST(T->lchild,key,T,p);}else{return SearchBST(T->rchild,key,T,p);} } //插入函數(shù) BOOL InsertBST(BiTree T,ElemType e){BiTree p=NULL;//如果查找不成功,需做插入操作if (!SearchBST(T, e,NULL,&p)) {//初始化插入結(jié)點(diǎn)BiTree s=(BiTree)malloc(sizeof(BiTree));s->data=e;s->lchild=s->rchild=NULL;//如果 p 為NULL,說(shuō)明該二叉排序樹為空樹,此時(shí)插入的結(jié)點(diǎn)為整棵樹的根結(jié)點(diǎn)if (!p) {T=s;}//如果 p 不為 NULL,則 p 指向的為查找失敗的最后一個(gè)葉子結(jié)點(diǎn),只需要通過比較 p 和 e 的值確定 s 到底是 p 的左孩子還是右孩子else if(e<p->data){p->lchild=s;}else{p->rchild=s;}return true;}//如果查找成功,不需要做插入操作,插入失敗return false; }通過使用二叉排序樹對(duì)動(dòng)態(tài)查找表做查找和插入的操作,同時(shí)在中序遍歷二叉排序樹時(shí),可以得到有關(guān)所有關(guān)鍵字的一個(gè)有序的序列。
例如,假設(shè)原二叉排序樹為空樹,在對(duì)動(dòng)態(tài)查找表 {3,5,7,2,1} 做查找以及插入操作時(shí),可以構(gòu)建出一個(gè)含有表中所有關(guān)鍵字的二叉排序樹,過程如:
當(dāng)使用中序遍歷算法遍歷二叉排序樹時(shí),得到的序列為:1 2 3 5 7 ,為有序序列。
一個(gè)無(wú)序序列可以通過構(gòu)建一棵二叉排序樹,從而變成一個(gè)有序序列。
4.二叉排序樹的刪除
在查找過程中,如果在使用二叉排序樹表示的動(dòng)態(tài)查找表中刪除某個(gè)數(shù)據(jù)元素時(shí),需要在成功刪除該結(jié)點(diǎn)的同時(shí),依舊使這棵樹為二叉排序樹。
假設(shè)要?jiǎng)h除的為結(jié)點(diǎn) p,則對(duì)于二叉排序樹來(lái)說(shuō),需要根據(jù)結(jié)點(diǎn) p 所在不同的位置作不同的操作,有以下 3 種可能:
當(dāng)p左右孩子都有的情況下,第一種處理方式:令結(jié)點(diǎn) p 的左子樹為其雙親結(jié)點(diǎn)的左子樹;結(jié)點(diǎn) p 的右子樹為其自身直接前驅(qū)結(jié)點(diǎn)的右子樹
第二種方式:用結(jié)點(diǎn) p 的直接前驅(qū)(或直接后繼)來(lái)代替結(jié)點(diǎn) p,同時(shí)在二叉排序樹中對(duì)其直接前驅(qū)(或直接后繼)做刪除操作。如圖 4 為使用直接前驅(qū)代替結(jié)點(diǎn) p:
在對(duì)左圖進(jìn)行中序遍歷時(shí),得到的結(jié)點(diǎn) p 的直接前驅(qū)結(jié)點(diǎn)為結(jié)點(diǎn) s,所以直接用結(jié)點(diǎn) s 覆蓋結(jié)點(diǎn) p,由于結(jié)點(diǎn) s 還有左孩子,根據(jù)第 2 條規(guī)則,直接將其變?yōu)殡p親結(jié)點(diǎn)的右孩子
第一種方法可能增加樹的高度,后一種方法是以刪除結(jié)點(diǎn)左子樹中關(guān)鍵字最大的結(jié)點(diǎn)替代被刪結(jié)點(diǎn),然后從左子樹中刪除這個(gè)結(jié)點(diǎn),此結(jié)點(diǎn)一定沒有右子樹,不會(huì)增加樹的高度,所以一般采用后一種方法處理
int Delete(BiTree *p) {BiTree q, s;//情況 1,結(jié)點(diǎn) p 本身為葉子結(jié)點(diǎn),直接刪除即可if(!(*p)->lchild && !(*p)->rchild){*p = NULL;}else if(!(*p)->lchild){ //左子樹為空,只需用結(jié)點(diǎn) p 的右子樹根結(jié)點(diǎn)代替結(jié)點(diǎn) p 即可;q = *p;*p = (*p)->rchild;free(q);}else if(!(*p)->rchild){//右子樹為空,只需用結(jié)點(diǎn) p 的左子樹根結(jié)點(diǎn)代替結(jié)點(diǎn) p 即可;q = *p;*p = (*p)->lchild;//這里不是指針 *p 指向左子樹,而是將左子樹存儲(chǔ)的結(jié)點(diǎn)的地址賦值給指針變量 pfree(q);}else{//左右子樹均不為空,采用第 2 種方式q = *p;s = (*p)->lchild;//遍歷,找到結(jié)點(diǎn) p 的直接前驅(qū)while(s->rchild){q = s;s = s->rchild;}//直接改變結(jié)點(diǎn) p 的值(*p)->data = s->data;//判斷結(jié)點(diǎn) p 的左子樹 s 是否有右子樹,分為兩種情況討論if( q != *p ){q->rchild = s->lchild;//若有,則在刪除直接前驅(qū)結(jié)點(diǎn)的同時(shí),令前驅(qū)的左孩子結(jié)點(diǎn)改為 q 指向結(jié)點(diǎn)的孩子結(jié)點(diǎn)}else{q->lchild = s->lchild;//否則,直接將左子樹上移即可}free(s);}return TRUE; } int DeleteBST(BiTree *T, int key) {if( !(*T)){//不存在關(guān)鍵字等于key的數(shù)據(jù)元素return FALSE;}else{if( key == (*T)->data ){Delete(T);return TRUE;}else if( key < (*T)->data){//使用遞歸的方式return DeleteBST(&(*T)->lchild, key);}else{return DeleteBST(&(*T)->rchild, key);}} }5.二叉排序樹全功能完整代碼實(shí)現(xiàn)
#include<stdio.h> #include<stdlib.h> #define TRUE 1 #define FALSE 0 #define ElemType int #define KeyType int /* 二叉排序樹的節(jié)點(diǎn)結(jié)構(gòu)定義 */ typedef struct BiTNode {int data;struct BiTNode *lchild, *rchild; } BiTNode, *BiTree;//二叉排序樹查找算法 int SearchBST(BiTree T,KeyType key,BiTree f,BiTree *p){//如果 T 指針為空,說(shuō)明查找失敗,令 p 指針指向查找過程中最后一個(gè)葉子結(jié)點(diǎn),并返回查找失敗的信息if (!T){*p=f;return FALSE;}//如果相等,令 p 指針指向該關(guān)鍵字,并返回查找成功信息else if(key==T->data){*p=T;return TRUE;}//如果 key 值比 T 根結(jié)點(diǎn)的值小,則查找其左子樹;反之,查找其右子樹else if(key<T->data){return SearchBST(T->lchild,key,T,p);}else{return SearchBST(T->rchild,key,T,p);} } int InsertBST(BiTree *T,ElemType e){BiTree p=NULL;//如果查找不成功,需做插入操作if (!SearchBST((*T), e,NULL,&p)) {//初始化插入結(jié)點(diǎn)BiTree s=(BiTree)malloc(sizeof(BiTree));s->data=e;s->lchild=s->rchild=NULL;//如果 p 為NULL,說(shuō)明該二叉排序樹為空樹,此時(shí)插入的結(jié)點(diǎn)為整棵樹的根結(jié)點(diǎn)if (!p) {*T=s;}//如果 p 不為 NULL,則 p 指向的為查找失敗的最后一個(gè)葉子結(jié)點(diǎn),只需要通過比較 p 和 e 的值確定 s 到底是 p 的左孩子還是右孩子else if(e < p->data){p->lchild=s;}else{p->rchild=s;}return TRUE;}//如果查找成功,不需要做插入操作,插入失敗return FALSE; } //刪除函數(shù) int Delete(BiTree *p) {BiTree q, s;//情況 1,結(jié)點(diǎn) p 本身為葉子結(jié)點(diǎn),直接刪除即可if(!(*p)->lchild && !(*p)->rchild){*p = NULL;}else if(!(*p)->lchild){ //左子樹為空,只需用結(jié)點(diǎn) p 的右子樹根結(jié)點(diǎn)代替結(jié)點(diǎn) p 即可;q = *p;*p = (*p)->rchild;free(q);}else if(!(*p)->rchild){//右子樹為空,只需用結(jié)點(diǎn) p 的左子樹根結(jié)點(diǎn)代替結(jié)點(diǎn) p 即可;q = *p;*p = (*p)->lchild;//這里不是指針 *p 指向左子樹,而是將左子樹存儲(chǔ)的結(jié)點(diǎn)的地址賦值給指針變量 pfree(q);}else{//左右子樹均不為空,采用第 2 種方式q = *p;s = (*p)->lchild;//遍歷,找到結(jié)點(diǎn) p 的直接前驅(qū)while(s->rchild){q = s;s = s->rchild;}//直接改變結(jié)點(diǎn) p 的值(*p)->data = s->data;//判斷結(jié)點(diǎn) p 的左子樹 s 是否有右子樹,分為兩種情況討論if( q != *p ){q->rchild = s->lchild;//若有,則在刪除直接前驅(qū)結(jié)點(diǎn)的同時(shí),令前驅(qū)的左孩子結(jié)點(diǎn)改為 q 指向結(jié)點(diǎn)的孩子結(jié)點(diǎn)}else{q->lchild = s->lchild;//否則,直接將左子樹上移即可}free(s);}return TRUE; } int DeleteBST(BiTree *T, int key) {if( !(*T)){//不存在關(guān)鍵字等于key的數(shù)據(jù)元素return FALSE;}else{if( key == (*T)->data ){Delete(T);return TRUE;}else if( key < (*T)->data){//使用遞歸的方式return DeleteBST(&(*T)->lchild, key);}else{return DeleteBST(&(*T)->rchild, key);}} } void order(BiTree t)//中序輸出 {if(t == NULL){return ;}order(t->lchild);printf("%d ", t->data);order(t->rchild); } int main() {int i;int a[5] = {3,4,2,5,9};BiTree T = NULL;for( i = 0; i < 5; i++ ){InsertBST(&T, a[i]);}printf("中序遍歷二叉排序樹:\n");order(T);printf("\n");printf("刪除3后,中序遍歷二叉排序樹:\n");DeleteBST(&T,3);order(T); } // 中序遍歷二叉排序樹: 2 3 4 5 9 刪除3后,中序遍歷二叉排序樹: 2 4 5 96.二叉排序樹小結(jié)
使用二叉排序樹在查找表中做查找操作的時(shí)間復(fù)雜度同建立的二叉樹本身的結(jié)構(gòu)有關(guān)。即使查找表中各數(shù)據(jù)元素完全相同,但是不同的排列順序,構(gòu)建出的二叉排序樹大不相同。
例如:查找表 {45,24,53,12,37,93} 和表 {12,24,37,45,53,93} 各自構(gòu)建的二叉排序樹圖下圖所示:
使用二叉排序樹實(shí)現(xiàn)動(dòng)態(tài)查找操作的過程,實(shí)際上就是從二叉排序樹的根結(jié)點(diǎn)到查找元素結(jié)點(diǎn)的過程,所以時(shí)間復(fù)雜度同被查找元素所在的樹的深度(層次數(shù))有關(guān)。
本篇博客轉(zhuǎn)載C語(yǔ)言中文網(wǎng)
總結(jié)
以上是生活随笔為你收集整理的二叉排序树(完整案例与完整C语言代码)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分块查找(完整案例与C语言完整代码实现)
- 下一篇: 平衡二叉排序树(完整案例详解及完整C代码