二叉查找樹詳解

目錄

二叉查找樹的定義

二叉查找樹的基本操作

查找

插入

建立

刪除

二叉樹查找樹的性質

二叉查找樹的定義

二叉查找樹是一種特殊的二叉樹,又稱為排序二叉樹、二叉搜索樹、二叉排序樹。

二叉樹的遞歸定義如下:

(1)要么二叉查找樹是一棵空樹。

(2)要么二叉查找樹由根節點、左子樹、右子樹組成,其中左子樹和右子樹都是二叉查找樹,且左子樹上所以結點的數據域均小于或等于根節點的數據域,右子樹上的所有結點的數據域均大于根節點的數據域。

二叉查找樹的基本操作

查找

進行二叉樹的查找操作時,由于無法確定二叉樹的具體特性,因此只能對左右子樹都進行遞歸遍歷。但是二叉查找樹的性質決定了可以只選擇一棵子樹進行遍歷。因此查找將會是從根節點查找的一條路徑,故最壞時間復雜度是O\left ( h \right ),其中h是二叉查找樹的高度。于是就可以得到查找操作的基本思路:

(1)如果當前根節點root為空,說明查找失敗,返回。

(2)如果需要查找的x等于當前根節點的數據域root->data,查找成功,訪問。

(3)如果需要查找的x小于當前根節點的數據域root->data,說明應該往左子樹查找,因此向root->lchild遞歸。

(4)如果需要查找的x大于當前結點的數據域root->data,則應該往右子樹查找,因此向root->rchild遞歸。

void search(node* root,int x){if(root==NULL){printf("search failed\n");return;}if(x==root->data){printf("%d\n",root->data);}else if(x<root->data){search(root->lchild,x);}else{search(root->rchild,x);}
}

可以看到,和普通二叉樹的查找函數不同,二叉查找樹的查找在于對左右子樹的選擇遞歸。在普通二叉樹中,無法確定需要查找的值x到底是在左子樹還是右子樹,但是在二叉查找樹中就可以確定,因為二叉查找樹中的數據域順序總是左子樹<根節點<右子樹。

插入

對一棵二叉查找樹來說,查找某個數據域的結點一定是沿著確定的路徑進行的。因此,當對某個需要查找的值在二叉查找樹中查找成功,說明結點已經存在。反之,如果這個需要查找的值在二叉查找樹中查找失敗,那么說明查找失敗的地方一定是結點需要插入的地方。因此可以在上面查找操作的基礎上,在root==NULL時新建需要插入的點。

void insert(node* &root,int x){if(root==NULL){root=newNode[x];return;}if(x==root->data){return;}else if(x<root->data){insert(root->lchild,x);}else{insert(root->rchild,x);}
}

建立

node* Create(int data[],int n){node* root=NULL;for(int i=0;i<n;i++){insert(root,data[i]);}return root;
}

需要注意的是,即使是一組相同的數字,如果插入它們的順序不同,最后生成的二叉查找樹也可能不同。

刪除

把二叉查找樹中比結點權值小的最大結點稱為該結點的前驅,而把比結點權值大的最小結點稱為該結點的后繼。顯然,結點的前驅是該結點左子樹的最右結點(也就是從左子樹根節點開始不斷沿著rchild往下直到rchild為NULL時的結點),而結點的后繼則是該結點右子樹的最左節點(也就是從右子樹根節點開始不斷沿著lchild往下直到lchild為NULL時的結點)。下面兩個函數用來尋找以root為根的樹中最大或最小權值的結點,用以輔助尋找結點的前驅和后繼:

//尋找以root為根結點的樹中的最大權值結點 
node* findMax(node* root){while(root->rchild!=NULL){root=root->rchild;}return root;
}
//尋找以root為根結點的樹中的最小權值結點
node* findMin(node* root){while(root->lchild!=NULL){root=root->lchild;}return root;
} 

刪除操作的基本思路如下:

(1)如果當前結點root為空,說明不存在權值為給定權值x的結點,直接返回。

(2)如果當前結點root的權值恰為給定的權值x,說明找到了想要刪除的結點,此時進入刪除處理。如果當前結點root不存在左右孩子,說明是葉子節點,直接刪除。如果當前結點root存在左孩子,那么在左子樹中尋找結點前驅pre,然后讓前驅的數據覆蓋root,接著在左子樹中刪除結點pre。如果當前結點root存在右孩子,那么在右子樹中尋找結點后繼next,然后讓next的數據覆蓋root,接著在右子樹中刪除結點next。

