二叉排序树
关键点:左子树值<跟节点<=右子树;中序遍历法(左->根->右),其结果为依次递增
插入算法:
//传入值:跟节点地址&parent;插入二叉树的值t(结构体)
p=parent;q=parent;//前趋节点,当前节点
while(q){
p=q;
if(t.value>=q.value) q=q->right;
else q=q->left;
}
if(p){//p不为空,则说明不是空树
if(t.value>=p.value) p->right=t;
else p->left=t;
}
else//该树目前为空
parent=t;
return parent;
平衡二叉排序树
关键点:在二叉排序树的基础上,为每个结点增加一个域balance来存储平衡因子,只要将以*a为根的子树调整为平衡因子=0且高度与原*a为根的子树相同的平衡的二叉排序树即可。
和上述二叉排序树一样,记其将插入结点为t:
-
在查找t结点的插入位置的过程中,记下离t结点最近且平衡因子不等于0的结点,令*a指向该结点;
-
修改自*a至t路径上所有结点的平衡因子;
-
判断以*a为根的子树是否失衡,若失衡,根据4种旋转类型进行调整,使*a的平衡因子=0且*a子树 高度与原*a子树高度相同。
(在左子树上插入时,balance+=1;右子树则balance-=1;)
二叉树需要调整的条件:a指向的指针balance=2 / -2 / 0三种情况。
-2同理;
0则是代表*a的高度不变:
调整的类型为LL、LR、RR、RL四种类型:
LL:a->balance=2,a->lchild->balance=1
LR:a->balance=2,a->lchild->balance!=1
c->balance=0:c左右子树都有
c->balance=1:c右左子树
c->balance=-1:c无左子树
RR:a->balance=-2,a->rchild->balance=-1
逆时针旋转;两节点置0.
RL:a->balance=-2,a->rchild->balance!=-1
子树先顺时针旋转成RR型;再逆时针旋转。
c->balance=0: 调整后b->balance=a->balance=0;
c->balance=1: 则调整后b->balance=-1, a->balance=0;
c->balance=-1: 则调整后b->balance=0, a->balance=1;
#include <stdio.h>
typedef struct node
{
int bf,K;
struct node *lchild=NULL,*rchild=NULL;
}Bnode,*BNode;
int bf;//记录修改之前a->bf的值
typedef enum{ LL, LR, RR, RL} ROT_TP;
ROT_TP rot;
void prt(BNode bt)
{
if(bt)
{
prt(bt->lchild);
printf("%d(%d) ",bt->K,bt->bf);
prt(bt->rchild);
}
}
BNode finda(BNode &bt,BNode s)//找到离s结点 近且平衡因子不等于0的结点a
{
BNode a,p,f;
a=bt; p=bt; f=NULL;s->bf=0;
while(p)
{
if(p->bf!=0)
{a=p; bf=a->bf; }
f=p;
if(s->K<p->K) p=p->lchild;
else p=p->rchild;
}
if(!bt) bt=s;
else if(s->K<f->K) f->lchild=s;
else f->rchild=s;
return a;
}
void modify(BNode &a,BNode &s)//修改自a至s路径上所有结点的平衡因子
{
BNode p;
if(a)//a为NULL说明是空树插入第1个结点
{
if(s->K<a->K)
{ p=a->lchild; a->bf+=1; }//是在*a的左子树上插入
else { p=a->rchild; a->bf-=1; } //是在*a的右子树上插入
while(p&&p!=s)
{
if(s->K<p->K) { p->bf=1; p=p->lchild; }
else { p->bf=-1; p=p->rchild; }
}
}
else return;
if(a->bf==2)
{
if(a->lchild->bf==1) rot=LL;
else rot=LR;
}
else if(a->bf==-2)
{
if(a->rchild->bf==-1) rot=RR;
else rot=RL;
}
}
BNode findp(BNode bt,BNode a)
{
BNode fa=NULL,t=bt;
//寻找a的双亲节点
while(t)
{
if(a==t) return fa;
else if(a->K>t->K)
{ fa=t; t=t->rchild; }
else if(a->K<t->K)
{fa=t; t=t->lchild; }
}
return NULL;
}
void ll(BNode &bt,BNode &a)
{
BNode b,fa;
fa=findp(bt,a);
printf("不平衡,进行LL型调整\n");
b=a->lchild; a->lchild=b->rchild;
a->bf=0; b->rchild=a;
b->bf=0;
//以下实现*b与原*a的双亲结点连接
if(!fa) bt=b; //fa为*a的双亲结点地址
else if(b->K<fa->K) fa->lchild=b;
else fa->rchild=b;
}
void lr(BNode &bt,BNode &a)
{
BNode b,fa,c;
fa=findp(bt,a);
printf("不平衡,进行LR型调整\n");
b=a->lchild; c=b->rchild;
a->lchild=c->rchild; b->rchild=c->lchild;
c->lchild=b; c->rchild=a;
switch(c->bf)
{
case 0: a->bf=b->bf=0;break;
case 1: a->bf=-1; b->bf=0; break;
case -1: a->bf=0; b->bf=1; break;
}
c->bf=0;
//以下实现*c与原*a的双亲结点连接
if(!fa) bt=c; //fa为*a的双亲结点地址
else if(c->K<fa->K) fa->lchild=c;
else fa->rchild=c;
}
void rl(BNode &bt,BNode &a)
{
BNode b,c,fa;
fa=findp(bt,a);
printf("不平衡,进行RL型调整\n");
b=a->rchild; c=b->lchild;
a->rchild=c->lchild; b->lchild=c->rchild;
c->lchild=a; c->rchild=b;
switch(c->bf)
{
case 0: a->bf=b->bf=0;break;
case 1: a->bf=0; b->bf=-1; break;
case -1: a->bf=1; b->bf=0; break;
}
c->bf=0;
//以下实现*c与原*a的双亲结点连接
if(!fa) bt=c; //fa为*a的双亲结点地址
else if(c->K<fa->K) fa->lchild=c;
else fa->rchild=c;
}
void rr(BNode &bt,BNode &a)
{
BNode b,fa;
fa=findp(bt,a);
printf("不平衡,进行RR型调整\n");
b=a->rchild; a->rchild=b->lchild;
a->bf=0; b->lchild=a;
b->bf=0;
//以下实现*b与原*a的双亲结点连接
if(!fa) bt=b; //fa为*a的双亲结点地址
else if(b->K<fa->K) fa->lchild=b;
else fa->rchild=b;
}
int main()
{
int i,n,k;
BNode bt=NULL;
printf("input the num you want insert:");
scanf("%d",&n);
while(n--)
{
bf=0;
BNode s=new Bnode,a;
printf("input the keyword:");
scanf("%d",&s->K);
a=finda(bt,s);
modify(a,s);
if(bf!=0&&a->bf!=0)//需要调整
{
if(rot==RR) rr(bt,a);
else if(rot==RL) rl(bt,a);
else if (rot==LL) ll(bt,a);
else if (rot==LR) lr(bt,a);
printf("调整完毕,此时平衡\n");
}
else
printf("插入后平衡,不用调整\n");
printf("插入后的二叉树中序遍历顺序为:");
prt(bt); printf("\n");
}
return 0;
}
B-树
关键点:合磁盘等直接存取设备上组织动态查找表;
- 树中每个结点至多有m棵子树;
- 若根结点不是叶子结点,则它至少有两棵子树;
- 所有非终端结点至少有棵子树;
- 所有的非终端结点中包含下列信息数据 (n, A0, K1, A1, K2, A2, ... , Kn, An)。
- n为关键字个数; K1, K2, ... , Kn为n个递增的关键字; A0, A1, ... , An为n+1个子树的根结点指针;
示例构建B-树:从空树起连续插入以下20个关键字构建m=4的B-树:50, 15, 09, 18, 03, 85, 33, 72, 48, 22,91, 88, 11, 99, 06, 56, 68, 77, 43, 36
删除时,*p的关键字数目还剩(m/2)向上取整-2 个时需要合并:
- 若*p(该指针包含删除的数)的左兄弟的关键字数目>=:左兄弟中的最大关键字升高; K(介于两指针之间的值)插入*p的最开头;
- 若*p的右兄弟的关键字数目>=:兄弟中的最小关键字升高;K插入*p的结尾;
- 若*p的左兄弟的关键字数目=-1:连同中间的关键字一起合并
- 若*p的右兄弟的关键字数目= -1:连同中间的关键字一起合并
示例删除B-树:5阶B-树,按关键字递减的次序删除所有结点至空树。
B+树
关键点:广泛用于数据库索引;B+树有两个出发指针,一是根结点指针,二是最左边叶子结点的指针; 查询的最终目标是叶子中的某关键字,可以从根结点/最左边叶子出发查询至叶子上的某个关键字(即随机查询,索引查询)。
与B-树的区分:
- (n, K1, A1, K2, A2, ... , Kn, An)(一个关键字对应一个指针)
- 全部叶子结点从左向右串联成单链表,该单链表中各结点 的关键字是递增有序的。
插入:
- 插入关键字只对叶子结点进行。
- 某叶子结点插入关键字后,若该叶子中的关键字数目<=m,此时只需判断插入关键字后该叶子中的最大 (最小)关键字是否改变,若有改变,需更新双亲结点中 对应的最大(最小)关键字;
- 某叶子结点插入关键字后,若该叶子中的关键字数目=m+1,则需将该叶子分裂为两个叶子结点,所含关键字数目分别为和,此时应两个新叶子中的最大(最小)关键字更新至双亲结点中;(一直分裂直至父节点关键字数<=m)
删除:
- 删除关键字只对叶子结点进行
- 关键字最值是否改变,改变则更新
- 关键值数目<,则向上合并(两种方法)
- I型关键字数=-1的结点与关键字数=的左兄弟或右兄弟合并。则合并后的新结点中的关键字 数目为m(当m为奇数时)或m-1(当m为偶数时)。此时, 在双亲结点中删除两个关键字并插入一个新关键字;(判断是否继续向上合并)
- II型关键字数=-1的结点与关键字数>的左兄弟或右兄弟合并。只需将左兄弟中的最大关键字或右兄弟中的最小关键字移动到该结点即可。此时,可 能有必要更新双亲结点中这两个结点对应的最大(最小) 关键字。
哈希表
关键点:查找表中的所有数据元素均 存储到函数H()指定的地址
函数设计方法
-
直接定址法
-
数字分析法
-
平方取中法
-
折叠法
-
位移法
-
除留取余法
-
随机数法
冲突解决方法
一、开放定址法
-
线性探测再散列
增量为:1,2,3...n
-
二次探测再散列
增量为:1^2,-1^2,2^2,-2^2,...,k^2,k^2
示例:H(K)=K mod 13,地址空间范围0~15,用二次探测再散列解决冲突。画出哈希表;若各元素等概率查找,求成功查找时的平均查找长度{19,14,23,01,68,20,84,27,55,11,10,79}
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
27 | 14 | 01 | 68 | 55 | 84 | 19 | 20 |
| 10 | 23 | 11 |
| 79 |
|
|
3 | 1 | 2 | 1 | 2 | 3 | 1 | 1 |
| 3 | 1 | 1 |
| 5 |
|
|
19%13=6
14%13=1
23%13=10
1%13=1;(1*1+1)%16=2;
68%13=3;
20%13=7;
84%13=6;(6+1)%16=7;(6-1)%16=5;
27%13=1;(1+1)%16=2;(1-1)%16=0;
55%13=3;(3+1)%16=4;
11%13=11;
10%13=10;(10+1)%16=11;(10-1)%16=9;
79%13=1;(1+1)%16=2;(1-1)%16=0;(1+4)%16=5;(1-4)%16=13;
ASL成功=1/12*(1*5+3*3+2*2+1*6)=24/12=2
-
伪随机探测再散列
二、再哈希法
设置多个哈希函数,即在同义词产生地址冲突时, 计算另一个哈希函数地址,直到冲突不再发生。
三、链地址法
链表
四、建立一个公共溢出区
当冲突发生时,不管它的哈希地址是什么,都填入溢出表。