真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

C++之二叉搜索樹詳解-創(chuàng)新互聯(lián)

文章目錄
  • 前言
  • 一、二叉搜索樹的概念
  • 二、二叉搜索樹的操作
    • 1.節(jié)點(diǎn)類
    • 2.二叉搜索樹類內(nèi)部定義
    • 3.遍歷操作
    • 4.構(gòu)造函數(shù)
    • 5.拷貝構(gòu)造函數(shù)
    • 6.賦值運(yùn)算符重載
    • 7.析構(gòu)函數(shù)
    • 8.插入函數(shù)
      • 非遞歸實(shí)現(xiàn)
      • 遞歸實(shí)現(xiàn)
    • 9.刪除函數(shù)
      • 非遞歸實(shí)現(xiàn)
      • 遞歸實(shí)現(xiàn)
    • 10.查找函數(shù)
      • 非遞歸實(shí)現(xiàn)
      • 遞歸實(shí)現(xiàn)
  • 三、二叉搜索樹的應(yīng)用
    • K模型
    • KV模型
  • 四、二叉搜索樹的性能分析
  • 總結(jié)

成都創(chuàng)新互聯(lián)公司主營澤普網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,app軟件定制開發(fā),澤普h5小程序開發(fā)搭建,澤普網(wǎng)站營銷推廣歡迎澤普等地區(qū)企業(yè)咨詢前言

對(duì)于已經(jīng)學(xué)習(xí)C++/C語言的朋友來說,二叉樹這個(gè)概念相信已經(jīng)再熟悉不過了,我們平時(shí)接觸的二叉樹可能都很簡單,今天我們來看一個(gè)比較有難度的二叉樹,他的名字就是——二叉搜索樹,只是聽名字就知道他其實(shí)是用來搜索數(shù)據(jù)用的,那么他到底有什么特別之處呢?下面讓我們一起來看看吧。

一、二叉搜索樹的概念

二叉搜索樹又稱二叉排序樹(二叉查找樹),它或者是一棵空樹,或者是具有以下性質(zhì)的二叉樹:
1.若它的左子樹不為空,則左子樹上所有結(jié)點(diǎn)的值都小于根結(jié)點(diǎn)的值。
2.若它的右子樹不為空,則右子樹上所有結(jié)點(diǎn)的值都大于根結(jié)點(diǎn)的值。
3.他的左右子樹也分別為二叉樹搜索樹
在這里插入圖片描述
上面的這棵樹就是一個(gè)典型的二叉搜索樹。

二、二叉搜索樹的操作

既然已經(jīng)大概知道一棵二叉搜索樹是什么樣子的了,那么就讓我們一起來實(shí)現(xiàn)一下吧。

1.節(jié)點(diǎn)類

我們知道二叉樹其實(shí)就是一個(gè)一個(gè)的結(jié)點(diǎn)連接在一起,然后遵循我們前面所說的概念,就能夠形成一棵二叉搜索樹了,所以結(jié)點(diǎn)類是必不可少的。

template//模板參數(shù)
struct BSTreeNode
{
	struct BSTreeNode* _left;//左指針
	struct BSTreeNode* _right;//右指針
	K _key;  //結(jié)點(diǎn)存儲(chǔ)的值(節(jié)點(diǎn)值)
//構(gòu)造函數(shù),實(shí)現(xiàn)對(duì)創(chuàng)建的結(jié)點(diǎn)的初始化
	BSTreeNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};

注: 因?yàn)槲覀兒竺娑x的二叉搜索樹需要用到我們這里定義的結(jié)點(diǎn)類,所以直接將他定義為struct的形式,因?yàn)閟truct中的成員函數(shù)和成員變量默認(rèn)是共有的,這樣方便我們后面的調(diào)用。

