第四章:查找
Part1:平均查找长度(ASL, Average Search Length)
1、计算方法:
A
S
L
=
查
找
次
数
/
要
查
找
的
元
素
个
数
ASL=查找次数/要查找的元素个数
ASL=查找次数/要查找的元素个数
2、注意事项:
查找次数即为关键字比较次数之和,要查找的元素个数一定要仔细计算
Part2:单关键字查找
1、线性结构查找
1.1 顺序查找
按照顺序一个个找过去
1.2折半查找
如果原来的序列已经有序了,那么每次只比较中间位置,如果失败,则再起前/后半部分重复该查找方式
1.3 分块查找
将数据分块,块间有序,块内无需–>块间折半,块内顺序
2、树形结构查找
详见第二章
3、散列结构查找:散列表
散列表使用散列函数来建立关键字和存储地址之间的直接映射关系,当不同的关键字被散列函数映射到同一个地址,被称为冲突,无论散列函数有多么的优秀,冲突都是无法避免的,故我们需要要有冲突处理办法。
3.1 装填因子α
α
=
散
列
表
表
中
的
实
际
记
录
个
数
/
散
列
表
的
总
长
度
α=散列表表中的实际记录个数/散列表的总长度
α=散列表表中的实际记录个数/散列表的总长度
3.2 散列函数
常用H(Key)=key%p
3.3 冲突解决方案
-
辅助链表法
将同义词放在同一个链表中
-
线性探测在散列法
将同义词放到下一个空单元
Part3:字符串查找1
我们考虑这样子一个现实问题,一个比较大是字符串S是否包含了一个较小的字符串P。我们称S为主串,P为模式串(Pattern)
char string_long[n]; // S串 其中 n>m
char string_short[m]; // Pattern串
我们先从朴素的Brute-Force Algorithm(暴力破解)讲起
1、Brute-Force Algorithm
容易想到的是,我们可以一个个子串比较过去,如果比较到最后一个子串也没有找到相同的子串的话,那么说明就不包含了。因此容易写出以下代码
- for代码
int flag = 0; // 用于记录是否找到该子串
for (int i = 0; i < n - m + 1; ++i)
{
for (int j = 0; j < m; ++j)
{
// 如果有一个字符不相同则退出重来
if(string_long[i] != string_short[j])
break;
// 如果全部比较结束均匹配则说明找到了该子串
if(j == m-1)
flag = 1;
}
if (flag)
break;
}
这样子的代码思路可能有些不清晰,请看一下代码
- while代码
int i, j = 0;
while(i < n && j < m){
// 如果两个字符匹配则向前移动
if (string_long[i] == string_short[j]){
i++;
j++;
}
// 如果不匹配则回溯
else{
i = i - j + 1;
j = 0;
}
}
容易由第一份代码得知暴力破解的时间复杂度T(n)=O(nm - m2)=O(nm)
2、对Brute-Force的思考
-
分析
有1可知,该算法慢的跟爬一样(实际上就是在爬),考虑下面的这一种特殊情况
类似于这样子的情况A·······AB和AB做比较,正常人类一眼就能够看出,而Brute-Force却只能逐个比较和,逐个失败
-
考虑改进Brute-Force算法
想要优化一个算法,首先需要问自己一个问题,“我们手上拥有什么信息”,我们拥有的信息是否足够,有效,决定了我们可以把一个算法优化到何种的程度。
在我们的1.2while代码中,决定while循环次数的是两个部分,一个是比较字符串部分,一个是指针回溯部分,分析这两个部分
- 比较字符串:很遗憾,想要确定两个字符串是否相同,真的只能逐个比较所有的字符,这个地方没有改进的余地
- 指针回溯部分:如果我们可以减少指针回溯的幅度,不要每次均回溯j个单位,那就可以减少循环的次数。而且在2.1的那个特殊的例子中,我们得知有许多次的字符串比较一定会失败,这里说不定可以产生突破。
-
对指针回溯部分改进的思考
在Brute-Force算法中,如果从string_long[i]处开始的匹配失败了,那么指针会回溯至string_long[i+1]处重新开始匹配,这很明显没有从过去的失败中学到教训。我们应当注意到,如果字符在j=r处失配了,那么其实string_long[i]到string_long[i + r]的这个部分和string_short[0]到string_short[r]这个部分的是匹配的
需要完成的任务是字符串匹配,而在一次次的失败中,我们可以知道S串的某一个子串是Pattern的某一个前缀,但是这又有什么用?
3、对Brute-Force的改进
有些趟数的比较有可能会成功,而有些则毫无可能,如果我们跳过那些不可能的比较,说不定可以把复杂度降到可以接受的范围
- 一个引例
-
next数组
由2.3的分析可知,从string_long[i]处开始的匹配,当匹配到第r个字符失配时,说明前面r-1个字符是匹配的,如果按照Brute-Force的算法,就依然需要从string_long[i+1]处开始匹配,而我们已经知道string_long[i+1]=string_short[1],相当于模式串自己和自己比较。
如果我们一开始就让模式串自己和自己比较,并把结果记录下来,那么下次失败的时候直接使用即可,无需重复对比,这样子即可节省大量的重复对比次数。
由于我们已知了子串P可以通过事先和自己比较并记录结果的方式来跳过那些不可能成功的尝试,所以我们构造了next数组,令
j = next[j];
首先,我们确定一下next数组的长度,长度为m,因为有可能出现最有一位字符失配的情况,其次我们需要如何求出next数组里面的值呢?
-
计算next数组
next[i]表示string_short这个字符串,前面k个字符恰好等于后k个字符的最大的k
-
算法负责度分析
使用摊还分析,得出改进之后算法的时间负责度为O(n+m)
-
快速求出next数组(递归)
待续未完
这种改进之后的算法就是KMP算法
Part end:参考文献和一些说明
这个部分全部内容参考自这问题的第一篇回答(阮行止大佬的回答):https://www.zhihu.com/question/21923021/answer/1032665486 ↩︎