二叉搜索树的性质与实现

二叉搜索树是一种特殊的二叉树,对于其中的任意一个节点z,如果其存在左子树,则其左子树存储的关键字值最大不超过z所存储的关键字值;如果其存在右子树,则其右子树存储的关键字值最小不小于z所存储的关键字值。对于相同的关键字集合,可以有不同的二叉搜索数构型。如下图所示都是二叉搜索树:



二叉搜索数拥有许多特殊的性质,下面就以其性质与一些属性为主线,实现一个基本的二叉搜索树。首先,定义一个头文件如下:

#ifndef __BinarySearchTree_H_
#define __BinarySearchTree_H_

#include <iostream>

using namespace std;

template<class Type>
struct tagTreeNode
{
	Type			Data;
	tagTreeNode*	Parent;
	tagTreeNode*	LeftChild;
	tagTreeNode*	RightChild;
	tagTreeNode() : Data(Type()) , Parent(NULL) , LeftChild(NULL) , RightChild(NULL) {}
};

template<class Type>
class BinarySearchTree
{
public:
	typedef	tagTreeNode<Type>		TreeNode;
	typedef tagTreeNode<Type>*		PTreeNode;

public:
	BinarySearchTree();
	~BinarySearchTree();

	void			AddNode(Type data);
	bool			Delete(Type data);
	bool			Delete(PTreeNode pNode);
	PTreeNode		Find(Type data);
	bool			HasNode(Type data);

	void			ResetTree();

	void			PreOrderTreeWalk();
	void			InOrderTreeWalk();
	void			PostOrderTreeWalk();

	PTreeNode		TreeMaxmum();
	PTreeNode		TreeMinmum();
	PTreeNode		TreeMaxmum(PTreeNode pNode);
	PTreeNode		TreeMinmum(PTreeNode pNode);
	PTreeNode		TreePredecessor(PTreeNode pNode);	//前驱
	PTreeNode		TreeSuccessor(PTreeNode pNode);	//后继

private:
	void			Destory();
	void			_Destory(PTreeNode pNode);
	void			_PreOrderTreeWalk(PTreeNode pNode);
	void			_InOrderTreeWalk(PTreeNode pNode);
	void			_PostOrderTreeWalk(PTreeNode pNode);
	void			TransPlant(PTreeNode src , PTreeNode dst);

private:
	PTreeNode	m_Root;
};
#endif __BinarySearchTree_H_

该头文件使用了简单的模板,先定义了一个结构体tagTreeNode来表示树的节点,其有四个数据,一个存储关键字,其余三个指向本结构体指针分别表示父结点、左孩子结点、右孩子结点。如果指向不存在则为NULL(VS2008没有C++11中的空指针)。定义了一个类BinarySearchTree来表示二叉搜索树,该类只定义了一个指向根结点的结构体指针。


1、插入与查找:设x是二叉搜索树中的一个结点,如果y是x左子树中的一个结点,那么y.data ≤ x.data 。如果y是x右子树中的一个 结点,那么 y.data ≥ x.data。

根据这个性质,我们可以构造一个二叉搜索树,定义向二叉搜索树中增加结点如下:

template<class Type>
void BinarySearchTree<Type>::AddNode(Type data)
{
	cout<<"Add node "<<data<<endl;
	PTreeNode pNewNode = new TreeNode;
	pNewNode->Data = data;

	PTreeNode pParent = NULL;
	PTreeNode pNode = m_Root;
	while(pNode)
	{
		pParent = pNode;
		if(pNode->Data > data)
			pNode = pNode->LeftChild;
		else
			pNode = pNode->RightChild;
	}

	if(pParent == NULL)
	{
		pNewNode->Parent = NULL;
		m_Root = pNewNode;
	}
	else
	{
		pNewNode->Parent = pParent;
		if(pParent->Data > data)
			pParent->LeftChild = pNewNode;
		else
			pParent->RightChild = pNewNode;
	}
}

我们先把要增加的结点生成出来,再放到适当的位置。第一个while循环查找位置:如果当前结点的值比要插入结点的值大则继续在其左子树中查找,否则在右子树中查找,直到符合该性质的空尾,保留可以插入的位置的父结点以便插入。


同理,根据这个性质,我们可以很容易的在一棵二叉搜索树中查找一个值、一个结点是否存在或实现定位。

template<class Type>
tagTreeNode<Type>* BinarySearchTree<Type>::Find(Type data)
{
	PTreeNode pNode = m_Root;
	while(pNode && pNode->Data != data)
	{
		if(pNode->Data > data)
			pNode = pNode->LeftChild;
		else
			pNode = pNode->RightChild;
	}

	return pNode;
}