2.二叉搜索樹類內(nèi)部定義
templateclass BSTree
{
	typedef BSTreeNodeNode; //typedef使代碼更簡潔
public:
	//構(gòu)造函數(shù)
	//這樣可以讓編譯器強(qiáng)制自己生成默認(rèn)構(gòu)造函數(shù)
	BSTree() = default;

	//拷貝構(gòu)造
	BSTree(const BSTree& t){}

	//析構(gòu)函數(shù)
	~BSTree(){}

	BSTree& operator=(BSTreet){}
	//插入操作
	bool Insert(const K& key){}
	//刪除操作
	bool Erase(const K& key){}
	//中序遍歷
	void InOrder(){}
	//查找操作
	bool Find(const K& key)

private:
	//子函數(shù)
	Node* _Copy(Node* root){}
	void _InOrder(Node* root){}
	void _Destory(Node* root){}
private:
	Node* _root = nullptr;
};
3.遍歷操作

因?yàn)槲覀冎笠榭次覀儎?chuàng)建的樹到底符不符合我們的要求以及一些接口實(shí)現(xiàn)的正確性,所以我們需要將其中存儲(chǔ)的數(shù)據(jù)打印出來,這里還有一個(gè)細(xì)節(jié),如果我們以中序遍歷的方式去遍歷整棵二叉樹的話,最后的打印結(jié)果是以升序的方式排布的,這樣方便我們查看。

void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);//遍歷左子樹
	cout<< root->_key<< " ";
	_InOrder(root->_right);//遍歷右子樹
}
void InOrder()
{
	_InOrder(_root);//調(diào)用子函數(shù)
	cout<< endl;
}

這里大家可以看到我們上面給出了兩個(gè)函數(shù),其中_InOrder是用訪問限定符private修飾的,這里為什么要這么操作呢?
解釋:
這里的_InOrder是InOrder函數(shù)的子函數(shù),因?yàn)開root是私有成員,用戶去調(diào)用這里的遍歷函數(shù)的話就需要傳入根結(jié)點(diǎn),但是我們經(jīng)過封裝后,用戶是無法訪問_root這個(gè)成員變量的,所以才有了這里的子函數(shù)的誕生,類內(nèi)的函數(shù)是可以訪問類內(nèi)的所有成員變量的,我們可以通過子函數(shù)去實(shí)現(xiàn)函數(shù)的功能,用戶在調(diào)用函數(shù)的不需要傳入任何參數(shù),這樣既沒有會(huì)暴露我們的底層實(shí)現(xiàn),也實(shí)現(xiàn)了函數(shù)的功能。
在后面的操作中涉及到遞歸函數(shù)的時(shí)候,也會(huì)這樣使用。

4.構(gòu)造函數(shù)

這里得構(gòu)造函數(shù)是一定要自己實(shí)現(xiàn)的,因?yàn)楹竺嫖覀冃枰截悩?gòu)造函數(shù),他其實(shí)也是一個(gè)構(gòu)造函數(shù),根據(jù)我們前面所學(xué)的,當(dāng)我們自己寫了一個(gè)構(gòu)造函數(shù)的時(shí)候,編譯器是無法再自動(dòng)生成默認(rèn)構(gòu)造函數(shù)的。就不能再像下面的這種情況進(jìn)行樹的定義,所以構(gòu)造函數(shù)一定要自己顯示寫一個(gè)。

//沒有默認(rèn)構(gòu)造函數(shù)的話,就不能像這樣進(jìn)行構(gòu)造
BSTreet;

構(gòu)造函數(shù)有三種寫法:

//1.可以這么寫,順便進(jìn)行初始化
BSTree()
	:_root(nullptr)//創(chuàng)建一個(gè)空樹
{}
//2.可以這么寫,內(nèi)置類型編譯器可以自動(dòng)初始化
BSTree(){}
//3.可以這么寫
//這樣可以讓編譯器強(qiáng)制自己生成默認(rèn)構(gòu)造函數(shù)
BSTree() = default;
5.拷貝構(gòu)造函數(shù)

這里又要用到我們前面遍歷函數(shù)所講的子函數(shù),道理是相同的,這里就不再進(jìn)行描述。