(3)如果當前結點root的權值大于給定權值的x,則在左子樹中遞歸刪除權值為x的結點。

(4)如果當前結點root的權值小于給定權值的x,則在右子樹中遞歸刪除權值為x的結點。

//尋找以root為根結點的樹中的最大權值結點 
node* findMax(node* root){while(root->rchild!=NULL){root=root->rchild;}return root;
}
//尋找以root為根結點的樹中的最小權值結點
node* findMin(node* root){while(root->lchild!=NULL){root=root->lchild;}return root;
} 
void deleteNode(node* &root,int x){if(root==NULL){return;}if(root->data==x){if(root->lchild==NULL&&root->rchild==NULL){root==NULL;}else if(root->lchild!=NULL){node* pre=findMax(root->lchild);root->data=pre->data;deleteNode(root->lchild,pre->data);}else{node* next=findMin(root->rchild);root->data=next->data;deleteNode(root->rchild,next->data);}}else if(root->data>x){deleteNode(root->lchild,x);}else{deleteNode(root->rchild,x);}
}

但是也要注意,總是優先刪除前驅或后繼容易導致樹的左右子樹高度極度不平衡,使得二叉查找樹退化成一條鏈。解決這一問題的辦法有兩種:一種是每次交替刪除前驅或后繼;另一種是記錄子樹高度,總是優先在高度較高的一棵子樹里刪除結點。

二叉樹查找樹的性質

二叉查找樹一個實用的性質:對二叉查找樹進行中序遍歷,遍歷的結果是有序的

另外,如果合理調整二叉查找樹的形態,使得樹上的每個結點都盡量有兩個子節點,這樣整個二叉查找樹的高度就會很低,也即樹的高度大概在logN的級別。

例題

給出N個正整數來作為一棵二叉排序樹的結點插入順序,問:這串序列是否是該二叉排序樹的先序序列或是該二叉排序樹的鏡像樹的先序序列。所謂鏡像樹是指交換二叉樹的所有結點的左右子樹而形成的樹(也即左子樹所有結點數據域大于或等于根節點,而根節點數據域小于右子樹所有結點的數據域)。如果是,則輸出YES,并輸出對應的樹的后序序列;否則,輸出NO。

思路

通過給定的插入序列,構建出二叉排序樹。對鏡像樹的先序遍歷只需要在原樹的先序遍歷時交換左右子樹的訪問順序即可。

//鏡像樹先序遍歷,結果存放于vi
void preordermirror(node* root,vector<int>&vi){if(root==NULL){return;}vi.push_back(root->data);preordermirror(root->right,vi);preordermirror(root->left,vi);
} 

注意點

使用vector來存放初始序列、先序序列、鏡像樹先序序列,可以方便相互之間的比較。若使用數組,則比較操作需要使用循環才能實現。

