1. 程式人生 > >二叉排序樹基本功能實現(C++)

二叉排序樹基本功能實現(C++)


二叉排序樹(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;
}