
typedef struct{
ElemType *elem;
int TableLen;
}SSTable;
int Search_Seq(SSTable ST,KeyType key){
ST.elem[0]=key
for(i=ST.TableLen;ST.elem[i]!=key;--i); //当不存在key,将查找到i=0,退出循环
return i;
}
- 空间复杂度:仅使用一个辅助变量,因而空间复杂度为O(1);
- 平均查找长度:ASL = (n+1)/2
- 使用范围:顺序表、链式表
int Search_Seq(SSTable ST,KeyType key){
ST.elem[0]=key
for(i=ST.TableLen;ST.elem[i]>=key;--i); //修改地方:ST.elem[i]>=key
return i;
}
- 平均查找长度:ASL=n/2+n/(n+1)
- 空间复杂度:仅使用一个辅助变量,因而空间复杂度为O(1);
- 使用范围:有序顺序表、有序链式表
int Search_Bin(SSTable ST,KeyType key){
int low=0,high=ST.TableLen-1,mid;
while(low<high){
mid=(low+high)/2;
if(ST.elem[mid]==key) return mid;
else if(ST.elem[mid]<key) low=mid+1;
else high=mid-1;
}
return -1;
}

- 平均查找长度:ASL= l o g 2 log_2 log2(n+1)
- 空间复杂度:O(1)
- 使用范围:有序顺序表、有序链表
- 块内无序,块之间有序。即第一个块中的最大关键字小于第二个块中的所有记录的关键字,以此类推。
- 再建立一个索引表,索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址。
- 查找过程:首先在索引表中确定待查记录所在的块,可以顺序查找也可以折半查找。然后在块内顺序查找。

- 平均查找长度:ASL=ASL折半查找+ASL顺序查找= l o g 2 log_2 log2(b+1) +(s+1)/2,其中b为块数,s为块内记录个数
【算法思想】
- 若key=结点,返回结点地址;
- 若key<结点,查找左子树;
- 若key>结点,查找右子树;
【代码实现】
BSTree SearchBST(BSTree T,KeyType key){
while(T!=NULL&&key!=T->data){
if(key<T->data) T=T->lchild;
else T=T->rchild;
}
return T;
}
【具体实例】

【算法分析】
- 当有序表是静态查找表时,宜用顺序表作为其存储结构,而采用二分查找实现其查找操作;
当有序表是动态查找表时,宜用二叉排序树作为逻辑结构;
【算法思想】
- 若原二叉排序树为空,则直接插入结点;
- 若原二叉树不为空,
*******若关键字k小于根结点值,则插入左子树,
*******若关键字k大于根结点值,则插入到右子树。- 插入的结点一定是一个新添加的叶结点
【代码实现】
int BST_Insert(BiTree &T,KeyType k){
if(T==NULL){ //这是最终要实现的,下面的都是查找适合的位置
T=(BiTree*)malloc(sizeof(BSTNode));
T->data=k;
T->lchild=NULL;
T->rchild=NULL;
return 1;
}
else if(k=T->data) return 0; //二叉排序树存在相同元素,则插入失败
else if(k>T->data) return BST_Insert(T->lchild,k);
else return BST_Insert(T->rchild,k);
}
【具体实例】

4. 二叉排序树的构造
【算法思想】
对每个元素调用一次插入
【代码实现】
void Creat_BST(BiTree &T,KeyType str[],int n){
T=NULL;
int i=0;
while(i<n){
BST_Insert(T,str[i]);
i++;
}
}
【具体实例】:与上述插入过程完成一样。
【算法思想】:将因删除结点而断开的二叉链表重新链接起来,防止重新链接后树的高度增加
- 删除叶节点,只需将其双亲结点指向它的指针清零,再释放它即可。
- 被删除结点只有左子树,可以拿它的左子树结点顶替它的位置,再释放它。
- 被删除结点只有右子树,可以拿它的右子树结点顶替它的位置,再释放它。
- 被删除结点有左右子树,可以拿它的右子树中寻找中序下的第一个结点顶替它的位置,再来处理这个结点的删除问题。
【具体实例】


- 平衡因子:该结点左子树与右子树的高度差
1. 重点是在三个元素中找到中位数作为根结点
【LL平衡旋转】
【RR平衡旋转】
【LR平衡旋转】
【RL平衡旋转】
- 与二叉树的删除相同,删除后要平衡旋转
- 与二叉树的查找相同
- 平均查找长度:ASL=O(log2n)
算法分析
- 高度:二叉平衡树高度保持在O( l o g 2 log_2 log2n)

- B树:B树与AVL树的差别就是B树属于多叉树,又名平衡多路查找树,即一个结点的查找路径不止左右两个,而是有多个。数据库索引技术里大量使用B树和B+树的数据结构,一个结点存储多个值。
- B树的阶数:M阶表示一个B树的结点最多有多少个查找路径。M=2是二叉树,M=3是三叉树。
- 每个结点的值都是按递增次序排序存放的,并遵循左小右大原则
- B树的所有叶子结点都位于同一层并且不带信息。实际上这些结点是不存在的,指向这些结点的指针为空。
- 根节点的子节点个数为[2,M]
- 除根节点以外的非叶子结点的子节点个数为[Math.ceil(M/2),M]。
- 每个非叶子结点的元素的个数=子节点个数-1,为[Math.ceil(M/2)-1,M-1]

