二叉搜索树的简单实现(Binary Search Tree)


一、二叉搜索树的概念

二叉搜索树,又称二叉排序树,二叉查找树,他能够高效的完成数据的插入,查询,删除操作,是一种高效的数据结构。

如下图就是一个构建好的二叉搜索树。


特点:

        所有的结点,都满足左子树上的所有节点都比自己小,而右子树上的所有节点都比自己大。


二、二叉搜索树的结构

查找:

根据上述特性,我们可以很轻松的写出查找某一个数(X)是否存在的算法。

  • 如果当前结点比X大,就去查找左儿子
  • 如果当前结点比X小,就去查找右儿子
  • 直到找到节点,或者儿子为空。


例如我们查找上图的二叉搜索树中是否含有数字10,

根结点的数值为7 比10小,所以往右走,走到结点的数值为15,比10大,再往左走,找到10。

插入:

如何插入数值(X)呢?

我们可以按照刚才查找数值的方法去试图找这个数值(X)的结点,就可以知道X位置了,之后在那个位置插入新的结点即可。


            如图所示插入元素6。

删除:

最后是删除数值。

例如我们要删除15,如果直接删除15,那么15的子节点就悬空了,我们要找一个子节点来代替15的位置。

一般来说有三种情况。

① 需要删除的结点没有左儿子,那么就把右儿子提上去。

② 需要删除的结点的左儿子没有右儿子,那么就把左儿子提上去。

③ 以上两种条件都不满足,就把左儿子子孙结点的最大结点提到要删除的结点上。


如图所示删除节点 15. (对应了第三种情况)

三、二叉搜索树的复杂度

可以看出,不论哪一种操作,所花的时间都和树的高度成正比,因此如果共有n个元素,平均需要 O(log n)。

注意到,平均两个字,二叉搜索树也是有bug的,当出现树退化的时候,二叉搜索树的效率可能达到上限 O(n),为了防止这个事情的发生就要实现平衡二叉树了,这里先留个坑,日后补上。

四、二叉搜索树的实现

通过上面二叉搜索树原理的介绍,我们就来简单的实现一下这个数据结构增删查的功能吧。

#include <iostream>
#include <cstdio>
using namespace std;
struct node{
	int val; 
	node *lch,*rch;
	node(){
		val = 0; lch = rch = NULL;
	}
};
node* insert(node* p,int x){ //插入操作
	if(p == NULL){
		node* q = new node;
		q->val = x;
		q->lch = q->rch = NULL;
		return q;
	}
	else{
		if(x < p->val)
			p->lch = insert(p->lch,x);
		else 
			p->rch = insert(p->rch,x);
		return p;
	}
}
bool find(node* p,int x){
	if(p == NULL)
		return false;
	else if(x == p->val) return true;
	else if(x < p->val) return find(p->lch,x);
	else return find(p->rch,x);
}
//删除操作
//1.需要删除的结点没有左儿子,把右儿子提上去
//2.需要删除的结点的左儿子没有右儿子,把左儿子提上去
//3.以上两种情况都不满足,把左儿子子孙中最大的结点提到要删除的结点位置。
node* remove(node* p,int x){
	if(p == NULL) return NULL;
	else if(x < p->val) p->lch = remove(p->lch,x);
	else if(x > p->val) p->rch = remove(p->rch,x); //将返回的值 赋值给 右儿子
	else if(p->lch == NULL){  //1.
		node* q = p->rch;
		delete p;
		return q;
	}
	else if(p->lch->rch == NULL){ //2.
		node* q = p->lch;
		q->rch = p->rch;
		delete p;
		return q;
	}
	else {
		node* q;
		for(q = p->lch;q->rch->rch != NULL;q = q->rch);
		node *r = q->rch; //将找到的”左儿子子孙中最大的结点“ 赋值给r
		q->rch = r->lch;  //将最大结点原先的左子节点赋值给 上一级结点右结点
		                  //经过这两步,最大结点已经提出来了
		r->lch = p->lch;  //最大结点取代被删除结点的位置
		r->rch = p->rch;
		delete p;
		return r;
	}
	return p;
}
void print(node* tmp){
	if(tmp == NULL){
		return ;
	}
	else{
		print(tmp->lch); //递归向左子树找到最小的值
		printf("%d\n",tmp->val);  //输出 (中序遍历)
		print(tmp->rch); //再找右子树
	}
}
int main(){
	node* tmp;
	tmp = NULL; //不要忘记清空
	tmp = insert(tmp,7);   //测试数据
	tmp = insert(tmp,2);
	tmp = insert(tmp,15);
	tmp = insert(tmp,10);
	tmp = insert(tmp,11);
	tmp = insert(tmp,17);
	tmp = insert(tmp,16);
	tmp = insert(tmp,19);
	tmp = insert(tmp,8);
	tmp = remove(tmp,15);
	print(tmp);
	int num = 15;
	if(find(tmp,num))
		printf("Find %d !\n",num);
	else printf("Not find!\n");
	num = 16;
	if(find(tmp,num))
		printf("Find %d !\n",num);
	else printf("Not find!\n");
	return 0;
}