//利用遞歸實(shí)現(xiàn)拷貝構(gòu)造的子函數(shù)
Node* _Copy(Node* root)
{
	//如果是空樹的話直接返回
	if (root == nullptr)
		return nullptr;
	Node* copyRoot = new Node(root->_key);//先拷貝根結(jié)點(diǎn)
	copyRoot->_left = _Copy(root->_left);//拷貝左子樹
	copyRoot->_right = _Copy(root->_right);//拷貝右子樹
	return copyRoot; //返回拷貝的樹的根結(jié)點(diǎn)
}
BSTree(const BSTree& t)
{
//因?yàn)樾枰猒root私有成員,所以需要一個(gè)子函數(shù)。
	_root = _Copy(t._root); //拷貝子函數(shù)
}

注意: 這里的拷貝構(gòu)造函數(shù)實(shí)現(xiàn)的是深拷貝。

6.賦值運(yùn)算符重載

關(guān)于賦值運(yùn)算符重載一般情況下是有兩種方法的,我們將其稱為現(xiàn)代寫法和傳統(tǒng)寫法。
現(xiàn)代寫法

BSTree& operator=(BSTreet)//函數(shù)在接收傳入的參數(shù)的時(shí)候會(huì)先自動(dòng)調(diào)用拷貝構(gòu)造函數(shù)創(chuàng)建t對(duì)象
{
	swap(_root, t._root);//將本對(duì)象的_root結(jié)點(diǎn)與t對(duì)象中的_root進(jìn)行交換,就實(shí)現(xiàn)可兩棵樹數(shù)據(jù)互換,即拷貝成功
	return *this; //這樣返回的話也就可以支持連續(xù)賦值
}

傳統(tǒng)寫法
傳統(tǒng)寫法就是將傳入的樹的結(jié)點(diǎn)一一進(jìn)行拷貝后再鏈接在一起就可以了。

BSTree& operator=(BSTree& t)
{
	if (this != &t) //防止自己給自己賦值
	{
		_root = _Copy(t._root);
	}
	return *this;//支持連續(xù)賦值
}
7.析構(gòu)函數(shù)

這里也需要實(shí)現(xiàn)一個(gè)子函數(shù),因?yàn)槲覀冊(cè)阡N毀樹的時(shí)候也需要傳入私有成員–根結(jié)點(diǎn)。

void _Destory(Node* root)
{
	//空樹直接返回
	if (root == nullptr)
	{
		return;
	}
	//轉(zhuǎn)化為子問題
	_Destory(root->_left);//銷毀左子樹
	_Destory(root->_right);//銷毀右子樹
	delete root; //釋放根結(jié)點(diǎn)
	root = nullptr;
}
//析構(gòu)函數(shù)
~BSTree()
{
	//調(diào)用子函數(shù)
	_Destory(_root);
}
8.插入函數(shù)

二叉搜索樹的插入
插入的具體過程如下:
a. 樹為空,則直接新增結(jié)點(diǎn),賦值給root指針
b. 樹不空,按二叉搜索樹性質(zhì)查找插入位置,插入新結(jié)點(diǎn)
若樹不為空其具體操作如下:
1.若插入的結(jié)點(diǎn)的值小于根結(jié)點(diǎn)的值,則需要進(jìn)入左子樹進(jìn)行插入
2.若插入的結(jié)點(diǎn)的值大于根結(jié)點(diǎn)的值,則需要進(jìn)入右子樹進(jìn)行插入
3.若插入的結(jié)點(diǎn)的值與根結(jié)點(diǎn)的值相等,則插入失敗


重復(fù)上面的操作,找到與自己相等的結(jié)點(diǎn),插入失敗,直接返回。若是走到了空結(jié)點(diǎn)的位置,則說明在該樹中沒有與之相等的值,進(jìn)行插入操作即可。

非遞歸實(shí)現(xiàn)

下面的key變量即為我們要插入的值,因?yàn)槲覀冏詈筮€要將新建的節(jié)點(diǎn)與前面的結(jié)點(diǎn)(也就是父結(jié)點(diǎn))連接起來,所以還需要個(gè)結(jié)點(diǎn)記錄父結(jié)點(diǎn)(即parent)

