145.DS--第六章

查找

image

前面的数据结构基本介绍完了 后续就是关于方法查找和排序
查找方法就是插入和删除的前提
查找顾名思义就是找元素
看几个概念后续用到的:

  • 关键字:唯一标识元素的某个数据项的值
  • 平均查找长度(ASL):查找关键字比较次数的加权平均值 (衡量查找算法效率的核心指标)
  • 查找表:只涉及查找无须动态修改即静态查找表 否则即动态查找表

一.线性结构(顺序 折半 分块查找)

先看顺序查找(线性查找)

  • 适应于顺序表/链表
  • 无序/有序

假设线性表是无序的 顺序查找

就是从一端开始挨个找 直到找到 到另一端若没找到就失败
image

我们看算法实现

int Search_Seq(SSTable ST,ElemType key){
	ST.elem[0]=key;    //哨兵
	for(int i=ST.TableLen;ST.elem[i]!=key;--i);
	return i;
}

上述是从后往前找 引入哨兵就是为了防止重复判断是否越界
其实就是省去了边界判断提高效率

看其ASL(查找每个元素的概率相等)
image
之前概念提过 ASL主要就是比较次数

可以看出当数据量大的时候 效率就低 只用于顺序表

再看有序的线性表顺序查找

image
比如这个例子 有序的线性表可以看作一棵二叉排序树

关于其ASL
image

适用于顺序表和链式存储

看一道题目

Screenshot 2026-05-14 114139
image

折半查找(二分查找)

仅适用于关键字有序的顺序表
原理很多跟有序线性表的顺序查找相似

比如举个例子就能搞懂

比如找元素8
image
上下界 然后通过mid找中间 就可以直接折半找

每次查找采用相同的取整方式

int Binary_Search(SSTable L,ElemType key){
	int low=0,high=L.TableLen-1,mid;
	while(low<=high){
	mid=(low+high)/2;
	if(L.elem[mid]==key) return mid;
	if(L.elem[mid]>key) high=mid-1;
	else low=mid+1;
	}
	return -1;
}

这个很好理解不过多阐述过程了

我们通过此例题来分析一下ASL

  • ASL成功:根结点到目的结点路径上的结点数
  • ASL失败:根结点到对应失败结点父结点路径上的结点数
    image
    两者所除的分母不同前者是成功结点就是存在的 后者是失败的不存在树中的结点

核心就是折半查找判定树是一棵平衡二叉树
后续讲解平衡二叉树

时间复杂度
image
效率高于顺序查找

再次强调只适用于有序的顺序表

再看关于比较次数

image
image
看判定树 通过判定树来分析

分块查找(索引顺序查找)

块内元素可以无序 块间元素必须有序
很好理解先找到对应的块 然后再在块内找

它的ASL
image
当块大小取根号n的时候最优

虽然索引占一定存储空间 但分块结构限制了块内查找的范围所以效率仍然高于普通顺序查找

Screenshot 2026-05-14 114146
找出块数和每块的记录数 套公式
image


二.树形查找(二叉排序树 平衡二叉树 红黑树 B(+)树)

这节比较重量级
一个一个来看

1.二叉排序树 BST

也叫做二叉查找树 动态查找插入删除
前面介绍过特点就是:关键字

  • 左子树<根
  • 根<右子树
    依次类推
    中序遍历可以得到有序关键字序列
    然后来探讨构造 查找 插入删除

通过一道题来看构造
image
ABD选项一样 我们就以C来分析

image

void Creat_BST(BiTree &T,KeyType str[],int n){
	T=NULL;
	int i=0;
	while(i<0){
	BST_Insert(T,str[i]);
	i++;
	}
}

再来看插入操作

比如一棵空二叉排序树 依次插入80 60 90
image
依次类推继续这样插入 最终满足BST的要求即可

int BST_Insert(BiTree &T,KeyType k){
	if(T=NULL){
	T=(BiTree)malloc(sizeof(BSTNode));
	T->data=k;
	T->lchild=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);
}