另一种实现方法

这里再贴一份网易云课堂上的构造方法,也是课本上比较普遍的构造方法 , 删除的时候与上述代码有所不同,应该更加容易理解

新增加了查找最小值,最大值的函数,方便在删除操作时应用。

#include<iostream>
#include<cstdio>
using namespace std;
typedef int ElementType;
struct node{
	ElementType Data;
	node *Left,*Right;
	node(){
		Left = Right = NULL;
		Data = 0;
	}
};
typedef node* BinTree;
/* BinTree Find(ElementType X,BinTree BST){ //递归方法, 尾递归,效率低
	if(!BST)
		return NULL;
	if(X >BST->Data)
		return Find(X , BST->Right);
	else  if(X < BST->Data)
		return Find(X , BST->Left);
	else //X == BST->Data;
		return BST ; 
} */
BinTree Find(ElementType X, BinTree BST){ //迭代版效率高, 可将尾递归 改为迭代函数
	while(BST){
		if(X >BST->Data)
			BST = BST->Right;
		else if(X <BST->Data)
			BST = BST->Left;
		else 
			return BST;
	}
	return NULL;
}
BinTree FindMin(BinTree BST){ //迭代版本
	while(BST->Left)
		BST = BST->Left;
	return BST;
}
BinTree FindMax(BinTree BST){ //递归版本
	if(BST->Right)
		return FindMax(BST->Right);
	return BST;
}
BinTree Insert(ElementType X,BinTree BST){
	if(!BST){
		BST = new node;
		BST->Data = X;
		BST->Left = BST->Right = NULL;
	}
	else{
		if(X < BST->Data)
			BST->Left = Insert(X , BST->Left);
		else //这样保证了重复节点的插入。即X == BST->Data;
			BST->Right = Insert(X , BST->Right);
	}
	return BST;
}
//删除操作
// 1、要删除的 节点 只有一个儿子 删除当前节点,将这个儿子提到当前位置;
// 2、要删除的节点 没有儿子,直接删除即可;
// 3、要删除的节点既有左儿子,又有右儿子。(这里有两种处理方法)
//      1 。 找到右儿子后代中最小的那个儿子 ,覆盖当前位置, 删除那个儿子。
//	2 。 找到左儿子后代最大的那个儿子,覆盖当前位置, 删除那个儿子。
BinTree Delete(ElementType X , BinTree BST){
	BinTree Tmp;
	if(!BST) printf("要删除的元素未找到\n");
	else if(X < BST->Data)
		BST->Left = Delete(X , BST->Left); //递归的删除左子树
	else if(X > BST->Data)
		BST->Right = Delete(X, BST->Right);
	else//找到要删除的节点  
		if(BST->Left && BST->Right){ //如果既有左儿子又有右儿子
			Tmp = FindMin(BST -> Right); //这里我们找到右儿子中最小的儿子
			BST->Data = Tmp->Data; //赋值给当前节点
			BST->Right = Delete(BST->Data , BST->Right); //递归的删除那个右儿子
		}
		else{ //被删除的节点的子节点只有一个,或者没有
			Tmp = BST;
			if(!BST->Left) //如果左边为空
				BST = BST->Right;    //这里隐式的处理了左右儿子都为空的情况。
			else if(!BST->Right)
				BST = BST->Left;
			delete Tmp; //删除当前节点
		}
	return BST;
}
void Traverse(BinTree BST){ //中序遍历二叉搜索树
	if(!BST)
		return ;
	else{
		Traverse(BST->Left);
		printf("%d\n" , BST->Data); 
		Traverse(BST->Right);
	}	
}
int main(){
	BinTree tree = NULL;
	tree = Insert(5,tree);
	tree = Insert(3,tree);
	tree = Insert(2,tree);
	tree = Insert(5,tree);
	tree = Insert(7,tree);
	tree = Insert(67,tree);
	Traverse(tree);
	cout<<"------------"<<endl;
	tree = Delete(67,tree);
	tree = Delete(9,tree);
	BinTree tmp  = Find(2,tree);
	printf("查找 %d\n",tmp->Data);
	Traverse(tree);
	return 0;
}


posted @ 2015-09-18 19:49  编程菌  阅读(229)  评论(0编辑  收藏  举报