bool Insert(const K& key)
{
//剛進(jìn)入查找時(shí),根結(jié)點(diǎn)為空,說明是空樹,直接插入即可
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	//循環(huán)找空,cur走到空則找到了自己的位置
	while (cur)//(cur != nullptr)
	{
		//key大于當(dāng)前結(jié)點(diǎn)的值,根據(jù)性質(zhì)需要到右子樹去尋找
		if (key >cur->_key)
		{
			parent = cur;
			cur = cur->_right;
		}
		//key小于當(dāng)前結(jié)點(diǎn)的值,根據(jù)性質(zhì)需要到左子樹去尋找
		else if (key< cur->_key)
		{
			parent = cur;
			cur = cur->_left;
		}
		//上面的兩種情況都不是的話,說明key值與當(dāng)前節(jié)點(diǎn)的值相等,插入失敗
		else
			return false;
	}
	//循環(huán)結(jié)束后說明找到了屬于自己的位置,創(chuàng)建結(jié)點(diǎn)
	cur = new Node(key);
	//此時(shí)還需要判斷一下節(jié)點(diǎn)與父結(jié)點(diǎn)的關(guān)系
	//大于父結(jié)點(diǎn)的值,插入到父結(jié)點(diǎn)的右邊
	if (key >parent->_key)
		parent->_right = cur;
	//否則插入父結(jié)點(diǎn)的左邊
	else
		parent->_left = cur;
	//插入成功返回true
	return true;
}
遞歸實(shí)現(xiàn)
//子函數(shù)實(shí)現(xiàn)遞歸
bool _InsertR(Node*& root, const K& key)//注意引用,十分重要
{
	//查找時(shí),根結(jié)點(diǎn)為空,說明是空樹或找到了插入的位置,直接插入
	if (root == nullptr)
	{
		root = new Node(key);
		//插入成功,返回true
		return true;
	}
	//key大于當(dāng)前結(jié)點(diǎn)的值,根據(jù)性質(zhì)需要到右子樹去尋找
	if (key >root->_key)
	{
		//進(jìn)入右子樹
		return _InsertR(root->_right, key);
	}
	//key小于當(dāng)前結(jié)點(diǎn)的值,根據(jù)性質(zhì)需要到左子樹去尋找
	else if (key< root->_key)
	{
		//進(jìn)入左子樹
		return _InsertR(root->_left, key);
	}
	//上面的兩種情況都不是的話,說明key值與當(dāng)前結(jié)點(diǎn)的值相等,插入失敗
	else
		return false;
}
//外部函數(shù)對(duì)遞歸函數(shù)實(shí)現(xiàn)封裝
bool InsertR(const K& key)
{
	//直接調(diào)用子函數(shù)
	return _InsertR(_root, key);
}

注意: 本函數(shù)中最精華的地方就是傳的是引用參數(shù),因?yàn)橛幸玫拇嬖冢覀兙筒恍枰ビ涗浉腹?jié)點(diǎn)所在的位置,直接修改root的指向,同時(shí)也就修改了父結(jié)點(diǎn)的指向。

9.刪除函數(shù)

