二叉排序树基本功能实现(C++)


二叉排序树(Binary Sort Tree )也称二叉搜索树(Binary Search Tree),以下简称BST

  • 它的特点是左小右大(左子树小于根,右子树大于根),令人困惑的是他不允许相等存在,一定要分个高低。这个特点与二叉堆排序有所不同,堆是允许存在相同关键字的,所以堆可用于任意排序;而BST建立后必定是一个无重复关键字的树,对其中序遍历,必为升序,这个过程等价于将一个链表去重后再排序(或是排序后去重)
  • 顾名思义,这个结构可以用来排序,也可用来搜索(查找)
  • 他是特殊的二叉树,意味着它可以用二叉树的遍历方法,但是根据其定义可知,与普通二叉树建立方式有所不同

看看效果图
在这里插入图片描述

数据结构

typedef struct BSTNode{
	int key;//关键字
	struct BSTNode *lchild,*rchild;//左右子树
}BSTNode,*BSTree;

BST的构建

到了反常识的时刻了,通常我们的认知是某个东西先存在,然后才可以对其查、改、删。这是我们 习以为常的思考模式,然而实现该思想却需要倒过来:先查重,再确定是否插入,反复这两个过程,从而建立BST

查找(递归)

查找可谓BST构建中最为核心的一步,其它操作均建立在此基础上,查找思想与普通二叉树基本一致,不过是添加了自身特有的判断,为了能为插入,删除服务,设计时添加了f,p,fd三个参数

  • f为T的双亲,为记录插入位置准备(f初始为空)
  • 查找失败:p保存即将被插入的元素的双亲(为插入服务)
  • 查找成功:p记录关键字为e的节点位置;fd记录关键字为e的节点的双亲位置(为删除服务)
//二叉排序树关键字左小右大&&无重复关键字
//所以通过插入建立二叉查找树时必须先查重,所以查找就尤为重要
//为了查找直接为插入服务,所以查找过程需要记录插入的位置,
//参数设计:
//1,返回值true表示找到;false表示未找到
//2,T为当前需查找的BST;e为需查找的关键字;
//	f为T的双亲,为记录插入位置准备(f初始为空);查找失败:p记录即将被插入的元素的双亲;查找成功:p记录关键字为e的节点位置
//递归设计:
//状态分解(4中状态):
//T空;T非空:T->key =/</> e
//《结束条件:》 
//1,T空说明查找失败,返回false,保存即将要插入元素的双亲位置 
//2,T非空且T->key=e,说明查找成功,返回true,并保存当前位置
//3,T非空且T->key<e,在T的右子树继续查找
//4,T非空且T->key>e,在T的左子树继续查找
bool SearchBST(BSTree T,int e,BSTNode* f,BSTNode* &p,BSTNode* &fd)
{	if(T == NULL){//结束状态1:查找失败 
		p = f;//记录即将被插入节点的双亲 
		return false;
	}
	else{
		if(T->key == e){//结束状态2:查找成功 
			fd = f;
			p = T;//可获取目标节点 
			return true;
		}
		else if(T->key < e) return SearchBST(T->rchild,e,T,p,fd);//在右子树继续查找 
		else return SearchBST(T->lchild,e,T,p,fd);//在左子树继续查找 
	}
}

插入

借用查找利器,取得被插入点的双亲位置,判断下插入的左右即可

//插入:利用查找函数 
void InsertBST(BSTree &T,int e)
{
	BSTNode *f=NULL,*p=NULL,*pcur,*fd=NULL;
	if(!SearchBST(T,e,f,p,fd)){
		//将e置入节点 
		pcur = (BSTNode*)malloc(sizeof(BSTNode));
		pcur->key = e;
		pcur->lchild = pcur->rchild = NULL;
		
		if(p == NULL)T = pcur;//根节点为空 
		else{
			if(p->key < e)p->rchild = pcur;//判断插入位置 
			else p->lchild = pcur;
		} 
	} 
}

建立二叉排序树

如你所见,基础打好,建立仅需反复调用查找与插入即可
文件二叉排序树.txt内容

45 24 53 45 12 24 90 12 2 100 23 32 14 430 0 9 8

//从文件读取数据并创建二叉排序树
//反复调用插入 
void CreateBST(BSTree &T)
{
	fstream inFile("二叉排序树.txt",ios::in);
	if(!inFile)cout<<"二叉排序树.txt 打开失败!"<<endl;
	int t;
	while(true)
	{
		inFile>>t;
		if(!inFile)break;//cout<<t<<endl; 
		InsertBST(T,t);
	}
	inFile.close();
} 

如何删除?

在普通二叉树中删除一个节点时没有意义的,因为破坏了树的结构,成为森林。而BST是一个序列,删除一个元素还是一个序列,只要通过调整依旧是BST,所以删除的关键在于如何调整,像是堆排序关键在于筛选,也是调整的一种。根据删除点的特征,可分为三类:

删除节点:分三种情况(假设删除点p)

  • 1,p为叶子,直接删除
  • 2,p为单枝,仅有左/右子树,单枝上移即可
  • 3,p为双枝,左右子树均有,令p的左子树的最右节点s替代p,同时删除s,由于s是左子树最右节点,所以不可能为双枝,于是又回到了情况1,2
    tips:根需特殊处理,因为其无双亲
//删除节点:分三种情况(假设删除点p) 
//1,p为叶子,直接删除
//2,p为单枝,仅有左/右子树,单枝上移即可
//3,p为双枝,左右子树均有,令p的左子树的最右节点s替代p,
//	同时删除s,由于s是左子树最右节点,所以不可能为双枝,于是又回到了情况1,2 
//根需特殊处理,因为其无双亲 
void DeleteBST(BSTree &T,int e)
{
	BSTNode *f=NULL,*fd=NULL,*p=NULL;
	if(SearchBST(T,e,f,p,fd)){//查找成功 
		if(p->lchild == NULL && p->rchild == NULL){//叶子 
			if(fd == NULL)T = NULL;//根处理 
			else{
				if(fd->key < p->key)fd->rchild = NULL;//判断左右 
				else fd->lchild = NULL;
			}
			free(p); 
		}
		else if(p->lchild == NULL && p->rchild != NULL){//右单枝 
			if(fd == NULL)T = p->rchild;//根处理 
			else if(fd->key < p->key)fd->rchild = p->rchild;//判左右 
			else fd->lchild = p->rchild;
			free(p);
		}
		else if(p->lchild != NULL && p->rchild == NULL){//左单枝 
			if(fd == NULL)T = p->lchild;
			else if(fd->key < p->key)fd->rchild = p->lchild;
			else fd->lchild = p->lchild;
			free(p);
		}
		else{//双枝 
			BSTNode *fs,*s;//s为p的左子树最右节点,fs为s的双亲 ;寻找s:向左移一个节点,在向右走到头 
			fs = p;
			s = p->lchild;// 左移一个节点 
			while(s->rchild != NULL){//向右走到尽头 
				fs = s;
				s = s->rchild;
			}
			p->key = s->key;//有待改进!!!,数据量大时指针操作较方便 
			if(fs->key < s->key)fs->rchild = s->lchild;
			else fs->lchild = s->lchild;
			free(s);
		}
	}
}

中序遍历(测试使用)

  • 二叉树的遍历方法皆可用
  • 测试使用(升序即正确)
//打印调试:若建立正确,中序遍历输出结果必为升序
//BST:左小右大;中序遍历:左根右
//建立的BST中一定没有重复值(所有节点元素可以构成一个集合) 
void InOrderTraverseBST(BSTree T)
{
	if(T == NULL)return;
	else{
		InOrderTraverseBST(T->lchild);
		cout<<T->key<<" ";
		InOrderTraverseBST(T->rchild);
	}
}

小收获

  • 写代码时只给指针变量赋值是无法改变真正指向的
  • 递归设计关键在于退出条件设计,退出条件依赖于对状态的分析,只要捋顺状态转换,递归就清晰易懂;递归定义的结构通常可以使用递归求解,如与二叉树相关的数结构,堆,哈夫曼树等等,共性比较强

完整代码

#include<iostream>
using namespace std;
#include<fstream>
#include<stdlib.h>