#include<cstdio>
#include<vector>
using namespace std;
struct node{int data;node* left,*right;
}; 
vector<int> origin,pre,preM,post,postM;
void insert(node* &root,int data){if(root==NULL){root=new node;root->data=data;root->left=root->right=NULL;return;}if(data<root->data){insert(root->left,data);}else{insert(root->right,data);}
}
void preorder(node* root,vector<int> &vi){if(root==NULL){return;}vi.push_back(root->data);preorder(root->left,vi);preorder(root->right,vi);
}
void preordermirror(node* root,vector<int> &vi){if(root==NULL){return;}vi.push_back(root->data);preordermirror(root->right,vi);preordermirror(root->left,vi);
}
void postorder(node* root,vector<int> &vi){if(root==NULL){return;}postorder(root->left,vi);postorder(root->right,vi);vi.push_back(root->data);
}
void postordermirror(node* root,vector<int> &vi){if(root==NULL){return;}postordermirror(root->right,vi);postordermirror(root->left,vi);vi.push_back(root->data);
}
int main(){int n,data;node* root=NULL;scanf("%d",&n);for(int i=0;i<n;i++){scanf("%d",&data);origin.push_back(data);insert(root,data);}preorder(root,pre);preordermirror(root,preM);postorder(root,post);postordermirror(root,postM);if(origin==pre){printf("YES\n");for(int i=0;i<post.size();i++){printf("%d",post[i]);if(i<post.size()-1){printf(" ");}}}else if(origin==preM){printf("YES\n");for(int i=0;i<postM.size();i++){printf("%d",postM[i]);if(i<postM.size()-1){printf(" ");}}}else{printf("NO\n");return 0;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/24537.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/24537.shtml
英文地址,請注明出處:http://en.pswp.cn/web/24537.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

10. MySQL 用戶

文章目錄 【 1. 權限表 】1.1 user 權限表1.1.1 用戶列1.1.2 權限列1.1.3 安全列1.1.4 資源控制列 1.2 db 表用戶列權限列 1.3 tables_priv 表1.4 columns_priv 表1.5 procs_priv表 【 2. 用戶管理 】2.1 創建用戶 CREATE USER2.2 用戶的登陸、退出登陸 MySQL退出 MySQL 2.3 重…

Java常見錯誤-內部類-簡要分析

Java常見錯誤-內部類-簡要分析 概念分類成員內部類&#xff08;非靜態內部類&#xff09;靜態內部類成員內部類和靜態內部類區別 局部內部類匿名內部類 注意事項總結 概念 ? 內部類&#xff0c;顧名思義&#xff0c;就是在一個類的內部定義的類。這種設計允許將一個類的實現細…

深度學習-10-測試

深度學習-10-測試 本文是《深度學習入門2-自製框架》 的學習筆記&#xff0c;記錄自己學習心得&#xff0c;以及對重點知識的理解。如果內容對你有幫助&#xff0c;請支持正版&#xff0c;去購買正版書籍&#xff0c;支持正版書籍不僅是尊重作者的辛勤勞動&#xff0c;也是鼓勵…

Web前端ES6-ES13筆記合集(下)

#### 五.ES10新特性 ##### 1. Object.fromEntries > Object.fromEntries()方法允許你輕松地將鍵值對列表轉換為對象 js const arr [["name", "kerwin"], ["age", 100]]; console.log(Object.fromEntries(arr))//{name: kerwin, age: 100} …

pytorch 筆記:pytorch 優化內容(更新中)

1 Tensor創建類 1.1 直接創建Tensor&#xff0c;而不是從Python或Numpy中轉換 不要使用原生Python或NumPy創建數據&#xff0c;然后將其轉換為torch.Tensor直接用torch.Tensor創建或者直接&#xff1a;torch.empty(), torch.zeros(), torch.full(), torch.ones(), torch.…

樹莓派【Raspberry Pi-64位】3b+,Pi4J 2.0入門

一.前言: 前面的文章講解了樹莓派在centos7 arm64版本下的使用,用一款智能小車為例子,做了代碼實踐。 由于centos7不再維護,且Pi4J 1.x版本也因為WiringPi 的局限,Pi4J從1.x升級為2.x.所以本專欄的技術棧也將進行調整,A.從centos7系統回到Raspberry Pi-64位系統。B.Pi4…

4.通用編程概念

目錄 一、變量與常量1.1 變量1.2 常量 二、遮蔽三、數據類型3.1 標量類型1. 整型2. 浮點型3. 布爾類型4.字符類型 3.2 復合類型1. 元組2. 數組 四、函數五、語句和表達式六、函數的返回值 一、變量與常量 1.1 變量 在Rust中默認的變量是不可變的&#xff0c;如果修改其值會導致…

《青少年編程與數學》課程方案:4、課程策略

《青少年編程與數學》課程方案&#xff1a;4、課程策略 一、工程師思維二、使命感驅動三、價值觀引領四、學習現代化五、工作生活化六、與時代共進 《青少年編程與數學》課程策略強調采用工程師思維&#xff0c;避免重復造輪子&#xff0c;培養使命感&#xff0c;通過探索興趣、…

編程語言有哪些?這些希望你都知道

編程語言有哪些 編程語言有很多種&#xff0c;包括但不限于以下幾種&#xff1a; Java&#xff1a;當今最普遍使用的開發語言之一&#xff0c;簡單易學&#xff0c;且跨平臺性非常強&#xff0c;對網絡開發的支持令人稱贊。Python&#xff1a;語法清楚&#xff0c;干凈&#…

【Vue】如何提供訪問vuex的數據

文章目錄 一、提供數據二、訪問Vuex中的數據通過$store訪問的語法1&#xff09;模板中使用2&#xff09;組件邏輯中使用3&#xff09;js文件中使用 三、通過輔助函數 - mapState獲取 state中的數據 一、提供數據 State提供唯一的公共數據源&#xff0c;所有共享的數據都要統一…

[office] 快速刪除excel中的空行和列的方法 #其他#學習方法#經驗分享

快速刪除excel中的空行和列的方法 用戶在網上下載好的Excel表格打開之后發現有很多空白行&#xff0c;怎么樣將這些空白行或單元格一次性刪除掉呢?下面教大家在Excel中用定位一次性可以把空白行刪除 用戶在網上下載好的Excel表格打開之后發現有很多空白行&#xff0c;怎么樣將…

Vue3 使用audio播放語音+監聽播放語音完成事件

需求&#xff1a;輸入一段文字&#xff0c;點擊語音框&#xff0c;將本地語音&#xff08;提前準備好的&#xff09; 播放出來 播放中效果 代碼 <div class"listConAI" click"handleOpenSpeech" ><imgsrc"../../../../assets/images/blueo…

web前端 孫俏:深度探索與實戰之路

web前端 孫俏&#xff1a;深度探索與實戰之路 在這個數字化、信息化的時代&#xff0c;web前端技術以其獨特的魅力&#xff0c;吸引著越來越多的開發者投身其中。今天&#xff0c;我們將跟隨孫俏的腳步&#xff0c;一同探索web前端的深度與廣度&#xff0c;揭開其神秘的面紗。…

中文文案寫作有哪些合適的AIGC工具?

這是計育韜老師第 8 次開展面向全國高校的新媒體技術公益巡講活動了。而在每場講座尾聲&#xff0c;互動答疑環節往往反映了高校師生當前最普遍的運營困境&#xff0c;特此計老師在現場即興答疑之外&#xff0c;會盡量選擇有較高價值的提問進行文字答疑梳理。 *本輪巡講主題除了…

【Vue】開啟嚴格模式及Vuex的單項數據流

文章目錄 一、引出問題二、開啟嚴格模式 一、引出問題 目標 明確 vuex 同樣遵循單向數據流&#xff0c;組件中不能直接修改倉庫的數據 這樣數據的流向才會更加清晰&#xff0c;將來對數據的修改&#xff0c;都在倉庫內部實現的&#xff0c;更易于維護 直接在組件中修改Vuex中…

Git:版本控制的藝術與GitLab實戰指南

在當今快速發展的軟件開發領域&#xff0c;高效、協同的代碼管理是項目成功的關鍵。Git&#xff0c;作為一款分布式版本控制系統&#xff0c;憑借其強大的功能和靈活性&#xff0c;成為了眾多開發者首選的版本控制工具。本文將帶您深入探索Git的核心概念、基礎操作&#xff0c;…

B3870 [GESP202309 四級] 變長編碼

[GESP202309 四級] 變長編碼 題目描述 小明剛剛學習了三種整數編碼方式&#xff1a;原碼、反碼、補碼&#xff0c;并了解到計算機存儲整數通常使用補碼。但他總是覺得&#xff0c;生活中很少用到 2 31 ? 1 2^{31}-1 231?1 這么大的數&#xff0c;生活中常用的 0 ~ 100 0…

Spring進階技巧:利用AOP提前介入的巧妙實踐

Spring框架中的面向切面編程&#xff08;AOP&#xff09;是一種強大的機制&#xff0c;它允許開發者在不修改原有代碼的情況下&#xff0c;對程序進行橫向切面的功能擴展。AOP提供了一種方式&#xff0c;可以在目標Bean的生命周期早期階段就實施切面邏輯&#xff0c;這為我們在…

Python 中如何使用 lambda 函數

在 Python 中&#xff0c;可以使用 lambda 函數來創建匿名函數。lambda 函數的語法是&#xff1a;lambda 參數: 表達式。以下是一些使用 lambda 函數的例子&#xff1a; 通過 lambda 函數來計算兩個數的和&#xff1a; add lambda x, y: x y print(add(2, 3)) # 輸出 5通過…

Oracle 日志挖掘

oracle 11g 日志挖掘測試 需要開啟補充日志 alter database add supplemental log data; SELECT SUPPLEMENTAL_LOG_DATA_MIN, SUPPLEMENTAL_LOG_DATA_PK, SUPPLEMENTAL_LOG_DATA_UI FROM V$DATABASE;在用戶下執行一些刪除&#xff0c;插入等操作 SQL> create table zxy( …