在實(shí)現(xiàn)二叉搜索樹的函數(shù)中,刪除操作是最復(fù)雜的,其中的情況有很多。
首先查找元素是否在二叉搜索樹中,如果不存在,則返回,否則要?jiǎng)h除的結(jié)點(diǎn)可能分下面的四種情況:
a.要?jiǎng)h除的結(jié)點(diǎn)無孩子結(jié)點(diǎn)
b.要?jiǎng)h除的結(jié)點(diǎn)左子樹為空
c.要?jiǎng)h除的結(jié)點(diǎn)右子樹為空
d.要?jiǎng)h除的結(jié)點(diǎn)左右子樹均不為空
看起來要?jiǎng)h除結(jié)點(diǎn)是有4中情況,實(shí)際情況中情況a可以與情況b或者情況c合并起來,因此真正的刪除過程如下:
1.要?jiǎng)h除的結(jié)點(diǎn)左子樹為空(左右子樹均為空)
2.要?jiǎng)h除的結(jié)點(diǎn)右子樹為空
3.要?jiǎng)h除的結(jié)點(diǎn)左右子樹均不為空
只是這么看的話,可能比較抽象,下面我們將其分開剖析,然后再結(jié)合畫圖,一起看看如何實(shí)現(xiàn)其代碼。
情況1:要?jiǎng)h除的結(jié)點(diǎn)左子樹為空(左右子樹均為空)
這里我們將前面所說的情況a放入情況b中一起講解。
這里為了更好地演示,我會(huì)將幾個(gè)空結(jié)點(diǎn)也畫出來。
1.先來看左右子樹均為空的場(chǎng)景
在這里插入圖片描述
2.再來看一下左子樹為空的場(chǎng)景
場(chǎng)景1:
在這里插入圖片描述
通過上面兩個(gè)圖的對(duì)比,我們可以看出在情況a與情況b中的操作是相同的,所以我們可以將他們歸為一種情況去操作。
其實(shí)在刪除左子樹為空的結(jié)點(diǎn)的時(shí)候是有兩個(gè)場(chǎng)景的,上面的場(chǎng)景是要?jiǎng)h除的結(jié)點(diǎn)在父節(jié)點(diǎn)的左邊,下面的這種場(chǎng)景是要?jiǎng)h除的結(jié)點(diǎn)在父節(jié)點(diǎn)的右邊,但是他們共有的特性都是左子樹為空。所以只是在處理父節(jié)點(diǎn)的時(shí)候不同而已
場(chǎng)景2:
在這里插入圖片描述
情況2:要?jiǎng)h除的結(jié)點(diǎn)右子樹為空
這種情況其實(shí)與情況1中的左子樹為空一樣,也有兩種場(chǎng)景,與父結(jié)點(diǎn)有關(guān)系,所以我們就直接放在一起了。
在這里插入圖片描述
情況3.要?jiǎng)h除的結(jié)點(diǎn)左右子樹均不為空

在這里插入圖片描述
其實(shí)除了我們上面舉出的例子外,還有刪除父結(jié)點(diǎn)的值的情況,其實(shí)他們的操作都是類似的,都是將要?jiǎng)h除的值換出去,進(jìn)而delete其他的結(jié)點(diǎn)。