插入的元素除了空的时候 一定是叶结点

二叉排序树删除操作
有限制:

  • 如果是叶子结点 直接删
  • 如果删除的只有一棵子树 删掉它后用那棵子树上移
  • 如果删除的有两棵子树 则通过中序序列找到后驱(前驱)替代 然后后面采用第二种方式调整

比如还以那道构建题为例
image

查找过程很简单:从根开始找 比根大就往右子树找 比根小就往左子树找 依次类推直到找到或没有

BSTNode *BST_Search(BiTree T,ElemType key){
	while(T!=NULL&&key!=T->data){
	if(key<T->data) T=T->lchild;
	else T=T->rchild;
	}
	return T;
}

也可以采用递归实现 但递归栈需要消耗存储空间效率低

二叉排序树查找效率分析
image
静态就采用顺序表 频繁插入删除动态就采用二叉排序树

image

image

2.平衡二叉树 AVL

前面所有的疑问在这儿都将解开
关于定义就一句话:任意结点的左右子树高度差绝对值不超过1
这里引入一个概念:平衡因子=左右子树高度差 值为1 0 -1

怎么说来看

image
平衡二叉树可为空

通过上图可以看出关于平衡二叉树的查找很简单
就取决于树深度

平均查找时间复杂度
image
结点数最多情况就是满二叉树

难点在于插入删除 因为插入或删除后 依旧需要维持它是一棵平衡二叉树

先看插入规则:

  • LL 左左 1->2 右单旋转
  • RR 右右 -1->-2 左单旋转
  • LR 左右 1->2 先左旋 再右旋
  • RL 右左 -1->-2 先右旋 再左旋

来通过题理解

image
先右旋
image
然后再左旋
image

注意几点:一般这个二叉树本身是二叉排序树 然后再构造平衡二叉树
左右子树高度差 而不是左右子树结点数差
构造过程本质就是插入的过程

还不懂?再来一道从头构建的题目理解

image
image
image

再看平衡二叉树的删除
操作本质就是插入的操作
删除后看如何调整二叉树继续维持成平衡二叉树

我们通过一道题来理解
image
image
其他不再做例子 其实删除本质也跟插入的操作一样 采取插入的规则进行调整 使其维持平衡二叉树

3.红黑树

这是一个难点 后续用一篇文章结合代码来专门研究这个
参考:https://www.cnblogs.com/gaodiyuanjin/p/20072531

4.B(+)树

重点了解B树
定义:自平衡的m路查找树 所有叶结点位于同一层次

同样可为空树 m阶B树

  • 每个结点至多m棵子树 至多包含m-1个关键字
  • 任意结点子树个数=其关键字个数+1
  • 除根结点和叶结点外至少有 向上取整m/2棵子树 至少关键字向上取整m/2-1
  • 所有叶结点位于同一层次
  • 结点中的关键字从左到右递增
  • 根结点不是叶结点的话 至少2棵子树 至少包含1个关键字
    具体构造的样子我们在后面插入和删除来看
    先来看看算B树结点数

image
通过上述公式可得最小/大高度 或求出最多/少结点
注意公式中的n是关键字个数 不是结点数
还有就是求出来的高度不包括叶结点所在的那一层

看题

image
任意结点子树个数=其关键字个数+1

image
image

image
这道题目就可以套公式了
image
得出高后 还有关键字数限制

image
磁盘存取次数和B树的高度密切相关 注意一点B树高度不包括最底层外部叶结点所处那一层 所以-1

image
通过这道题也可以看出B树的一些性质
最多包含3-1=2个关键字 任意结点子树个数=其关键字个数+1
根结点至少2棵子树至少1个关键字
除根叶结点外至少向上取整3/2=2棵子树 至少含关键字向上取整3/2-1=1个关键字

前面已经含沙射影的介绍了B树的查找
两个操作:

  • 1.在磁盘上找目标结点
  • 2.在内存中的结点内查找关键字
    磁盘查找次数即B树高度就是决定查找效率的关键因素

