自己动手实现二叉搜素树数据结构

话说二叉搜索树真的是一个非常高效的数据结构,插入和查找数据效率相当高,如果是中序遍历输出元素,那么元素已经自动拍好序了,用来实现优先级队列、集合、映射都是非常不错的选择。

二叉搜索树和双向链表一样,也是由一个个节点组成,要实现搜素二叉树,就要先实现节点类,节点类包括以下的成员:指向当前节点左子节点的指针、指向当前节点右子节点的指针、指向当前节点父节点的指针、当前节点的节点值。先实现这个类:

template <typename T>
class stnode{
public:
	T nodeValue;//节点值
	stnode<T> *left,*right,*parent;//依次是指向当前节点的左子结点、右子节点、父节点的指针
	stnode(const T& item);//构造函数
};
template <typename T>//初始化nodeValue的值为item,三个指针均为NULL
stnode<T>::stnode(const T& item=T()):nodeValue(item){
	left=NULL;
	right=NULL;
	parent=NULL;
}

实现完这个类之后,就需要实现stree类,stree需要向外提供如下的接口:

insert() 插入一个元素

find() 查找一个元素

erase() 删除一个元素

size() 返回树中元素的个数

不管插入还是删除元素,都有保持搜索二叉树的结构特性,即左子节点的值小于父节点的值,父节点的值小于右子节点的值。

类的实现如下:

#include "stnode.h"
template <typename T>
class stree{
public:
	stree(const T& item=T());//构造函数
	int erase(const T& item);//删除一个特定的元素
	int streeSize;//元素的个数
	stnode<T>* find(const T& item);//查找一个特定的节点,返回指向该节点的指针
	void insert(const T& item);//像搜索树中插入一个元素
	int size() const;//返回树中元素的个数
	stnode<T> *root;//根节点
};

