二叉排序树(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;
}