非遞歸實(shí)現(xiàn)
bool Erase(const K& key)
{
	//定義一個(gè)cur結(jié)點(diǎn)指針,進(jìn)行查找
	Node* cur = _root;
	//定義一個(gè)結(jié)點(diǎn)保存父結(jié)點(diǎn)的指針
	Node* parent = nullptr;
	//查找我們要?jiǎng)h除的結(jié)點(diǎn)
	while (cur)
	{
		//key大于當(dāng)前結(jié)點(diǎn)的值,根據(jù)性質(zhì)需要到右子樹去尋找
		if (key >cur->_key)
		{
			parent = cur;
			cur = cur->_right;
		}
		//key小于當(dāng)前結(jié)點(diǎn)的值,根據(jù)性質(zhì)需要到左子樹去尋找		
		else if (key< cur->_key)
		{
			parent = cur;
			cur = cur->_left;
		}
		//上面兩種情況都不是,就是已經(jīng)找到了,開始刪除
		else
		{
			//1.左為空,只有右孩子
			if (cur->_left == nullptr)
			{
				//如果是根結(jié)點(diǎn),直接讓_root指向當(dāng)前結(jié)點(diǎn)的右結(jié)點(diǎn)
				if (cur == _root)
				{
					_root = cur->_right;
				}
				//如果不是根結(jié)點(diǎn),需要判斷要?jiǎng)h除的結(jié)點(diǎn)是父節(jié)點(diǎn)的左結(jié)點(diǎn)還是右結(jié)點(diǎn)			
				else
				{
					//是父節(jié)點(diǎn)的左結(jié)點(diǎn),則讓父親的左結(jié)點(diǎn)指向要?jiǎng)h除節(jié)點(diǎn)的右結(jié)點(diǎn)
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					//是父節(jié)點(diǎn)的右結(jié)點(diǎn),則讓父親的右結(jié)點(diǎn)指向要?jiǎng)h除節(jié)點(diǎn)的右結(jié)點(diǎn)
					else
					{
						parent->_right = cur->_right;
					}
				}
				//釋放結(jié)點(diǎn)的空間
				delete cur;
				cur = nullptr;//置空
			}
			//2.右為空,只有左孩子
			else if (cur->_right == nullptr)
			{
				//如果是根結(jié)點(diǎn),直接讓_root指向當(dāng)前結(jié)點(diǎn)的右結(jié)點(diǎn)
				if (cur == _root)
				{
					_root = cur->_left;
				}
				//如果不是根結(jié)點(diǎn),需要判斷要?jiǎng)h除的結(jié)點(diǎn)是父節(jié)點(diǎn)的左結(jié)點(diǎn)還是右結(jié)點(diǎn)							
				else
				{
					//是父節(jié)點(diǎn)的左結(jié)點(diǎn),則讓父親的左結(jié)點(diǎn)指向要?jiǎng)h除節(jié)點(diǎn)的左結(jié)點(diǎn)
					if (parent->_left = cur)
					{
						parent->_left = cur->_left;
					}
					//是父節(jié)點(diǎn)的右結(jié)點(diǎn),則讓父親的右結(jié)點(diǎn)指向要?jiǎng)h除節(jié)點(diǎn)的左結(jié)點(diǎn)
					else
					{
						parent->_right = cur->_left;
					}
				}
				//釋放結(jié)點(diǎn)的空間
				delete cur;
				cur = nullptr;
			}
			//3.有左右孩子,替換法
			//這里我們找右子樹的最小值
			else
			{
				//定義一個(gè)結(jié)點(diǎn)記錄找到的最小值所在的結(jié)點(diǎn)
				Node* min = cur->_right;
				//定義一個(gè)結(jié)點(diǎn)記錄找到的最小值所在的結(jié)點(diǎn)的父節(jié)點(diǎn)
				Node* minParent = cur;
				//循環(huán)查找最小值所在的結(jié)點(diǎn)
				while (min->_left)
				{
					minParent = min;//min結(jié)點(diǎn)發(fā)生變化的時(shí)候父節(jié)點(diǎn)也要變化
					min = min->_left;//min結(jié)點(diǎn)變化
				}
				//循環(huán)結(jié)束表示已經(jīng)找到最小值所在的結(jié)點(diǎn)
				swap(cur->_key, min->_key);//交換數(shù)據(jù)
				//min結(jié)點(diǎn)在父結(jié)點(diǎn)的左邊
				if (minParent->_left == min)
				{
					minParent->_left = min->_right;
				}
				//min結(jié)點(diǎn)在父節(jié)點(diǎn)的右邊
				else
				{
					minParent->_right = min->_right;
				}
				//釋放結(jié)點(diǎn)的空間
				delete min;
				min = nullptr;//置空
			}
			//刪除成功返回true
			return true;
		}
	}
	//刪除失敗返回false
	return false;
}
遞歸實(shí)現(xiàn)

遞歸實(shí)現(xiàn)刪除操作的思路:
1.若樹為空樹(這里所說的樹可能是整棵大樹,也可能是遞歸時(shí)的子樹),則結(jié)點(diǎn)刪除失敗,直接返回false。
2.若所傳入的key值小于當(dāng)前樹的根節(jié)點(diǎn)的值,則問題變?yōu)閯h除左子樹中值為key的結(jié)點(diǎn)。
3.若所傳入的key值大于當(dāng)前樹的根節(jié)點(diǎn)的值,則問題變?yōu)閯h除右子樹中值為key的結(jié)點(diǎn)。
4.若所傳入的key值等于當(dāng)前樹的根節(jié)點(diǎn)的值,則分析該結(jié)點(diǎn)滿足的我們上面分析的哪一種情況,刪除該結(jié)點(diǎn)。

