算法导论 第12章 二叉查找树

二叉查找树是一种树数据结构,它与普通的二叉树最大的不同就是二叉查找树满足一个性质:对于树中的任意一个节点,均有其左子树中的所有节点的关键字值都不大于该节点的关键字值,其右子树中的任意一个节点的关键字值都不小于该节点的关键字值。


在二叉查找树上可以进行搜索、取最小值、取最大值、取指定节点的前驱、取指定节点的后继以及插入和删除节点操作,因此二叉查找树和堆(大顶堆和小顶堆)一样,也可以做优先队列,都能够在 O(lgn) 的时间内取得集合的最大值和最小值。一个二叉查找树的期望高度为O(lgn),因此在二叉查找树上的基本操作都能在期望时间O(lgn)下实现,但是最坏情况下时间为O(n),也就是二叉排序树极度不平衡,变成一条链。对二叉查找树的前序、中序和后序遍历均是在 O(n) 的时间复杂度内实现。根据二叉查找树的性质,其中序遍历的结果就是树元素的按非递减序输出,因此研究二叉排序树的中序遍历及遍历中元素的前驱和后继是非常重要的。


使用二叉查找树也有一个问题,随着插入和删除操作的不断执行,树的高度就会发生变化,不一定为O(lgn),这也是它的一个缺陷,因此在做优先队列时,虽然堆(看做完全二叉树)和查找树都是利用二叉树的结构来实现的,但是用堆较查找树多。


下面的代码是包括了二叉树的前序、中序和后序遍历的递归和非递归算法,以及二叉查找树的相关操作。

/*
 *	算法导论 第十二章 二叉查找树
 */

#include <iostream>
#include <stack>
using namespace std;

//二叉树节点定义
typedef struct TNode 
{
	int key;
	TNode *parent, *left, *right;
} TNode, Tree;

/*
 *	前序遍历
 *	递归
 */
void preOrderTreeWalkRecursion(Tree* tree)
{
	if (tree)
	{
		cout<<tree->key<<" ";
		preOrderTreeWalkRecursion(tree->left);
		preOrderTreeWalkRecursion(tree->right);
	}
}

/*
 *	前序遍历二叉树
 *	非递归
 */
void preOrderTreeWalkNoRecursion(Tree* tree)
{
	if (! tree)
	{
		return;
	}

	stack<TNode*> treeStack;
	treeStack.push(tree);
	while (! treeStack.empty())
	{
		tree = treeStack.top();
		while (tree)
		{
			cout<<tree->key<<" ";
			tree = tree->left;
			treeStack.push(tree);
		}
		treeStack.pop();
		if (! treeStack.empty())
		{
			tree = treeStack.top();
			treeStack.pop();
			treeStack.push(tree->right);
		}
	}
}

/*
 *	中序遍历
 *	递归
 */
void inOrderTreeWalkRecursion(Tree* tree)
{
	if (tree)
	{
		inOrderTreeWalkRecursion(tree->left);
		cout<<tree->key<<" ";
		inOrderTreeWalkRecursion(tree->right);
	}
}

/*
 *	中序遍历
 *	非递归 用栈
 */
void inOrderTreeWalkNoRecursionStack(Tree* tree)
{
	if (! tree)
		return;

	stack<TNode*> treeStack;
	treeStack.push(tree);
	while (! treeStack.empty())
	{
		tree = treeStack.top();
		while (tree)
		{
			tree = tree->left;
			treeStack.push(tree);
		}

		treeStack.pop();
		if (! treeStack.empty())
		{
			tree = treeStack.top();
			treeStack.pop();
			cout<<tree->key<<" ";
			treeStack.push(tree->right);
		}

	}
}

/*
 *	中序遍历
 *	非递归 无栈
 */
void inOrderTreeWalkNoRecursionNoStack(Tree* tree)
{
	if (! tree)
		return;

	while (tree)
	{
		TNode* pLeft = tree->left;
		if (pLeft)
		{
			while (pLeft->right && pLeft->right != tree)
			{
				pLeft = pLeft->right;
			}

			if (! pLeft->right)
			{//向下搜索建立遍历顺序的链接关系
				pLeft->right = tree;
				tree = tree->left;
				continue;
			} else {//向上回溯遍历解除链接关系
				pLeft->right = NULL;
			}
		}
		//在向下阶段直到没有左孩子,在向上回溯阶段表示左子树已经遍历完
		//此时应该遍历此节点,然后遍历其右孩子
		cout<<tree->key<<" ";
		tree = tree->right;
	}
}

/*
 *	后序遍历
 *	递归
 */
