查找算法
数据结构-查找(顺序查找,加监视稍,折半查找(代码实现),分块查找,散链表查找(无代码))
一,顺序查找1
按照线性顺序,一个一个的比较,找到返回其位置,找不到返回值0
int Seq_Search1(SeqList p,DataType a)
{
int i=1;
while(i<=p.length&&p.data[i]!=a)
{
i++;
}
if(i>p.length)
return 0;
else
return i;
}
二,顺序查找2(加监视稍)
int Seq_Search2(SeqList p,DataType a)
{
int i;
p.data[0]=a;//监视稍
i=p.length;
while(p.data[i]!=a)
{
i--;
}
return i;
}
比较次数少,效率更高!!!
顺序表上的顺序查找性能分析
对于n个元素的查找表,若查找的是第i个记录时,需进行n-i+1次关键字比较,即ci=n-i+1。设查找每个元素的概率相等。
查找成功时,顺序查找的平均查找长度为:
查找不成功时,表中每个关键字都要比较一次,直到监视哨,因此关键字的比较次数总是n+1次
二,折半查找
折半查找的思想为:在有序表中,取中间元素作为比较对象,
若给定值与中间元素的关键字相等,则查找成功;若给定值小
于中间元素的关键字,则在中间元素的左半区继续查找;若给
定值大于中间元素的关键字,则在中间元素的右半区继续查找。
不断重复上述查找过程,直到查找成功,或所查找的区域无数
据元素,查找失败。
例如: key=64 的查找过程如下:
伪代码
key≠data[mid],key>p.data[mid]
∴low=mid+1
mid=(low+high)/2
a≠data[mid],key<data[mid]
∴high=mid-1
mid=(low+high)/2
a=data[mid] 此时就找到了key(关键字)
返回 mid 下标值
int Binary_Search(SeqList p,DataType a)
{
int mid,low=1,high=p.length;
while(low<=high)
{
mid=(low+high)/2;
if(a==p.data[mid])
{return mid;}
else if(a<p.data[mid])
{high=mid-1;}
else
low=mid+1;
return 0;
}
}
折半查找的平均查找长度可用判定树来分析
设 i=11
先判断根节点
当判定树的左子树
即将查找区间调整到左半区,此时的查找区间是
[1,5],也就是说,左分支上为根结点的值减1,代表查找区间的高端high,此时,根结点的左孩子是(1+5)/2=3,
当判定树的右子树
即将查找区间调整到右半区,此时的查找区间是
[7,11],也就是说,右分支上为根结点的值加1,代表查找区间的低端low,此时,根结点的右孩子是(7+11)/2=9
重复,依次确定每个结点的左右孩子
关于平均查找长度,应用到树形结构的知识,可以点击这里:
树与二叉树链接地址
三 分块查找(索引顺序查找)
1,首先把一个大的线性表分解成若干块,线性表有序或者分块有序,分块必须有序,(若i<j,则第j块中所有记录的关键字均大于第i块中的最大关键字);
2,建立“索引表”(每个结点含有最大关键字域和本块第一个节点的指针,且按关键字有序,如下图所示)
查找过程 先确定待查记录所在快(顺序或折半查找)再在块内查找。
索引储存表示
设分块查找中将长为 n 的表分成均等的 m个块,每块 s 个元素,则 m = (n / s)上取整,
索引顺序表=索引+顺序表
一般情况下,索引表为有续表
查找步骤:
1.由索引表确定所在区间;
2.在顺序表的某个区间进行顺序查找,也可折半查找。
如果索引表中采用顺序查找,则
ASL=ASL(索引)+ASL(顺序表)
ASL=(m+1)/2+(s+1)/2;
如果索引表中采用折半查找,则
ASL=(s+1)/2+log2(m+1)-1
对于表长n确定的情况下,m=
此时分块查找的ASL达到最小值!!! 考点!!考点!!!
上述三种方法的比较
折半查找具有最高的查找效率,但要求必须是顺序存储结构且元素有序排列,且若要进行插入,删除运算时,因需移动大量元素,运行效率降低,所以折半查找只适用于有序表静态查找;
顺序查找效率最低,但对线性表无任何要求(顺序存储或链式存储都可),是否有序都不影响。
分块查找是顺序查找折半查找的综合。
四,散列表查找*
散列查找不同与上述查找
散列表也叫哈希(hash)表或杂凑表,是基于散列存储策略建立的查找表。
基本思想是确定一个函数,求得每个关键字相应的函数值并以此作为存储地址,直接将该数据元素存入到相应的地址空间去,因此它的查找效率很高
但是哈希函数经常产生冲突,一般情况下只能选择适当的哈希函数,使冲突尽可能的减少。
(1)构造好的散列函数Hash(key)。
好的散列函数应符合以下两个原则:
① 所选函数尽可能简单,以便提高转 换速度;
② 所选函数对关键字计算出的地址, 应在散列地址集中大致均匀分布,以 减少空间浪费。
(2) 制定解决冲突的方案。
常用的散列函数
实际造表时,采用何种构造哈希函数的方法取决于建表的关键字集合的情况(包括关键字的范围和形态),总的原则是使产生冲突的可能性降到尽可能地小。
p一般选取为小于m的最大的质数,也可以是不包含小于20质因子的合数。
例
将包含11个元素的关键字序列{ 18,27,1,20,22,6,10,13,41,15,25}。存储在一个长度为12的连续存储空间。
选取关键字与元素位置间的函数为 f(key)=key%11
18%11=7 a[7]=18,27%11=5 a[5]=27 … …
则存储情况为
若要存储23, 23%11=2 a[2]=23,与上表产生冲突
那么怎么解决冲突呢?
处理冲突的方法及散列表的构造和查找
1,开放地址法
用开放定址法处理冲突就是当冲突发生时,形成一个地址序列。沿着这个序列逐个探测,直到找出一个“空”的开放地址,将发生冲突的关键字值存放到该地址中。
其中m为哈希表长, di为增量函数。
增量序列的取法不同,可得到不同的开放地址处理冲突探测方法。
(1)线性探测
Hi=(Hash(k)+di) % m
其中:di=1,2,……,m-1
例
关键字集合
{19,01,23,14,55,68,11,82,36}
设定哈希函数H(k)=k MOD 11(表长=11)
若采用线性探测再散列处理冲突
19%11=8 a[8]=19,
01%11=1 a[1]=01,
23%11=1 a[1]=11,
此时产生冲突
(23+1)%11=2 a[2]=23 解决冲突
14%11=3 a[3]=14,
55%11=0 a[0]=55,
68%11=2 a[2]=68 产生新的冲突
继续向后探测 (68+1)%11=3 a[3]=68 产生新的冲突
继续探测 (68+1+1)%11=4 a[4]=68 冲突解决!
继续
11%11=0 a[0]=11 又产生冲突
向后探测 (11+1)%11=1,(11+2)%11=2,(11+3)%11=3,(11+4)%11=4,(11+5)%=5 冲突解决!
继续
82%11=5 产生冲突
(82+1)%11=6 a[6]=82 解决冲突!
继续
36%11=3 产生冲突
(36+1)%11=4,(36+2)%11=5,(36+3)%11=6,(36+4)%11=7 a[7]=36 解决冲突!
求 ALS 平均查找长度
先在序列下方标上每个空间要查找的次数:
ALS=1/9×(1×4+2×2+3+5+6)=22/9
2)二次探测法(平方探测法)
Hi=(Hash(k)+di+m) % m
di=1^2.
-1^2,
2^2,
-2^2,
…
k^2,
-k^2 (k<=m/2)
还是上述的关键字集合
{19,01,23,14,55,68,11,82,36}
设定哈希函数H(k)=k MOD 11(表长=11)
若采用二次探测再散列处理冲突
19%11=8 a[8]=19,
01%11=1 a[1]=01,
23%11=1 a[1]=11,此时产生冲突
(23-1^2)%11=2 a[2]=23
14%11=3 a[3]=14,
55%11=0 a[0]=55,
68%11=2 a[2]=68 此时产生冲突
开始二次探测 (68+1^2)%11=3,有新的冲突
(68-1^2)%11=2,又有新的冲突
(68+2^2)% =6,a[6]=68,解决冲突!
继续探测 11%11=0 产生冲突
(11+1^2)%11=1 有新的冲突
(11-1^2)%11=-1(10) 循环到序列尾部 解决冲突!
继续探测 82%11=5 a[5]=82,
36%11=3,产生冲突
(36+1^2)%11=4,解决冲突!
求ALS
ALS=(1×5+2×2+3+4)/9=16/9
3)双散列函数探测法*
2,拉链法(链地址法)
根据哈希函数将所有哈希地址相同的记录都链接在同一链表中,如下图
ALS=(6×1+2×2+3)/9=13/9
散列表查找性能分析
散列表的查找过程与建表过程一致。
从查找过程得知,哈希表查找的平均查找长度实际上并不等于1。
决定哈希表查找的ASL的因素:
1)选用的哈希函数;
2)选用的处理冲突的方法;
3)哈希表饱和的程度
——装填因子α=n/m的大小
(n—表中填入的记录数,m—哈希表的长度
3,公共溢出法
为所有冲突的关键字开辟一个公共的溢出区来存放,适用于相对于基本表来说冲突数据很少的情况