template<class Type>
bool BinarySearchTree<Type>::HasNode(Type data)
{
	PTreeNode pNode = Find(data);
	if(pNode != NULL)
	{
		cout<<"Has Node "<<data<<endl;
		return true;
	}
	else
	{
		cout<<"Do Not Have Node "<<data<<endl;
		return false;
	}
}
当然,可以更简单的实现递归版的查找功能,但是循环版的在性能上来说更好一点。

2、遍历:如果x是一颗有n个结点子树的根,那么中序遍历该树需要Θ(n)时间。

我们这里不去证明该定理,而是去实现中序、先序、后序遍历。谈到二叉树,就必须了解它的三种遍历方式。所谓中序遍历即:先遍历某个结点,再去遍历它的左子树的结点,然后去遍历它的右子树的结点。其实这三种遍历区别在于哪个时候访问当前结点的值。

由于这种遍历的方式,让人很自然就想到用递归去遍历,这里也是这样实现的:

template<class Type>
void BinarySearchTree<Type>::InOrderTreeWalk()
{
	_InOrderTreeWalk(m_Root);
	cout<<endl;
}

template<class Type>
void BinarySearchTree<Type>::_InOrderTreeWalk(PTreeNode pNode)
{
	if(pNode == NULL)
		return;
	_InOrderTreeWalk(pNode->LeftChild);
	cout<<pNode->Data<<"  ";
	_InOrderTreeWalk(pNode->RightChild);
}

第一个函数只是传递树根调用第二个函数。第二个函数是中序遍历的递归实现:如果当前结点为空,则立即返回,这是跳出递归的必须条件。先遍历左子树,如果其左子树不为空,则又会用同样的方法递归遍历其左子树,如果做子树为空,则会遇到跳出条件理解返回。访问当前结点的值,再遍历其右子树。同理,先序遍历和有序遍历基本相同:

template<class Type>
void BinarySearchTree<Type>::PreOrderTreeWalk()
{
	_PreOrderTreeWalk(m_Root);
	cout<<endl;
}

template<class Type>
void BinarySearchTree<Type>::PostOrderTreeWalk()
{
	_PostOrderTreeWalk(m_Root);
	cout<<endl;
}

template<class Type>
void BinarySearchTree<Type>::_PreOrderTreeWalk(PTreeNode pNode)
{
	if(pNode == NULL)
		return;
	cout<<pNode->Data<<"  ";
	_PreOrderTreeWalk(pNode->LeftChild);
	_PreOrderTreeWalk(pNode->RightChild);
}

template<class Type>
void BinarySearchTree<Type>::_PostOrderTreeWalk(PTreeNode pNode)
{
	if(pNode == NULL)
		return;
	_PostOrderTreeWalk(pNode->LeftChild);
	_PostOrderTreeWalk(pNode->RightChild);
	cout<<pNode->Data<<"  ";
}

3、搜索:对于一棵高度为h的二叉搜索树,要查找最大/最小关键值元素总能再O(h)时间内完成。

所谓最大关键值元素即在某一棵不为空的二叉搜索棵树中,其存储的值不比树中其他存储的值小。我们可以根据数的特性,一直搜索一个数的右子树来实现,即如果以z为根的二叉搜索数,如果其存在右子树,则最大关键值元素一定在其右子树中。同理,如果该树存在左子树,则其最小关键值元素一点存在其左子树中。如果不存在子树,则为其本身。

template<class Type>
tagTreeNode<Type>* BinarySearchTree<Type>::TreeMaxmum()
{
	return TreeMaxmum(m_Root);
}

template<class Type>
tagTreeNode<Type>* BinarySearchTree<Type>::TreeMinmum()
{
	return TreeMinmum(m_Root);
}

template<class Type>
tagTreeNode<Type>* BinarySearchTree<Type>::TreeMaxmum(PTreeNode pNode)
{
	while(pNode && pNode->RightChild)
		pNode = pNode->RightChild;

	return pNode;
}

template<class Type>
tagTreeNode<Type>* BinarySearchTree<Type>::TreeMinmum(PTreeNode pNode)
{
	while(pNode && pNode->LeftChild)
		pNode = pNode->LeftChild;

	return pNode;
}


4、前驱和后继:

要查找前驱,则根据二叉搜索树的性质,可以分为两种情形:

a、如果结点x的左子树非空,则其前驱为其左子树的最大关键值结点。

b、如果结点x的左子树为空,则其前驱为沿该结点向上,第一个不为左孩子的结点的父结点。当然,这种情形前驱可能不存在。

