二叉搜索樹的插入,刪除,遍歷操作詳解

	今天來詳細介紹下二叉搜索樹的一些操作以及代碼實現;
	參考書籍:《算法導論第三版》(二叉搜索樹的相關章節),《數據結構與算法》


一,二叉搜索樹的定義

一顆二叉搜索樹是以一顆二叉樹來組織的,這樣的一棵樹可以使用一個鏈表數據結構來表示:

struct TreeNode
{
TreeNode* le;   //需要注意,C語言中需要這樣寫:struct TreeNode* le,下同,寫慣了C++的人這裏是一個小坑
TreeNode* rh;
int value;
//有些時候會加上父親節點   TreeNode* pa;
}


	現在我們有了樹的數據結構,可以給出二叉樹的定義了: 設 X是二叉搜索樹中的一個節點。如果Y是 X左子樹中的一個節點,那麼 必定有: Y.value<= X.value。
如果Y是X右子樹中的一個節點,那麼,必定有Y.value>=X.value ;

	這是算法導論上的定義,翻譯一下就是對於任意一個二叉搜索樹中的節點,左子樹中的任意一個節點的值都會小於它的值,右子樹中的任意一個節點的值都會大於它的

	二叉搜索樹本身是自排序的,看定義就明白了,一直往右是最大值,一直往左是最小值,因此二叉搜索樹可以作爲一個優先級隊列,這個以後介紹。
	二叉搜索樹的基本操作所花費的時間和這個樹的高度是成正比的,對於一個有N個節點的二叉搜索樹的各種操作的平均時間是O(logn); 當極度不平衡時,即任意節點沒
有左子樹或者右子樹時,退化爲鏈表;

二,二叉搜索樹的插入操作

	插入操作挺好理解的,就是從根節點開始往下走,如果插入的值大於等於該節點的值就往右子樹走,否則就往左子樹走,這裏我就直接貼代碼了,代碼中有詳細的註釋

#include<iostream>


using namespace std;


struct TreeNode
{
	TreeNode* le;
	TreeNode* rh;
	int value;
};


void addNodeToSearchBT(TreeNode** ptrPtrRoot, int k)
{
	TreeNode* pa = nullptr;  //用於保存插入位置的父節點,因爲下面循環中出來的節點是nullptr
	auto root = *ptrPtrRoot;
	while (root != nullptr)
	{
		//循環遍歷比較,判斷該往左走還是往右走
		pa = root;
		if (k >= root->value)
			root = root->rh;
		else if (k < root->value)
			root = root->le;
	}


	TreeNode* newNode = new TreeNode;
	newNode->le = nullptr;
	newNode->rh = nullptr;
	newNode->value = k;
	
	//這兒就是簡單的判斷大小然後插入就可以了
	if (pa == nullptr)
	{
		*ptrPtrRoot = newNode;   //二叉樹爲空
		return;
	}


	if (k >= pa->value)
		pa->rh = newNode;
	else if (k < pa->value)
		pa->le = newNode;
}


三,二叉搜索樹的刪除操作

	這裏感覺第一次看還是比較複雜的,會詳細的介紹,一般來說都是使用被刪除節點的右子樹中的最小值作爲替換節點,所以下面就不討論使用左子樹的最大值作爲替換
節點的情況了;
	首先第一步當然是找到這個要被刪除的節點,假設這個節點是D, 需要注意的是,我們這裏並沒有使用雙向鏈,所需需要保存D節點的父親節點,因爲,拼接子樹的時候
需要用到父親節點,這裏假設爲Dpa;,被用來替換的節點假設爲X;
	1.如果D是葉子節點,直接刪除就OK,見圖一
	2.如果D有子樹,並且只有右子樹,那也就是可以直接將D的右子樹接到Dpa上就可以,見圖二
	3.如果D有子樹,並且只有左子樹,那麼直接將D的左子樹接到Dpa上就可以,見圖三
	4.如果D有兩個子樹,那麼就找到右子樹中的最小值,也就是從D開始一直往左走,直到走到葉子左子樹爲空的節點,那麼我們就找到可以被用來替換的節點了,見圖四