bool _EraseR(Node*& root, const K& key)//注意引用
{
	//若根節(jié)點(diǎn)為空,直接返回false
	if (root == nullptr)
		return false;
	//若傳入的key值大于當(dāng)前結(jié)點(diǎn)的值,則要?jiǎng)h除的結(jié)點(diǎn)在右子樹中
	if (key >root->_key)
		return _EraseR(root->_right, key);
	//若傳入的key值小于當(dāng)前結(jié)點(diǎn)的值,則要?jiǎng)h除的結(jié)點(diǎn)在左子樹中
	else if(key< root->_key)
		return _EraseR(root->_left, key);
	//走到這里這里表示當(dāng)前節(jié)點(diǎn)就為要?jiǎng)h除的的結(jié)點(diǎn)
	else
	{
		//定義一個(gè)節(jié)點(diǎn)保存要?jiǎng)h除的結(jié)點(diǎn)的地址
		Node* del = root;
		//1.左為空,只有右孩子
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		//2.右為空,只有左孩子
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		//3.有左右孩子
		else
		{
			//定義一個(gè)結(jié)點(diǎn)記錄找到的最小值所在的結(jié)點(diǎn)
			Node* min = root->_right;
			//循環(huán)查找最小值,左為空,則當(dāng)前節(jié)點(diǎn)中的值就為最小值
			while (min->_left)
			{
				min = min->_left;
			}
			//交換最小值與要?jiǎng)h除的值
			swap(root->_key, min->_key);
			//替換后轉(zhuǎn)化為刪除右子樹中的節(jié)點(diǎn)(轉(zhuǎn)化為子問題)。
			//因?yàn)槲覀兊牟僮骶褪侨ビ易訕渲胁檎易钚≈?,所以要?jiǎng)h除的結(jié)點(diǎn)一定在右子樹中。
			return _EraseR(root->_right, key);
		}
		//釋放要?jiǎng)h除的結(jié)點(diǎn)的空間
		delete del;
		del = nullptr;//置空
		//刪除成功,返回true
		return true;
	}
}
bool EraseR(const K& key)
{
	//調(diào)用子函數(shù)
	return _EraseR(_root, key);
}

注意: 本函數(shù)中最精華的地方就是傳的是引用參數(shù),因?yàn)橛幸玫拇嬖?,我們就不需要去記錄父?jié)點(diǎn)所在的位置,直接修改root的指向,同時(shí)也就修改了父結(jié)點(diǎn)的指向。

10.查找函數(shù)

查找函數(shù)就非常簡單了,我們只需要根據(jù)二叉搜索樹的性質(zhì)去進(jìn)行查找就可以了。
查找操作的思路:
1.若樹為空樹,則查找失敗,直接返回false
2.若所傳入的key值小于當(dāng)前樹的根節(jié)點(diǎn)的值,則應(yīng)該去左子樹中查找
3.若所傳入的key值大于當(dāng)前樹的根節(jié)點(diǎn)的值,則應(yīng)該去右子樹中查找
4.若所傳入的key值等于當(dāng)前樹的根節(jié)點(diǎn)的值,就找到了,直接返回true

非遞歸實(shí)現(xiàn)
bool Find(const K& key)
{
	//定義一個(gè)cur負(fù)責(zé)查找我們要找的值
	Node* cur = _root;
	//如果cur!=nullptr,就可以一直進(jìn)行查找
	while (cur)
	{
		//如果key的值大于當(dāng)前結(jié)點(diǎn)的值,則要找的值在右子樹中
		if (key >cur->_key)
			cur = cur->_right;
		//如果key的值小于當(dāng)前結(jié)點(diǎn)的值,則要找的值在左子樹中
		else if (key< cur->_key)
			cur = cur->_left;
		//走到這里表示當(dāng)前的值就為我們要找的值
		else
			//直接返回true,表示樹中存在key值
			return true;
	}
	//循環(huán)結(jié)束,表示cur走到了空,即樹中沒有該key值
	return false;
}
遞歸實(shí)現(xiàn)
bool _FindR(Node* root,const K& key)
{
	//如果根節(jié)點(diǎn)為空,則直接返回
	if (root = nullptr)
		return false;
	//如果key的值大于當(dāng)前結(jié)點(diǎn)的值,則要找的值在右子樹中
	if (key >root->_key)
		return _FindR(root->_right, key);
	//如果key的值小于當(dāng)前結(jié)點(diǎn)的值,則要找的值在左子樹中
	else if (key< root->_key)
		return _Find(root->_left, key);
	//到這一步則證明已經(jīng)找到與key值相同的值,返回true
	else
		return true;
}