template <typename T>//生成一课搜索树,初始化跟节点值为item
stree<T>::stree(const T& item){
	root=new stnode<T>(item);//根节点的三个指针均为NULL;
	streeSize=1;//元素个数为1
}
template <typename T>
void stree<T>::insert(const T& value){
	stnode<T> *newNode;//需要新生成的节点
	/*插入一个元素首先要寻找合适的插入位置,寻找的过程中需要用curr更新当前节点的位置
	用par追踪当前节点的父节点*/
	stnode<T> *curr=root,*par=root;
	int flag=0;//找到插入位置以后,需要用flag来标志需要插入到上一个节点的
	newNode=new stnode<T>(value);
	if(streeSize==1){//表明只有一个跟节点,只需要判断与根节点的大小
		if(value<(root->nodeValue)){
			root->left=newNode;//小于根节点值,插入到左边
		}else if(value>(root->nodeValue)){
			root->right=newNode;//大于根节点值,插入到右边
		}else if(value==(root->nodeValue)){//相等则返回
			return;
		}
		newNode->parent=root;//跟新新插入节点的父节点
	}else{//寻找插入位置,知道找到一个空节点,并用flag标志沿着树下降时,上一步操作是朝左还是朝右
		while(curr!=NULL){
			if(value<(curr->nodeValue)){
				par=curr;
				curr=curr->left;
				flag=0;
			}else if(value>(curr->nodeValue)){
				par=curr;
				curr=curr->right;
				flag=1;
			}else if(value==(curr->nodeValue)){
				return ;
			}
		}
		//flag值为1,表明沿着树下降时,上一步操作是朝右,所以新节点为当前节点的又子节点,否则反之
		if(flag){
			par->right=newNode;
		}else{
			par->left=newNode;
		}
		newNode->parent=par;//更新新插入节点的父节点
	}
	streeSize++;
}
/*删除一个特定值的节点,删除节点比较麻烦,因为删除一个节点需要更新的节点很多,
而且要使二叉搜索树保持自身的结构特性,即左子节点值小于父节点值,父节点值小于右子节点*/
template<typename T>
int stree<T>::erase(const T& item){
	/*依次为需要删除的节点,删除这个节点后替代的节点,需要删除的节点的父节点,替代节点的父节点*/
	stnode<T> *dNode,*rNode,*pOfDnode,*pOfRnode;
	dNode=find(item);//通过find函数找到要删除的值所对应的节点
	if(dNode==NULL)//返回值为空则代表该元素不在搜索树中
		return 0;
	pOfDnode=dNode->parent;
	/*删除一个节点分为四种情况。1、该节点为叶节点,即左子节点和右子节点均为NULL,这种情况下,
	只需要将该节点从树中断开即可。2、该节点没有右子节点,但是有左子节点,这种情况下,该节点的左
	子节点就是需要查找的替代节点。3、该节点没有左子节点,但是有右子节点,那么右子节点就是该节点
	的替代节点,4、该节点既有左子结点,又有右子节点,那么寻找替代节点的过程如下,首先进入该节点
	的右子节点,如果右子节点没有左子节点,那么这个右子节点就是替代节点,如果右子节点存在左子节点
	那么久沿着左子节点一直下降,直到该节点的左子节点为NULL。
	处理完这四种情况,就能顺利完成删除,寻找替代元素的方法并不是唯一的,只要能位置搜索树的结构特
	性就行*/
	if((dNode->left==NULL)&&(dNode->right==NULL)){
		/*第一种情况,判断删除节点是其父节点的左子节点还是右子节点,如果是右子节点就将父节点的右
		子节点置为NULL,另一种情况类似*/
		if((pOfDnode->nodeValue)<(dNode->nodeValue))
			pOfDnode->right=NULL;
		else
			pOfDnode->left=NULL;
		streeSize--;
		return 1;
	}
	/*第二种情况,替代节点就是要删除节点的左子节点*/
	if((dNode->left!=NULL)&&(dNode->right==NULL)){
		rNode=dNode->left;//找到替代节点
		if((pOfDnode->nodeValue)<(dNode->nodeValue)){//要删除的节点是其父节点的右子节点
			pOfDnode->right=rNode;
			rNode->parent=pOfDnode;
		}else{										//要删除的节点是其父节点的左子节点
			pOfDnode->left=rNode;
			rNode->parent=pOfDnode;
		}
		streeSize--;
		return 1;
	}
	/*第三种情况,替代节点是删除节点的右子节点,其他的与第二种情况类似*/
	if((dNode->left==NULL)&&(dNode->right!=NULL)){
		rNode=dNode->right;
		if((pOfDnode->nodeValue)<(dNode->nodeValue)){
			pOfDnode->right=rNode;
			rNode->parent=pOfDnode;
		}else{
			pOfDnode->left=rNode;
			rNode->parent=pOfDnode;
		}
		streeSize--;
		return 1;
	}
	/*第四种情况,左右子节点均不为NULL*/
	if((dNode->left!=NULL)&&(dNode->right!=NULL)){
		rNode=dNode->right;				//先进入删除节点的右子节点
		if(rNode->left==NULL){			//右子节点没有左子节点,那么右子节点就是替代节点
			rNode->left=dNode->left;
			dNode->left->parent=rNode;
			if((pOfDnode->nodeValue)<(dNode->nodeValue)){//判断要删除的节点和其父节点的关系,并更新相应的节点
				pOfDnode->right=rNode;
				rNode->parent=pOfDnode;
			}else{
				pOfDnode->left=rNode;
				rNode->parent=pOfDnode;
			}
		}else{							//右子节点的左子结点不为NULL,那么就沿着左子节点一直下降,直到左子节点为NULL
			while(rNode->left!=NULL){
				rNode=rNode->left;
			}
			/*找到了替代节点,因为替代节点还可能有右子节点,所以追踪替代节点的父节点,将从替代节点断开的这一截重新
			链在替代接节点的父节点上*/
			pOfRnode=rNode->parent;

			rNode->left=dNode->left;
			dNode->left->parent=rNode;
			if((pOfDnode->nodeValue)<(dNode->nodeValue)){//判断删除节点和其父节点的关系,并更新相应的节点
				pOfDnode->right=rNode;
				rNode->parent=pOfDnode;
				pOfRnode->left=rNode->right;
				rNode->right->parent=pOfRnode;
			}else{
				pOfDnode->left=rNode;
				rNode->parent=pOfDnode;
				pOfRnode->left=rNode->right;
				rNode->right->parent=pOfRnode;
			}
			rNode->right=dNode->right;
			dNode->right->parent=rNode;
		}
		streeSize--;
		return 1;
	}
}
/*find函数,寻找一个特定值的元素,并返回该元素的节点*/
template <typename T>
stnode<T>* stree<T>::find(const T& item){
	if(root==NULL)//如果跟为NULL,则代表搜索树为空,不用寻找
		return NULL;
	stnode<T> *temp=root;//从根节点开始,如果item大于节点值,则向右下降,如果item值小于节点值,向左下降
	while(item!=(temp->nodeValue)){//一直循环,直到找到该元素
		if(item<(temp->nodeValue))
			temp=temp->left;		
		else
			temp=temp->right;
		if(temp==NULL)//如果temp为NULL,则代表以及下降到叶节点的左子节点或者右子节点,说明不在树中,跳出循环
			break;			
	}
	return temp;//不管temp是为空或者不为空,均直接返回
	
}
/*返回树中元素的个数*/
template <typename T>
int stree<T>::size()const{
	if(streeSize>0)
		return streeSize;
	return -1;
}

实现完了之后,来测试一下,随机向树中插入100个0-1000范围内的数,按中序遍历输出树中全部元素,理论上来说,输出的元素是按升序排列好的

void main(){
	unsigned int i;	
	vector<int> v;
	stree<int> tree(500);
	for(i=0;i<100;i++){
		tree.insert(rand()%1000);
	}
	output(tree.root,v);
	for(i=0;i<v.size();i++){
		cout<<v[i]<<" ";
	}
	while(1);
}

中序遍历的输出函数
void output(stnode<int>* root,vector<int>& vec){
	if(root!=NULL){
		output(root->left,vec);
		vec.push_back(root->nodeValue);
		output(root->right,vec);
	}
}
将输出的元素全部存入向量之中。再把向量输出:


已经按升序排列了。

你可能感兴趣的:(数据结构,遍历,二叉树,搜索,指针)