圖一                              圖二

			             圖三							圖四


接下來只需要把X接到D的位置上,並且把X的右子樹接到它的父節點的左子樹上就可以了;下面把代碼貼上來,代碼中註釋的很詳細

TreeNode* findNodeInSearchBT(TreeNode*root, int k, TreeNode**pa = nullptr)
{
	//這裏是不需要進行null判斷的,因爲後面隱式的判斷了是否爲null
	while (root != nullptr)
	{
		if (k > root->value)
		{
			*pa = root;
			root = root->rh;
		}
		else if (k < root->value)
		{
			*pa = root;
			root = root->le;
		}
		else
			break;  //找到了相應的節點
	}


	//找到了相應的節點就返回該節點,若不存在該節點的父節點則返回nullptr
	return root;
}

bool deleteNodeInSearchBT(TreeNode*root, int k)
{
	TreeNode*PatoDelete = nullptr;     //用於保存D的父節點
	TreeNode* toDelete = findNodeInSearchBT(root, k, &PatoDelete);    //找到D節點

	//需要先判斷一下是否找到了這個節點,沒找到就直接返回false
	if (nullptr == toDelete)
		return false;

	//D節點沒有左子樹,或者左右子樹都沒有  第一第二中情況
	if (toDelete->le == nullptr)
	{
		if (k < PatoDelete->value)
			PatoDelete->le = toDelete->rh;   	//需要把這個父節點的左節點替換爲用來替換的節點
		else
			PatoDelete->rh = toDelete->rh;    //這裏就是右子樹需要被替換了
		delete toDelete;
		return true;
	}

	//D節點沒有右子樹第三種情況
	if (toDelete->rh == nullptr)
	{
		if (k < PatoDelete->value)
			PatoDelete->le = toDelete->le;   	//需要把這個父節點的左節點替換爲用來替換的節點
		else
			PatoDelete->rh = toDelete->le;    //這裏就是右子樹需要被替換了
		delete toDelete;
		return true;
	}

	//D節點左右子樹都有,第四種情況
	auto minInRh = toDelete;
	TreeNode* minInRhpa = nullptr;    //這裏用來保存用來替換的節點的父節點
	while (minInRh->le != nullptr)
	{
		minInRhpa = minInRh;
		minInRh = minInRh->le;
	}   //找到X節點,保存了其父節點

	//其實這裏就是對X做了一次刪除操作,只不過這個X一定是第一種或者第二種情況
	minInRhpa->le = minInRh->rh;  //這裏右子樹是否非空是沒有關係的;直接對X使用第一或者第二中方法刪除,然後用X替換D
	 //需要把用來替換的節點的左右子樹換成被刪除子樹的左右子樹,不然就把樹給弄斷了
	minInRh->le = toDelete->le;
	minInRh->rh = toDelete->rh;

	//一切準備就緒,可以替換了
	if (k < PatoDelete->value)
		PatoDelete->le = minInRh;   	//需要把這個父節點的左節點替換爲用來替換的節點
	else
		PatoDelete->rh = minInRh;    //這裏就是右子樹需要被替換了

//注意不要忘記釋放空間了
	delete toDelete;

	//走到了這裏說明一切都很順利,可以返回true了
	return true;
}

三,遍歷操作

中序遍歷:左子節點--父節點--右子節點

void midTraverse(TreeNode*root)
{
	if (root == nullptr) return;
	midTraverse(root->le);
        cout << root->value;
	midTraverse(root->rh);
	
}




前序遍歷:父節點--左子節點--右子節點

void midTraverse(TreeNode*root)
{
	if (root == nullptr) return;
        cout << root->value;
	midTraverse(root->le);
	midTraverse(root->rh);
	
}

後序遍歷:左子節點--右子節點--父節點

void midTraverse(TreeNode*root)
{
	if (root == nullptr) return;
	midTraverse(root->le);
	midTraverse(root->rh);
        cout << root->value;
}


四,二叉樹的重建---通過三種遍歷方式中的兩種重建二叉樹


待續。。。。。。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章