void postOrderTreeWalkRecursion(Tree* tree)
{
	if (tree)
	{
		postOrderTreeWalkRecursion(tree->left);
		postOrderTreeWalkRecursion(tree->right);
		cout<<tree->key<<" ";
	}
}

/*
 *	后序遍历
 *	非递归
 */
void postOrderTreeWalkNoRecursion(Tree* tree)
{
	if (! tree)
		return;

	stack<TNode*> treeStack;
	treeStack.push(tree);
	TNode* tempNode = NULL;//保存刚刚访问过的节点
	while (! treeStack.empty())
	{
		tree = treeStack.top();
		while (tree)
		{
			tree = tree->left;
			treeStack.push(tree);
		}

		treeStack.pop();

		if (! treeStack.empty())
		{
			tree = treeStack.top();

			//如果右孩子为空,或者右孩子为刚刚访问过的节点则
			//说明此节点的所有左右孩子都访问过了,可以访问此节点了
			if (! tree->right || tree->right == tempNode)
			{
				cout<<tree->key<<" ";
				tempNode = tree;
				treeStack.pop();
				//子树tree已经全部访问完,为了避免重复访问tree这颗子树,需要压入一个空指针
				//以便下次直接访问tree的父节点
				treeStack.push(NULL);
			} else {
				//否则访问右孩子
				treeStack.push(tree->right);
			}
		}

	}
}

/*
 *	二叉排序树的查找
 *	递归
 *	时间复杂度为树的高度O(lgn)
 */
TNode* treeSearch(Tree* tree, int k)
{
	if (! tree || tree->key == k)
	{
		return tree;
	}

	if (k < tree->key)
	{
		treeSearch(tree->left, k);
	} else {
		treeSearch(tree->right, k);
	}
}

/*
 *	二叉排序树查找
 *	非递归
 *	时间复杂度为O(lgn)
 */
TNode* treeSearchIterative(Tree* tree, int k)
{
	while (tree && tree->key != k)
	{
		if (k < tree->key)
		{
			tree = tree->left;
		} else {
			tree = tree->right;
		}
	}

	return tree;
}

/*
 *	查找二叉排序树中的最小元素
 *	即为最左元素
 *	时间复杂度为O(lgn)
 */
TNode* treeMinimum(Tree* tree)
{
	while (tree->left)
	{
		tree = tree->left;
	}

	return tree;
}

/*
 *	查找二叉查找树中的最大元素
 *	即为最右元素
 *	时间复杂度为O(lgn)
 */
TNode* treeMaximum(Tree* tree)
{
	while (tree->right)
	{
		tree = tree->right;
	}

	return tree;
}

/*
 *	求二叉排序树中指定节点node的中序遍历后继
 *	如果node右子树不为空,则后继则为node右子树中的最小节点
 *	否则node右子树为空,必须向上回溯找到第一个节点:该节点为其父节点的左孩子
 *	后继即为其父节点,如果不存在这样的节点,说明node为最右节点,后继为空
 *	时间复杂度为O(lgn)
 */
TNode* treeSuccessor(TNode* node)
{
	if (! node)
		return NULL;

	if (node->right)
	{
		return treeMinimum(node->right);
	}

	TNode* temp = node->parent;
	while (temp && node == temp->right)
	{
		node = temp;
		temp = node->parent;
	}
	return temp;
}

/*
 *	求二叉排序树中指定节点node的中序遍历前驱
 *	如果node左子树不为空,则前驱则为node左子树中的最大节点
 *	否则node左子树为空,必须向上回溯找到第一个节点:该节点为其父节点的右孩子
 *	前驱即为其父节点,如果不存在这样的节点,说明node为最左节点,前驱为空
 *	时间复杂度为O(lgn)
 */
TNode* treePredecessor(TNode* node)
{
	if (! node)
		return NULL;

	if (node->left)
	{
		return treeMaximum(node->left);
	}

	TNode* temp = node->parent;
	while (temp && node == temp->left)
	{
		node = temp;
		temp = node->parent;
	}
	return temp;
}

/*
 *	二叉查找树插入元素
 *	新元素只能插入到二叉树的叶子节点上
 *	首先需要找到这个插入点
 *	时间复杂度主要在搜索插入位置上,为O(lgn)
 */
void treeInsert(Tree* &tree, TNode* node)
{
	if (! node)
	{
		return;
	}

	TNode* pos = NULL;
	TNode* temp = tree;
	while (temp)
	{
		pos = temp;
		if (node->key < temp->key)
		{
			temp = temp->left;
		} else {
			temp = temp->right;
		}
	}
	
	node->parent = pos;

	if (! pos) 
	{
		tree = node;
	} else if (node->key < pos->key) {
		pos->left = node;
	} else {
		pos->right = node;
	}
}