template<class Type>
tagTreeNode<Type>* BinarySearchTree<Type>::TreePredecessor(PTreeNode pNode)
{
	if(pNode->LeftChild)
		return TreeMaxmum(pNode->LeftChild);

	PTreeNode pParent = pNode->Parent;
	while(pParent && pNode == pParent->LeftChild)
	{
		pNode = pParent;
		pParent = pNode->Parent;
	}

	return pParent;
}
如上面的第一幅图,值为2的结点一直往上查找第一个不为左孩子的结点,但是不存在,所有其前驱不存在。值为8的结点向上查找这样的结点,找到了值为7的结点,所有值为7的结点即为其前驱。第二棵二叉搜索树中, 深度为5的值为5的结点一直向上查找,找到7为第一个不为左孩子的结点,且其父结点存在,则其父结点(深度为2的值为5的结点)即为要找的前驱。


要查找后继,我们有如下策略:

a、如果结点存在右孩子,则其后继为其右孩子为根节点的子树的最小关键字结点。

b、如果结点不存在有孩子,则其后继为沿父结点往上查找,第一个不为右孩子的结点的父结点。也可能不存在。

template<class Type>
tagTreeNode<Type>* BinarySearchTree<Type>::TreeSuccessor(PTreeNode pNode)
{
	if(pNode->RightChild)
		return TreeMinmum(pNode->RightChild);

	PTreeNode pParent = pNode->Parent;
	while(pParent && pNode == pParent->RightChild)
	{
		pNode = pParent;
		pParent = pNode->Parent;
	}

	return pParent;
}


5、删除结点

删除结点比较复杂,可以分以下情形:

a、如果z没有左孩子(包括有右孩子和没有孩子结点的情形),那么用其右孩子来替换z,这个右孩子可以是NULL,也可以不是。

b、如果z仅有一个孩子且为其左孩子,则用其左孩子来替换z。

c、如果z同时存在两个孩子,则需要查找其后继y,做如下操作:

    1) 如果y是z的右孩子,则用y替换z,仅保留y的右孩子。

    2) 用y的右孩子替换y,然后用y替换z。


如上图为各种删除的情形,其中实现为父子关系,虚线表示中间可能间隔多个结点相连。程序实现如下

template<class Type>
bool BinarySearchTree<Type>::Delete(PTreeNode pNode)
{
	if(! pNode)
		return false;
	if(NULL == pNode->LeftChild)	// two case:1.left and right child both are NULL. 2.left child is NULL,and right child is not NULL
	{
		TransPlant(pNode , pNode->RightChild);	//make his right child or NULL to replace his position
		delete pNode;
		return true;
	}
	else if(NULL == pNode->RightChild)	//left child is not NULL and right child is NULL
	{
		TransPlant(pNode , pNode->LeftChild);	//make his left child to replace his position
		delete pNode;
		return true;
	}
	else	//both children are not NULL
	{
		PTreeNode y = TreeMinmum(pNode->RightChild);
		if(y->Parent != pNode)
		{
			TransPlant(y , y->RightChild);
			y->RightChild = pNode->RightChild;
			y->RightChild->Parent = y;
		}
		TransPlant(pNode , y);
		y->LeftChild = pNode->LeftChild;
		y->LeftChild->Parent = y;
		delete pNode;
		return true;
	}
}

template<class Type>
void BinarySearchTree<Type>::TransPlant(PTreeNode src , PTreeNode dst)
{
	if(! src->Parent)
		m_Root = dst;
	else if(src == src->Parent->LeftChild)
		src->Parent->LeftChild = dst;
	else
		src->Parent->RightChild = dst;
	dst->Parent = src->Parent;
}
这里专门写了个函数TransPlant来处理用dst结点替换src结点的操作。


其他一些函数实现:

template<class Type>
BinarySearchTree<Type>::BinarySearchTree():
m_Root(NULL)
{
	
}

template<class Type>
BinarySearchTree<Type>::~BinarySearchTree()
{
	Destory();
}

template<class Type>
void BinarySearchTree<Type>::ResetTree()
{
	Destory();
	m_Root = NULL;
}

template<class Type>
void BinarySearchTree<Type>::Destory()
{
	_Destory(m_Root);
}

template<class Type>
void BinarySearchTree<Type>::_Destory(PTreeNode pNode)
{
	if(pNode == NULL)
		return;
	if(pNode->LeftChild)
		_Destory(pNode->LeftChild);
	if(pNode->RightChild)
		_Destory(pNode->RightChild);

	cout<<"delete node "<<pNode->Data<<endl;
	delete pNode;
}










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值