二叉排序樹基本功能實現(C++)
阿新 • • 發佈:2019-01-12
二叉排序樹(Binary Sort Tree )也稱二叉搜尋樹(Binary Search Tree),以下簡稱BST。
- 它的特點是左小右大(左子樹小於根,右子樹大於根),令人困惑的是他不允許相等存在,一定要分個高低。這個特點與二叉堆排序有所不同,堆是允許存在相同關鍵字的,所以堆可用於任意排序;而BST建立後必定是一個無重複關鍵字的樹,對其中序遍歷,必為升序,這個過程等價於將一個連結串列去重後再排序(或是排序後去重)
- 顧名思義,這個結構可以用來排序,也可用來搜尋(查詢)
- 他是特殊的二叉樹,意味著它可以用二叉樹的遍歷方法,但是根據其定義可知,與普通二叉樹建立方式有所不同
看看效果圖
資料結構
typedef struct BSTNode{
int key;//關鍵字
struct BSTNode *lchild,*rchild;//左右子樹
}BSTNode,*BSTree;
BST的構建
到了反常識的時刻了,通常我們的認知是某個東西先存在,然後才可以對其查、改、刪。這是我們 習以為常的思考模式,然而實現該思想卻需要倒過來:先查重,再確定是否插入,反覆這兩個過程,從而建立BST
查詢(遞迴)
查詢可謂BST構建中最為核心的一步,其它操作均建立在此基礎上,查詢思想與普通二叉樹基本一致,不過是添加了自身特有的判斷,為了能為插入,刪除服務,設計時添加了f,p,fd三個引數
- f為T的雙親,為記錄插入位置準備(f初始為空)
- 查詢失敗:p儲存即將被插入的元素的雙親(為插入服務)
- 查詢成功:p記錄關鍵字為e的節點位置;fd記錄關鍵字為e的節點的雙親位置(為刪除服務)
//二叉排序樹關鍵字左小右大&&無重複關鍵字
//所以通過插入建立二叉查詢樹時必須先查重,所以查詢就尤為重要
//為了查詢直接為插入服務,所以查詢過程需要記錄插入的位置,
//引數設計:
//1,返回值true表示找到;false表示未找到
//2,T為當前需查詢的BST;e為需查詢的關鍵字;
// f為T的雙親,為記錄插入位置準備(f初始為空);查詢失敗:p記錄即將被插入的元素的雙親;查詢成功:p記錄關鍵字為e的節點位置
//遞迴設計:
//狀態分解(4中狀態):
//T空;T非空:T->key =/</> e
//《結束條件:》
//1,T空說明查詢失敗,返回false,儲存即將要插入元素的雙親位置
//2,T非空且T->key=e,說明查詢成功,返回true,並儲存當前位置
//3,T非空且T->key<e,在T的右子樹繼續查詢
//4,T非空且T->key>e,在T的左子樹繼續查詢
bool SearchBST(BSTree T,int e,BSTNode* f,BSTNode* &p,BSTNode* &fd)
{ if(T == NULL){//結束狀態1:查詢失敗
p = f;//記錄即將被插入節點的雙親
return false;
}
else{
if(T->key == e){//結束狀態2:查詢成功
fd = f;
p = T;//可獲取目標節點
return true;
}
else if(T->key < e) return SearchBST(T->rchild,e,T,p,fd);//在右子樹繼續查詢
else return SearchBST(T->lchild,e,T,p,fd);//在左子樹繼續查詢
}
}
插入
借用查詢利器,取得被插入點的雙親位置,判斷下插入的左右即可
//插入:利用查詢函式
void InsertBST(BSTree &T,int e)
{
BSTNode *f=NULL,*p=NULL,*pcur,*fd=NULL;
if(!SearchBST(T,e,f,p,fd)){
//將e置入節點
pcur = (BSTNode*)malloc(sizeof(BSTNode));
pcur->key = e;
pcur->lchild = pcur->rchild = NULL;
if(p == NULL)T = pcur;//根節點為空
else{
if(p->key < e)p->rchild = pcur;//判斷插入位置
else p->lchild = pcur;
}
}
}
建立二叉排序樹
如你所見,基礎打好,建立僅需反覆呼叫查詢與插入即可
檔案二叉排序樹.txt內容
45 24 53 45 12 24 90 12 2 100 23 32 14 430 0 9 8
//從檔案讀取資料並建立二叉排序樹
//反覆呼叫插入
void CreateBST(BSTree &T)
{
fstream inFile("二叉排序樹.txt",ios::in);
if(!inFile)cout<<"二叉排序樹.txt 開啟失敗!"<<endl;
int t;
while(true)
{
inFile>>t;
if(!inFile)break;//cout<<t<<endl;
InsertBST(T,t);
}
inFile.close();
}
如何刪除?
在普通二叉樹中刪除一個節點時沒有意義的,因為破壞了樹的結構,成為森林。而BST是一個序列,刪除一個元素還是一個序列,只要通過調整依舊是BST,所以刪除的關鍵在於如何調整,像是堆排序關鍵在於篩選,也是調整的一種。根據刪除點的特徵,可分為三類:
刪除節點:分三種情況(假設刪除點p)
- 1,p為葉子,直接刪除
- 2,p為單枝,僅有左/右子樹,單枝上移即可
- 3,p為雙枝,左右子樹均有,令p的左子樹的最右節點s替代p,同時刪除s,由於s是左子樹最右節點,所以不可能為雙枝,於是又回到了情況1,2
tips:根需特殊處理,因為其無雙親
//刪除節點:分三種情況(假設刪除點p)
//1,p為葉子,直接刪除
//2,p為單枝,僅有左/右子樹,單枝上移即可
//3,p為雙枝,左右子樹均有,令p的左子樹的最右節點s替代p,
// 同時刪除s,由於s是左子樹最右節點,所以不可能為雙枝,於是又回到了情況1,2
//根需特殊處理,因為其無雙親
void DeleteBST(BSTree &T,int e)
{
BSTNode *f=NULL,*fd=NULL,*p=NULL;
if(SearchBST(T,e,f,p,fd)){//查詢成功
if(p->lchild == NULL && p->rchild == NULL){//葉子
if(fd == NULL)T = NULL;//根處理
else{
if(fd->key < p->key)fd->rchild = NULL;//判斷左右
else fd->lchild = NULL;
}
free(p);
}
else if(p->lchild == NULL && p->rchild != NULL){//右單枝
if(fd == NULL)T = p->rchild;//根處理
else if(fd->key < p->key)fd->rchild = p->rchild;//判左右
else fd->lchild = p->rchild;
free(p);
}
else if(p->lchild != NULL && p->rchild == NULL){//左單枝
if(fd == NULL)T = p->lchild;
else if(fd->key < p->key)fd->rchild = p->lchild;
else fd->lchild = p->lchild;
free(p);
}
else{//雙枝
BSTNode *fs,*s;//s為p的左子樹最右節點,fs為s的雙親 ;尋找s:向左移一個節點,在向右走到頭
fs = p;
s = p->lchild;// 左移一個節點
while(s->rchild != NULL){//向右走到盡頭
fs = s;
s = s->rchild;
}
p->key = s->key;//有待改進!!!,資料量大時指標操作較方便
if(fs->key < s->key)fs->rchild = s->lchild;
else fs->lchild = s->lchild;
free(s);
}
}
}
中序遍歷(測試使用)
- 二叉樹的遍歷方法皆可用
- 測試使用(升序即正確)
//列印除錯:若建立正確,中序遍歷輸出結果必為升序
//BST:左小右大;中序遍歷:左根右
//建立的BST中一定沒有重複值(所有節點元素可以構成一個集合)
void InOrderTraverseBST(BSTree T)
{
if(T == NULL)return;
else{
InOrderTraverseBST(T->lchild);
cout<<T->key<<" ";
InOrderTraverseBST(T->rchild);
}
}
小收穫
- 寫程式碼時只給指標變數賦值是無法改變真正指向的
- 遞迴設計關鍵在於退出條件設計,退出條件依賴於對狀態的分析,只要捋順狀態轉換,遞迴就清晰易懂;遞迴定義的結構通常可以使用遞迴求解,如與二叉樹相關的數結構,堆,哈夫曼樹等等,共性比較強
完整程式碼
#include<iostream>
using namespace std;
#include<fstream>
#include<stdlib.h>
typedef struct BSTNode{
int key;
struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;
//二叉排序樹關鍵字左小右大&&無重複關鍵字
//所以通過插入建立二叉查詢樹時必須先查重,所以查詢就尤為重要
//為了查詢直接為插入服務,所以查詢過程需要記錄插入的位置,
//引數設計:
//1,返回值true表示找到;false表示未找到
//2,T為當前需查詢的BST;e為需查詢的關鍵字;
// f為T的雙親,為記錄插入位置準備(f初始為空);查詢失敗:p記錄即將被插入的元素的雙親;查詢成功:p記錄關鍵字為e的節點位置
//遞迴設計:
//狀態分解(4中狀態):
//T空;T非空:T->key =/</> e
//《結束條件:》
//1,T空說明查詢失敗,返回false,儲存即將要插入元素的雙親位置
//2,T非空且T->key=e,說明查詢成功,返回true,並儲存當前位置
//3,T非空且T->key<e,在T的右子樹繼續查詢
//4,T非空且T->key>e,在T的左子樹繼續查詢
bool SearchBST(BSTree T,int e,BSTNode* f,BSTNode* &p,BSTNode* &fd)
{ if(T == NULL){//結束狀態1:查詢失敗
p = f;//記錄即將被插入節點的雙親
return false;
}
else{
if(T->key == e){//結束狀態2:查詢成功
fd = f;
p = T;//可獲取目標節點
return true;
}
else if(T->key < e) return SearchBST(T->rchild,e,T,p,fd);//在右子樹繼續查詢
else return SearchBST(T->lchild,e,T,p,fd);//在左子樹繼續查詢
}
}
//插入:利用查詢函式
void InsertBST(BSTree &T,int e)
{
BSTNode *f=NULL,*p=NULL,*pcur,*fd=NULL;
if(!SearchBST(T,e,f,p,fd)){
//將e置入節點
pcur = (BSTNode*)malloc(sizeof(BSTNode));
pcur->key = e;
pcur->lchild = pcur->rchild = NULL;
if(p == NULL)T = pcur;//根節點為空
else{
if(p->key < e)p->rchild = pcur;//判斷插入位置
else p->lchild = pcur;
}
}
}
//從檔案讀取資料並建立二叉排序樹
//反覆呼叫插入
void CreateBST(BSTree &T)
{
fstream inFile("二叉排序樹.txt",ios::in);
if(!inFile)cout<<"二叉排序樹.txt 開啟失敗!"<<endl;
int t;
while(true)
{
inFile>>t;
if(!inFile)break;//cout<<t<<endl;
InsertBST(T,t);
}
inFile.close();
}
//列印除錯:若建立正確,中序遍歷輸出結果必為升序
//BST:左小右大;中序遍歷:左根右
//建立的BST中一定沒有重複值(所有節點元素可以構成一個集合)
void InOrderTraverseBST(BSTree T)
{
if(T == NULL)return;
else{
InOrderTraverseBST(T->lchild);
cout<<T->key<<" ";
InOrderTraverseBST(T->rchild);
}
}
//刪除節點:分三種情況(假設刪除點p)
//1,p為葉子,直接刪除
//2,p為單枝,僅有左/右子樹,單枝上移即可
//3,p為雙枝,左右子樹均有,令p的左子樹的最右節點s替代p,
// 同時刪除s,由於s是左子樹最右節點,所以不可能為雙枝,於是又回到了情況1,2
//根需特殊處理,因為其無雙親
void DeleteBST(BSTree &T,int e)
{
BSTNode *f=NULL,*fd=NULL,*p=NULL;
if(SearchBST(T,e,f,p,fd)){//查詢成功
if(p->lchild == NULL && p->rchild == NULL){//葉子
if(fd == NULL)T = NULL;//根處理
else{
if(fd->key < p->key)fd->rchild = NULL;//判斷左右
else fd->lchild = NULL;
}
free(p);
}
else if(p->lchild == NULL && p->rchild != NULL){//右單枝
if(fd == NULL)T = p->rchild;//根處理
else if(fd->key < p->key)fd->rchild = p->rchild;//判左右
else fd->lchild = p->rchild;
free(p);
}
else if(p->lchild != NULL && p->rchild == NULL){//左單枝
if(fd == NULL)T = p->lchild;
else if(fd->key < p->key)fd->rchild = p->lchild;
else fd->lchild = p->lchild;
free(p);
}
else{//雙枝
BSTNode *fs,*s;//s為p的左子樹最右節點,fs為s的雙親 ;尋找s:向左移一個節點,在向右走到頭
fs = p;
s = p->lchild;// 左移一個節點
while(s->rchild != NULL){//向右走到盡頭
fs = s;
s = s->rchild;
}
p->key = s->key;//有待改進!!!,資料量大時指標操作較方便
if(fs->key < s->key)fs->rchild = s->lchild;
else fs->lchild = s->lchild;
free(s);
}
}
}
int main()
{
BSTree T = NULL;
CreateBST(T);
InOrderTraverseBST(T);
int choice;
while(true)
{
cout<<endl<<"0--退出 1--插入 2--查詢 3--刪除"<<endl;
cout<<"請輸入選擇:";
cin>>choice;
if(choice == 0)break;
int t;
BSTNode *f=NULL,*p=NULL,*fd=NULL;
switch(choice){
case 1:cout<<endl<<"請輸入插入數:";
cin>>t;
InsertBST(T,t);
cout<<endl<<"插入後:";
InOrderTraverseBST(T);
break;
case 2:cout<<endl<<"請輸入查詢數:";
cin>>t;
if(SearchBST(T,t,f,p,fd))cout<<endl<<" 當前:"<<p->key<<endl;
break;
case 3:cout<<endl<<"請輸入刪除數:";
cin>>t;
DeleteBST(T,t);
cout<<endl<<"刪除後:";
InOrderTraverseBST(T);break;
}
}
return 0;
}