/*
 *	二叉查找树删除元素
 *	删除二叉查找树中的一个元素必须继续保持二叉查找树的性质,也就是保证中序遍历的结果不变
 *	根据被删除节点分三种情况
 *	1、删除节点没有孩子,这种情况下直接删除该节点即可
 *	2、删除节点有一个孩子,这种情况先将节点删除,再将该节点的孩子填补上来即可
 *	3、删除节点有两个孩子,这种情况相对麻烦一点,需要先找到删除节点的后继节点
 *		 然后将该删除节点的数据值修改为后继结点的值,然后将后继节点删除,由于后继节点不可能有左孩子,否则删除节点
 *		 的后继就应该是后继节点的左孩子,所以后继节点的删除很简单
 *	时间主要用在查找删除节点的后继节点上,所以需要O(lgn)
 */
TNode* treeDelete(Tree* &tree, TNode* node)
{
	if (! tree || ! node)
		return NULL;

	//找到要删除的节点
	TNode* delNode = NULL;//要删除的节点
	if (! (node->left && node->right))
	{
		delNode = node;
	} else {
		delNode = treeSuccessor(node);
	}
	//找到替补节点
	TNode* fillNode = NULL;//填补(到被删除的位置)节点
	if (delNode->left)
	{
		fillNode = delNode->left;
	} else {
		fillNode = delNode->right;
	}

	//将替补节点接到删除节点的位置
	//可能delNode没有孩子
	if (fillNode)
	{
		fillNode->parent = delNode->parent;
	}
	//delNode可能是根节点
	if (! delNode->parent)
	{
		tree = fillNode;
	} else if (delNode == delNode->parent->left) {
		delNode->parent->left = fillNode;
	} else {
		delNode->parent->right = fillNode;
	}

	//如果实际删除的是node的后继节点,则复制数据
	if (delNode != node)
	{
		node->key = delNode->key;
	}

	return delNode;
}


int main()
{
	int arr[] = {15, 5, 16, 3, 12, 20, 10, 13, 18, 23, 6, 7};
	int len = sizeof(arr) / sizeof(arr[0]);

	Tree* tree = NULL;
	for (int i=0; i<len; i++)
	{
		TNode* node = new TNode();
		node->key = arr[i];
		node->left = node->parent = node->right = NULL;
		treeInsert(tree, node);
	}

	cout<<"前序递归遍历二叉排序树:"<<endl;
	preOrderTreeWalkRecursion(tree);

	cout<<endl<<"前序非递归遍历二叉排序树:"<<endl;
	preOrderTreeWalkNoRecursion(tree);

	cout<<endl<<"中序递归遍历二叉排序树:"<<endl;
	inOrderTreeWalkRecursion(tree);

	cout<<endl<<"中序非递归用栈遍历二叉排序树:"<<endl;
	inOrderTreeWalkNoRecursionStack(tree);

	cout<<endl<<"中序无栈非递归遍历二叉排序树:"<<endl;
	inOrderTreeWalkNoRecursionNoStack(tree);

	cout<<endl<<"后序递归遍历二叉排序树:"<<endl;
	postOrderTreeWalkRecursion(tree);

	cout<<endl<<"后序非递归遍历二叉排序树:"<<endl;
	postOrderTreeWalkNoRecursion(tree);
	cout<<endl;

	cout<<"最大元素为:"<<treeMaximum(tree)->key<<endl;
	cout<<"最小元素为:"<<treeMinimum(tree)->key<<endl;

	int key = 7;
	TNode* pNode = treeSearch(tree, key);
	cout<<key<<"的中序前驱是:"<<(treePredecessor(pNode))->key
		<<",中序后继是:"<<(treeSuccessor(pNode))->key<<endl;

	key = 19;
	pNode = new TNode();
	pNode->key = key;
	pNode->left = pNode->parent = pNode->right = NULL;
	treeInsert(tree, pNode);
	cout<<"插入节点"<<key<<"以后,中序遍历结果为:"<<endl;
	inOrderTreeWalkNoRecursionNoStack(tree);
	cout<<endl;

	pNode = treeSearch(tree, key);
	pNode = treeDelete(tree, pNode);
	if (pNode)
	{
		delete pNode;
		pNode = NULL;
	}
	cout<<"删除节点"<<key<<"以后,中序遍历结果为:"<<endl;
	inOrderTreeWalkNoRecursionNoStack(tree);
	cout<<endl;

	return 0;
}


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值