二叉搜索树的插入,删除,遍历操作详解

	今天来详细介绍下二叉搜索树的一些操作以及代码实现;
	参考书籍:《算法导论第三版》(二叉搜索树的相关章节),《数据结构与算法》


一,二叉搜索树的定义

一颗二叉搜索树是以一颗二叉树来组织的,这样的一棵树可以使用一个链表数据结构来表示:

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;
}


四,二叉树的重建---通过三种遍历方式中的两种重建二叉树


待续。。。。。。




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