在数据库查询中,以树存储数据。树有多少层,就意味着要读多少次磁盘IO。
所以树的高度越矮,就意味着查询数据时,需要读IO的次数就越少。(众所周知,读IO是一件费事的操作)
当数据量大的时候,用AVL树存的话,就算AVL是平衡树,但是也扛不住数据量大,数据量大,AVL树的树高肯定很高,那么读取数据的IO次数也会多。那么有没有办法能压缩AVL树的树高呢?这时候B树就出来了。B树的一个结点可以装多个值,读取时,是把整个结点读到内存,然后在内存中,对结点的值进行处理,在内存中处理速度肯定比磁盘快。所以只要树的高度低,IO少,就能够提升查询效率,这是B树的好处之一。
【算法思想】
B树的查找基本分为两个基本操作:①在B树中找到结点;②在结点内找到关键字。由于B树常存储在磁盘上,因此前一个查找操作是在磁盘上进行的,而后一个查找操作是在内存中进行的,即在找到目标后,先将结点信息读入内存,然后再结点内采用顺序查找法或折半查找法
- 定位:利用上述B树的查找法,找到插入该关键字的子底层中的某个非叶结点。(在B树查找key中,会找到表示查找失败的叶结点,这样就确定了最底层非叶结点的插入位置。注意:插入位置一定是最底层中的某个非叶结点。)
- 插入:插入后结点个数小于m则直接插入,否则需要分裂。
- 分裂:插入key后的原结点,从中间位置math.ceil(m/2)将其中的关键字分为两部分,左部分关键字放在原结点中,右部分关键字放在新结点中,中间位置math.ceil(m/2)插入父节点。若此时导致父结点关键字个数超过了上限也要这样分裂。最后根节点也超上限了,那就高度+1
【具体实例】:6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4

7. B树的删除
【被删除关键字k不在终端结点】(一层一层的代替,把问题向下推,最后推成被删除关键字k在终端结点的问题)
可以用k的前驱或后继来替代k,这个前驱或后继“是通过中序遍历”得到的元素
【被删除关键字k在终端结点】(这个才是复杂的)
- 直接删除关键字k:在终端结点删除关键字k后符合B树定义,则直接删除关键字k
- 兄弟够借:如果删除结点中关键后,关键字个数
- 兄弟不够借:如果删除结点中关键后,关键字个数
【具体实例】B树动态实验地址
基本概念
【数量关系】
- 根结点子树个数为[2,M],关键字个数为[2,M]
- 非根结点子树个数为[Math.ceil(M/2),M],关键字个数为[Math.ceil(M/2),M]
- 结点子树个数和关键字个数相等
【B+树结构】
- 所有分支结点中仅包含它的各个子结点中关键字的最大值及指向其子结点的指针
- 所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排序,并且相邻叶结点按大小顺序相互链接起来。
- 通常在B+树中有两个头指针:一个指向根结点,一个指向关键字最小的叶结点
【B+树与B树的区别】
- B+树中,具有n个关键字的结点只含有n棵子树;
B树中,具有n个关键字的结点含有n+1棵子树;- B+树中,每个结点关键字个数范围为[Math.ceil(M/2),M];
B树中,每个结点关键字个数范围为[Math.ceil(M/2)-1,M-1]- 在B+树中,叶结点包含信息,所有非也结点仅起索引作用
- 在B+树中,叶节点包含了所有关键字
查找操作
- 方法一:从最小关键字开始的顺序查找;(因为是链表形式,所以只能顺序查找)
- 方法二:从根结点开始的多路查找;
- 散列表:根据关键字而直接进行访问的数据结构。也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系。在理想情况下,对散列表进行查找的时间复杂度为O(1)。
- 散列函数:就是上述提到的映射关系
- 冲突处理:把两个或多个关键字映射到同一地址上
【直接定址法】 H(key)=a*key+b
- 冲突:不冲突
- 适用:适合关键字的分布基本连续情况
- 问题:如果关键字不连续,那就产生很多空位
【除留余数法】 H(key)=key%p
- 冲突:关键是选好P,使得每个关键字通过该函数转换后等概率地映射到散列空间上的任一地址,从而尽可能减少冲突的可能性
【数字分析法】 抽取关键字中分布较为均匀的若干位作为散列地址
- 适用:适用在已知关键字集合中,如果更换了关键字集合,则需要重新构造新的散列函数
【平方取中法】 关键字的平法值中间几位作为散列地址
- 特点:散列地址较为均匀
【开放地址法】 即发生冲突时就寻找下一个空的哈希地址,只要地址足够大,总能找到
- 线性探测法:Hi=(Hash(key)+ d i d_i di)mod m,其中di=0,1,2,3…
- 平方探测法:Hi=(Hash(key)+ d i d_i di)mod m,其中di=0, 1 2 1^2 12,- 1 2 1^2 12, 2 2 2^2 22,- 2 2 2^2 22…
- 双散列法 : Hi=(Hash(key)+i* d i d_i di)mod m,其中di= H a s h 2 Hash_2 Hash2(key)
- 伪随机序列法:Hi=(Hash(key)+ d i d_i di)mod m,其中di=伪随机序列法
【链接法】 把所有的同义词存储在一个线性链表中,这个线性链表可以由其散列地址唯一标识。
【查找算法】
- 检测表中地址为Addr的位置上是否有记录,若无记录,返回查找失败;
若有记录,比较Addr对应元素与关键字,如相等则返回查找成功的标记;
否则执行步骤2- 用给定的冲突处理方法计算下一个散列地址,并把Addr置为此地址,转入步骤1
【算法分析】
- 以平均查找长度作为衡量散列表的查找效率的度量
- 决定查找效率的三个因素:散列函数、处理冲突方法、装填因子=(表中记录数n) / (散列表长度m)