其实关于插入删除就一个核心-关键字数的变化:
image

B树的插入:
image
插入后关键字数没超过m-1那就好说 超了就需要分裂

分裂
image

上述操作都是在关键字插入后进行的

image
这个过程清楚弄懂插入然后关键字数超了分裂的情况

B树的删除:
这个操作比较复杂
核心就是关键字个数不少于下限
image

  • 换位:删除后 父子换位 兄弟父节点调整
  • 合并:删除后 一个兄弟结点及父节点分隔它们的关键字合并

搞懂下面这道题就行
image
image
image
注意这里合并的就是 左/右兄弟结点+父结点中分隔它们的+剩下的结点
image
换位容易理解

这里的操作都是关键字插入前
插入删除构造的过程本质类似还是二叉排序树

至于B+树简单阐述一下:

  • n个关键字结点有n棵子树
  • 关键字数量范围不同
  • 关键字存储位置不同
  • 结点功能不同
  • 支持顺序查找
    还有就是查找无论成功与否 每次查找都对应一条从根结点到某个叶结点的路径

因为涉及不多不过多阐述


三.散列表(哈希表)

将关键字映射到其对应存储地址的函数
我们还需要弄懂几个概念:

  • 冲突:两个或两个以上的不同的关键字映射到同一地址
  • 同义词:发生冲突的不同关键字
    散列表查找的时间复杂度为O(1) 与表中元素的个数无关

先看散列函数的构造:

  • 直接定址法:H(key)=a*key+b 线性 适合关键字分布基本连续 简单不会产生冲突
  • 除留余数法:H(key)=key%p 常用 选择一个不超过散列表长m且尽可能接近m的指数p
  • 数字分析法:使用进制数码 适用于已知且固定的关键字集合
  • 平方取中法:取关键字平方值中间几位作为散列地址 用于关键字取值分布不均或位数少

任何散列函数都无法绝对避免冲突

处理冲突的方法:

  • 开放定址法
  • 拉链法

我们先看开放定址法 其实就是应用散列表中所有空闲地址
又可以分为:

  • 线性探测法(线性探测再散列法):就一个一个往后找空闲 容易发生聚集(堆积)现象
  • 平方探测法(二次探测法):增量序列 1² -1² 2² -2²... 无法探测所有位置
  • 双散列法:使用两个散列函数 第一个若冲突就用第二个函数计算增量
  • 伪随机序列法:增量序列是一个伪随机数生成器产生

注意:采用开放定址法 不能直接物理删除表中已有的元素 而是逻辑删除

拉链法 就是将同义词组织成一个链表
适合频繁插入删除场景
image

几道题目看一下应用:

image
拉链法的应用

image
注意按照概念是错误 应该是H(key)%17

再看一下关于ASL的查找成功失败分析
三步走:

  • 1.表长 画出来
  • 2.找对应函数位置
  • 3.解决冲突

image

顺带配合之前综合总结

image

image
注意看p 就算范围那些的比较次数

再配合看一个删除 注意是逻辑删除
image

还有一个补充:装填因子
image

image

一道综合题:
这道题目关键是看清楚解决冲突的时候始终是从H0开始加的
还有就是查找一个关键字失败就是定位到空位置说明失败了

image
先看第一题整理出来
image
然后第二题就是带进去找
(14*3)%11=9 3
(9+1)%11=10 18
(9+2²)%11=2 14 所以序列为{3,18,14}
第三题
image

综上有个小疑问关于解决冲突的方式是
image
注意是让得到的地址去加 而不是关键字去加 这是概念紧扣 即使答案正确 所以上述有一些题目解法并非是正确的 但可以做出来

按照大纲其实还应该有:字符串模式匹配 后续专门出一篇补充参考https://www.cnblogs.com/gaodiyuanjin/p/20094810

posted @ 2026-05-18 17:00  蒸饺  阅读(15)  评论(0)    收藏  举报