bool FindR(const K& key)
{
	//調(diào)用子函數(shù)
	return _FindR(_root,key);
}
三、二叉搜索樹的應(yīng)用 K模型

K模型即只有key作為關(guān)鍵碼,結(jié)構(gòu)中只需要存儲(chǔ)Key即可,關(guān)鍵碼即為需要搜索到的值。
比如:給一個(gè)單詞,判斷該單詞是否拼寫正確,具體方式如下:
以詞庫中所有單詞集合中的每個(gè)單詞作為key,構(gòu)建一棵二叉搜索樹
在二叉搜索樹中檢索該單詞是否存在,存在則拼寫正確,不存在則拼寫錯(cuò)誤。

KV模型

每一個(gè)關(guān)鍵碼key,都有與之對(duì)應(yīng)的值Value,即的鍵值對(duì)。
該種方式在現(xiàn)實(shí)生活中非常常見:
比如英漢詞典就是英文與中文的對(duì)應(yīng)關(guān)系,通過英文可以快速找到與其對(duì)應(yīng)的中文,英文單詞與其對(duì)應(yīng)的中文就構(gòu)成一種鍵值對(duì);
再比如統(tǒng)計(jì)單詞次數(shù),統(tǒng)計(jì)成功后,給定單詞就可快速找到其出現(xiàn)的次數(shù),單詞與其出現(xiàn)次數(shù)就是就構(gòu)成一種鍵值對(duì)。

四、二叉搜索樹的性能分析

插入和刪除操作都必須先查找,查找效率代表了二叉搜索樹中各個(gè)操作的性能。
對(duì)有n個(gè)結(jié)點(diǎn)的二叉搜索樹,若每個(gè)元素查找的概率相等,則二叉搜索樹平均查找長度是結(jié)點(diǎn)在二叉搜索樹的深度的函數(shù),即結(jié)點(diǎn)越深,則比較次數(shù)越多。
但對(duì)于同一個(gè)關(guān)鍵碼集合,如果各關(guān)鍵碼插入的次序不同,可能得到不同結(jié)構(gòu)的二叉搜索樹,如下
在這里插入圖片描述
最優(yōu)情況下,二叉搜索樹為完全二叉樹(或者接近完全二叉樹),其平均比較次數(shù)為:logN
最差情況下,二叉搜索樹退化為單枝樹(或者類似單支),其比較次數(shù)最壞能達(dá)到n。
如果退化成單枝樹,二叉搜索樹的性能就失去了。所以我們?cè)诤竺嬉獙?duì)其進(jìn)行改進(jìn),不論按照什么次序插入關(guān)鍵碼,二叉搜索樹的性能都能達(dá)到最優(yōu)。這里就涉及到了我們后面要學(xué)習(xí)的AVL樹和紅黑樹。

總結(jié)

以上就是我們所講的二叉搜索樹的全部內(nèi)容了,其中的重點(diǎn)和難點(diǎn)其實(shí)都是刪除操作,其他的一些操作還是比較容易理解的,后面我們還會(huì)講述更AVL樹、紅黑樹等更高階的樹的實(shí)現(xiàn),大家可以先關(guān)注博主,方便以后查看。如果你覺得我的內(nèi)容對(duì)你有用的話,記得給波三連呦?。?!

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧


名稱欄目:C++之二叉搜索樹詳解-創(chuàng)新互聯(lián)
網(wǎng)站地址:http://weahome.cn/article/ppjdg.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部