前言:
查找部分的知识就是线性表的查找,树表的查找和散列表的查找。这里我们就来看看树表查找的知识。首先来看一下目录,看看我们本片博客的基本内容:
目录:
一、二叉排序树
二、平衡二叉树
三、B-树
四、B+树
代码示例(来源于我亲爱的程老师超级棒)
一、二叉排序树
首先,我们要讲二叉排序树的定义。书上或者其它资料上给出的二叉排序树的定义是递归的。用自己的话来总结就是,首先二叉排序树,根节点的值比左子树小,比右子树大。而且它的子树也满足这个规则(有一个重要的性质就是中序遍历一颗二叉排序树可以得到一个节点值递增的有序序列)。
那么我们是要对这个二叉排序树做操作的,虽然我们这里主要讲的是查找操作,但是知识点应该还要涉及创建、插入、删除。
1、二叉排序树的查找
按照根节点大于左子树,小于右子树可以很快的找到我们所要查找的关键值。利用递归就有如下的(二叉排序树的二叉链表存储表示和以前的博客上提到的二叉树的存储结构是相同的,这里就没有列出)。
【算法描述】
BSTree SearchBST(BSTree T,KeyType key)
{//在根指针T所指二叉排序树中递归的查找某关键字等于key的数据元素
//若查找成功,则返回指向该数据元素的指针,否则返回空指针
if((!T)||key==T->data.key) return T;//查找结束
else if(key<T->data.key) return SearchBST(T->lchild,key);//在左子树中继续查找
else return SearchBST(T->rchild,key);//在右子树中继续查找
}
我们可以看到,时间复杂度任然是log2n级别的,和折半查找相差不大,但是就维护表的有序性而言,二叉排序树更加有效,因为无需移动记录,只需修改指针即可完成对结点的插入和删除操作。因此,对于需要经常进行插入、删除和查找运算的表,采用二叉排序树比较好。
2、二叉排序树的插入
这个也是很好理解的,我们直接看算法描述吧
【算法描述】
void InsertBST(BSTree &T,ElemType e)
{//当二叉排序树T中不存在关键字等于e.key的数据元素时,则插入该元素
if(!T)
{//找到插入位置,递归结束
S = new BSTNode;//生成新结点*s
S->data = e;//新结点*s的数据域置为e
S->lchild = S->rchlide=NULL;//新结点*s作为叶子结点
T=S;//把新结点*s链接到已找到的插入位置
}
else if(e.key<T->data.key)
InsertBST(T->lchild,e);//将*s插入左子树
else if(e.key>T->data.key)//将*s插入右子树
InsertBST(T->rchild,e);
}
3、二叉树的创建
在二叉树插入操作的基础上,这个就很好理解了
【算法描述】
void CreatBST(BSTree &T)
{
//依次读入一个关键字为key的结点,将此结点插入二叉排序树T中
T=NULL;//将二叉排序树T初始化为空树
cin>>e;
while(e.key!=ENDFLAG)//ENDFLAG为自定义常量,作为输入结束标志
{
InsertBST(T,e);//将此结点插入二叉排序树T中
cin>>e;
}}
4、二叉树的删除
这里的删除操作是最难的,因为,被删除的结点可能是二叉树中的任何结点,删除结点后,要根据其位置不同修改其双亲结点及相关结点的指针,以保持二叉排序树的特性。
那么你想一下,我们是不是就有几种情况啊。
1)如果我们删除的节点没有左右子树,那么直接删除即可。
2)若我们删除的结点有左子树或者右子树,那么也直接令左子树或者右子树结点成为待删除结点双亲的左子树即可。
3)不好操作的一种就是左右子树都存在的情况,(这里有两种处理情况,我只说最优的一种),就是以被删除结点左子树中关键字最大的结点替代被删结点,然后从左子树中删除这个结点(代码的算法描述就不一一列出了)。
二、平衡二叉树
根据上一个二叉排序树,我们发现,二叉排序树查找的时间复杂度和树的高度有关,高度越小,查找越快,所以就有了现在的这个二叉平衡树。
二叉平衡树的特征归纳一下就是,左子树和右子树的深度之差的绝对值不超过1,当然,它的定义也是递归的,所以左右子树也是平衡二叉树。
这里就是重点讲解插入的时候平衡调整的方法。
1、平衡调整方法。
虽然其他资料上也有,但是我自己归纳了一下,无非就是两种情况(分的细一点就是3种)。1)LL和RR型都是一样的,这个时候我们找到离插入点最近的不满足上面规则的节点,然后往下数三个,进行如下操作:
将中间的结点作为新的跟结点,其余按照规则不变。
2)RL或LR,这个时候如上述所述,但是我们进行的操作是最下面的节点,将它作为新的根结点。然后其它分支再加上就好了。
三、B-树
前面介绍的查找方法均适合于存储在计算机内存中较小的文件,统称为内查找法。若文件很大且存放于外存进行查找时,这些查找方法就不适用了。所以有适用于外查找的平衡多叉树——B-树,磁盘管理系统中的目录管理,以及数据库系统中的索引组织多采用B-树这种数据结构。
1、B-树的定义
一颗m阶的B-树,或为空树,或为满足一下特征的m叉树
1)树中每一个节点至多有m棵子树
2)若根节点不是叶子节点,则至少有两棵子树
3)除根以外的所有非终端结点至少有[m/2]棵子树
4)所有的叶子节点都出现在同一层次上,并不带信息,通常称为失败结点(失败结点并不存在,指向这些结点的指针为空。引入失败结点是为了方便分析B-树的查找性能)
B-树也有查找、插入、删除等操作,可以去查阅相关的资料感兴趣的话。
四、B+树
B+树是一种B-树的变形树,更适合于文件索引系统。
1、B+树和B-树的差异
1)有n棵子树的结点中含有n个关键字
2)所有的叶子结点中包含了全部关键字的信息,以及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接
3)所有的非终端结点可以看成是索引部分,结点中仅含有其子树(根节点)中最大(或最小)关键字。
代码演示:
//二叉排序树的递归查找
//二叉排序树的插入
//二叉排序树的创建
//二叉排序树的删除
#include<iostream>
using namespace std;
#define ENDFLAG '#'
//char a[10]={'5','6','7','2','1','9','8','10','3','4','#'};//全局变量
typedef struct ElemType{
char key;
}ElemType;
typedef struct BSTNode{
ElemType data; //结点数据域
BSTNode *lchild,*rchild; //左右孩子指针
}BSTNode,*BSTree;
//算法 二叉排序树的递归查找
BSTree SearchBST(BSTree T,char key) {
//在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素
//若查找成功,则返回指向该数据元素结点的指针,否则返回空指针
if((!T)|| key==T->data.key) return T; //查找结束
else if (key<T->data.key) return SearchBST(T->lchild,key); //在左子树中继续查找
else return SearchBST(T->rchild,key); //在右子树中继续查找
} // SearchBST
//算法 二叉排序树的插入
void InsertBST(BSTree &T,ElemType e ) {
//当二叉排序树T中不存在关键字等于e.key的数据元素时,则插入该元素
if(!T) { //找到插入位置,递归结束
BSTree S = new BSTNode; //生成新结点*S
S->data = e; //新结点*S的数据域置为e
S->lchild = S->rchild = NULL; //新结点*S作为叶子结点
T =S; //把新结点*S链接到已找到的插入位置
}
else if (e.key< T->data.key)
InsertBST(T->lchild, e ); //将*S插入左子树
else if (e.key> T->data.key)
InsertBST(T->rchild, e); //将*S插入右子树
}// InsertBST
//算法 二叉排序树的创建
void CreateBST(BSTree &T ) {
//依次读入一个关键字为key的结点,将此结点插入二叉排序树T中
T=NULL;
ElemType e;
cin>>e.key; //???
while(e.key!=ENDFLAG){ //ENDFLAG为自定义常量,作为输入结束标志
InsertBST(T, e); //将此结点插入二叉排序树T中
cin>>e.key; //???
}//while
}//CreatBST
void DeleteBST(BSTree &T,char key) {
//从二叉排序树T中删除关键字等于key的结点
BSTree p=T;BSTree f=NULL; //初始化
BSTree q;
BSTree s;
/*------------下面的while循环从根开始查找关键字等于key的结点*p-------------*/
while(p){
if (p->data.key == key) break; //找到关键字等于key的结点*p,结束循环
f=p; //*f为*p的双亲结点
if (p->data.key> key) p=p->lchild; //在*p的左子树中继续查找
else p=p->rchild; //在*p的右子树中继续查找
}//while
if(!p) return; //找不到被删结点则返回
/*―考虑三种情况实现p所指子树内部的处理:*p左右子树均不空、无右子树、无左子树―*/
if ((p->lchild)&& (p->rchild)) { //被删结点*p左右子树均不空
q = p;
s = p->lchild;
while (s->rchild) //在*p的左子树中继续查找其前驱结点,即最右下结点
{q = s; s = s->rchild;} //向右到尽头
p->data = s->data; //s指向被删结点的“前驱”
if(q!=p){
q->rchild = s->lchild; //重接*q的右子树
}
else q->lchild = s->lchild; //重接*q的左子树
delete s;
}//if
else{
if(!p->rchild) { //被删结点*p无右子树,只需重接其左子树
q = p; p = p->lchild;
}//else if
else if(!p->lchild) { //被删结点*p无左子树,只需重接其右子树
q = p; p = p->rchild;
}//else if
/*――――――――――将p所指的子树挂接到其双亲结点*f相应的位置――――――――*/
if(!f) T=p; //被删结点为根结点
else if (q==f->lchild) f->lchild = p; //挂接到*f的左子树位置
else f->rchild = p; //挂接到*f的右子树位置
delete q;
}
}//DeleteBST
//算法 二叉排序树的删除
//中序遍历
void InOrderTraverse(BSTree &T)
{
if(T)
{
InOrderTraverse(T->lchild);
cout<<T->data.key;
InOrderTraverse(T->rchild);
}
}
void main()
{
BSTree T;
cout<<"请输入若干字符,用回车区分,以#结束输入"<<endl;
CreateBST(T);
cout<<"当前有序二叉树中序遍历结果为"<<endl;
InOrderTraverse(T);
char key;//待查找或待删除内容
cout<<"请输入待查找字符"<<endl;
cin>>key;
BSTree result=SearchBST(T,key);
if(result)
{cout<<"找到字符"<<key<<endl;}
else
{cout<<"未找到"<<key<<endl;}
cout<<"请输入待删除的字符"<<endl;
cin>>key;
DeleteBST(T,key);
cout<<"当前有序二叉树中序遍历结果为"<<endl;
InOrderTraverse(T);
}
//算法 B-树的查找
//算法 B-树的插入
#include<iostream>
using namespace std;
#define FALSE 0
#define TRUE 1
#define OK 1
#define m 3 //B-树的阶,暂设为3
typedef struct BTNode{
int keynum; //结点中关键字的个数,即结点的大小
BTNode *parent; //指向双亲结点
int key[m+1]; //关键字矢量,0号单元未用
BTNode *ptr[m+1]; //子树指针矢量
}BTNode,*BTree;
//- - - - - B-树的查找结果类型定义- - - - -
struct Result{
BTNode *pt; //指向找到的结点
int i; //1..m,在结点中的关键字序号
int tag; //1:查找成功,0:查找失败
};
int Search(BTree T,int key)
{
BTree p=T;
int endnum;
if(p) //树不为空时
{
endnum=p->keynum; //获得首节点包含的记录个数
}
else
{
return 0; //返回没找到
}
int i=0;
if(endnum==0)
{
return i; //树存在,但仅有一个为空根节点
}
else if(key>=p->key[endnum])//节点不为空,但当前值比最大的key还大
{
i=endnum;
return i;
}
else if(key<=p->key[1]) //节点不为空,但当前值比最小的key还小
{
return i;}
else
{
for(i=1;i<endnum;i++) //有合适的位置,即处于当前结点的最大和最小值之间,或找到了
{
if(p->key[i]<=key && key<p->key[i+1])
return i;
}
}
}
void Insert(BTree &q,int i,int x,BTree &ap)
{//将x插入q结点的i+1位置中
int j;
for(j=m-1;j>i;j--)
{
//将插入位置之后的key全部后移一位
q->key[j+1]=q->key[j];
}
for(j=m;j>i;j--)
{
//相应地也移动其后ptr的位置
q->ptr[j]=q->ptr[j-1];
}
q->key[i+1]=x;//插入x到该位置
q->ptr[i+1]=ap;
q->keynum++;
}
void split(BTree &q,int s,BTree &ap)
{ //将q->key[s+1,..,m], q->ptr[s+1,..,m]移入新结点*ap作为右结点
//原结点作为新的左侧结点
//中间值被保存在ap[0]->key中,等待找到跳转回InsertBTree()寻找到到合适的插入位置插入
int i;
ap=new BTNode;
for(i=s+1;i<=m;i++)
{ //将q->key[s+1,..,m]保存到ap->key[0,..,m-s+1]中
//将q->ptr[s+1,..,m]保存到ap->ptr[0,..,m-s+1]中
ap->key[i-s-1]=q->key[i];
ap->ptr[i-s-1]=q->ptr[i];
}
if(ap->ptr[0])
{
//当ap有子树的时候
for(i=0;i<=1;i++)
{
//将ap的子树的父亲改为ap自己
ap->ptr[i]->parent=ap;
}
}
ap->keynum=(m-s)-1;
ap->parent=q->parent;//将ap的父亲改为q的父亲
q->keynum=q->keynum-(m-s);//修改q的记录个数
}
void NewRoot(BTree &T,BTree q,int x,BTree &ap)//生成含信息(T, x, ap)的新的根结点*T,原T和ap为子树指针
{
BTree newT=new BTNode;//新建一个结点作为新的根
newT->key[1]=x;//写入新根的key[1]
newT->ptr[0]=T;//将原来的树根作为新根的左子树
newT->ptr[1]=ap;//ap作为新根的右子树
newT->keynum=1;
newT->parent=NULL;//新根的父亲为空
ap->parent=newT;//ap的父亲为新根
T->parent=newT;//T的父亲为新根
T=newT;//树改成新根引导的
}
//算法 B-树的插入
int InsertBTree(BTree &T,int K,BTree q,int i){
int x=K;
BTree ap=NULL;
int finished=FALSE;//x表示新插入的关键字,ap为一个空指针
while(q&&!finished){
Insert(q,i,x,ap); //将x和ap分别插入到q->key[i+1]和q->ptr[i+1]
if (q->keynum<m)
finished=TRUE; //插入完成
else{ //分裂结点*q
int s= m/2;
split(q,s,ap);
x=ap->key[0];// x=q->key[s];
//将q->key[s+1..m], q->ptr[s..m]和q->recptr[s+1..m] 移入新结点*ap
q=q->parent;
if(q)
{
i=Search(q,x);
} //在双亲结点*q中查找x的插入位置
} //else
} //while
if(!finished) //T是空树(参数q初值为NULL)或者根结点已分裂为结点*q和*ap
NewRoot(T,q,x,ap); //生成含信息(T, x, ap)的新的根结点*T,原T和ap为子树指针
return OK;
} //InsertBTree //InsertBTree
//算法 B-树的查找
Result SearchBTree(BTree &T, int key){
/*在m阶B-树T上查找关键字key,返回结果(pt,i,tag)。若查找成功,则特征值tag=1,指针pt所指结点中第i个关键字等于key;否则特征值tag=0,等于key的关键字应插入在指针pt所指结点中第i和第i+1个关键字之间*/
BTree p=T;
BTree q=NULL;
int found=FALSE;
int i=0; //初始化,p指向待查结点,q指向p的双亲
while(p&&!found){
i=Search(p,key);
//在p->key[1..keynum]中查找i,使得:p->key[i]<=key<p->key[i+1]
if(i>0&&p->key[i]==key)
found=TRUE; //找到待查关键字
else
{
q=p;
p=p->ptr[i];
}
}
Result result;
if(found)
{
result.pt=p;
result.i=i;
result.tag=1;
return result;
} //查找成功
else
{
result.pt=q;
result.i=i;
result.tag=0;
return result;
} //查找不成功,返回K的插入位置信息
}//SearchBTree
void InitialBTree(BTree &T)
{
//初始化一个空的根
T->keynum=0;
T->parent=NULL;
for(int i=0;i<m+1;i++)
{
T->ptr[i]=NULL;
}
}
void main()
{
BTree T=new BTNode;
InitialBTree(T);
//先用SearchBTree()找到要插入的位置,得到一个Result结构体
//再用InsertBTree()插入数据
Result result;
int a[11]={45,24,53,90,3,12,50,61,70,100};
for(int i=0;i<10;i++)
{
result=SearchBTree(T,a[i]);
if(result.tag==0)
{
InsertBTree(T,a[i],result.pt,result.i);
}
}
cout<<"OK";
}
后记:
查找的第二部分就结束了,B-树和B+树特别少涉及,因为其实操作难度还是比较大的,可以下去补充哦。下一篇就有查找最后一个部分散列函数查找。冲鸭!如果有误,可以评论指出哦。