typedef struct BSTNode{
	int key;
	struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;

//二叉排序树关键字左小右大&&无重复关键字
//所以通过插入建立二叉查找树时必须先查重,所以查找就尤为重要
//为了查找直接为插入服务,所以查找过程需要记录插入的位置,
//参数设计:
//1,返回值true表示找到;false表示未找到
//2,T为当前需查找的BST;e为需查找的关键字;
//	f为T的双亲,为记录插入位置准备(f初始为空);查找失败:p记录即将被插入的元素的双亲;查找成功:p记录关键字为e的节点位置
//递归设计:
//状态分解(4中状态):
//T空;T非空:T->key =/</> e
//《结束条件:》 
//1,T空说明查找失败,返回false,保存即将要插入元素的双亲位置 
//2,T非空且T->key=e,说明查找成功,返回true,并保存当前位置
//3,T非空且T->key<e,在T的右子树继续查找
//4,T非空且T->key>e,在T的左子树继续查找
bool SearchBST(BSTree T,int e,BSTNode* f,BSTNode* &p,BSTNode* &fd)
{	if(T == NULL){//结束状态1:查找失败 
		p = f;//记录即将被插入节点的双亲 
		return false;
	}
	else{
		if(T->key == e){//结束状态2:查找成功 
			fd = f;
			p = T;//可获取目标节点 
			return true;
		}
		else if(T->key < e) return SearchBST(T->rchild,e,T,p,fd);//在右子树继续查找 
		else return SearchBST(T->lchild,e,T,p,fd);//在左子树继续查找 
	}
}
//插入:利用查找函数 
void InsertBST(BSTree &T,int e)
{
	BSTNode *f=NULL,*p=NULL,*pcur,*fd=NULL;
	if(!SearchBST(T,e,f,p,fd)){
		//将e置入节点 
		pcur = (BSTNode*)malloc(sizeof(BSTNode));
		pcur->key = e;
		pcur->lchild = pcur->rchild = NULL;
		
		if(p == NULL)T = pcur;//根节点为空 
		else{
			if(p->key < e)p->rchild = pcur;//判断插入位置 
			else p->lchild = pcur;
		} 
	} 
}
//从文件读取数据并创建二叉排序树
//反复调用插入 
void CreateBST(BSTree &T)
{
	fstream inFile("二叉排序树.txt",ios::in);
	if(!inFile)cout<<"二叉排序树.txt 打开失败!"<<endl;
	int t;
	while(true)
	{
		inFile>>t;
		if(!inFile)break;//cout<<t<<endl; 
		InsertBST(T,t);
	}
	inFile.close();
} 
//打印调试:若建立正确,中序遍历输出结果必为升序
//BST:左小右大;中序遍历:左根右
//建立的BST中一定没有重复值(所有节点元素可以构成一个集合) 
void InOrderTraverseBST(BSTree T)
{
	if(T == NULL)return;
	else{
		InOrderTraverseBST(T->lchild);
		cout<<T->key<<" ";
		InOrderTraverseBST(T->rchild);
	}
}
//删除节点:分三种情况(假设删除点p) 
//1,p为叶子,直接删除
//2,p为单枝,仅有左/右子树,单枝上移即可
//3,p为双枝,左右子树均有,令p的左子树的最右节点s替代p,
//	同时删除s,由于s是左子树最右节点,所以不可能为双枝,于是又回到了情况1,2 
//根需特殊处理,因为其无双亲 
void DeleteBST(BSTree &T,int e)
{
	BSTNode *f=NULL,*fd=NULL,*p=NULL;
	if(SearchBST(T,e,f,p,fd)){//查找成功 
		if(p->lchild == NULL && p->rchild == NULL){//叶子 
			if(fd == NULL)T = NULL;//根处理 
			else{
				if(fd->key < p->key)fd->rchild = NULL;//判断左右 
				else fd->lchild = NULL;
			}
			free(p); 
		}
		else if(p->lchild == NULL && p->rchild != NULL){//右单枝 
			if(fd == NULL)T = p->rchild;//根处理 
			else if(fd->key < p->key)fd->rchild = p->rchild;//判左右 
			else fd->lchild = p->rchild;
			free(p);
		}
		else if(p->lchild != NULL && p->rchild == NULL){//左单枝 
			if(fd == NULL)T = p->lchild;
			else if(fd->key < p->key)fd->rchild = p->lchild;
			else fd->lchild = p->lchild;
			free(p);
		}
		else{//双枝 
			BSTNode *fs,*s;//s为p的左子树最右节点,fs为s的双亲 ;寻找s:向左移一个节点,在向右走到头 
			fs = p;
			s = p->lchild;// 左移一个节点 
			while(s->rchild != NULL){//向右走到尽头 
				fs = s;
				s = s->rchild;
			}
			p->key = s->key;//有待改进!!!,数据量大时指针操作较方便 
			if(fs->key < s->key)fs->rchild = s->lchild;
			else fs->lchild = s->lchild;
			free(s);
		}
	}
}
int main()
{
	BSTree T = NULL;
	CreateBST(T);
	InOrderTraverseBST(T);
	int choice;
	while(true)
	{
		cout<<endl<<"0--退出  1--插入  2--查找  3--删除"<<endl;
		cout<<"请输入选择:";
		cin>>choice;
		if(choice == 0)break; 
		int t;
		BSTNode *f=NULL,*p=NULL,*fd=NULL;
		switch(choice){
			case 1:cout<<endl<<"请输入插入数:"; 
				   cin>>t;
				   InsertBST(T,t);
				   cout<<endl<<"插入后:"; 
				   InOrderTraverseBST(T);
				   break;
			case 2:cout<<endl<<"请输入查找数:"; 
				   cin>>t;
					if(SearchBST(T,t,f,p,fd))cout<<endl<<" 当前:"<<p->key<<endl;
					break;
			case 3:cout<<endl<<"请输入删除数:"; 
					cin>>t;
					DeleteBST(T,t);
					cout<<endl<<"删除后:"; 
					InOrderTraverseBST(T);break;
		}
	} 
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值