邓公数据结构与算法 第十三章 串
ADT
定义和特点
特点:串长远远大于字符种类
术语
ADT接口实现
模式匹配
问题与需求
主要解决4个问题。
算法测试方法
成功与失败的情况分开来测试。
蛮力匹配
构思
- 自左向右,以字符为单位,依次移动模式串直到在某个位置发现匹配
蛮力匹配:版本1
- return i-j
i-j 大于n-m 即可判定失败。因为失败时 (字符串T)i=n,(模板串P)j<m。
i-j<=n-m 即可判定成功
i-j的意义是字符串中与模板串开始比较的位置。
蛮力匹配:版本2
上述算法有个细节
if( m <= j ) break;
直接跳出当前for(i。。。。i++)
的循环,而 当前循环的i
加1操作没有执行!
当返回值 i = n - m 时配对成功,当 i =n - m + 1 时配对失败
蛮力匹配:性能分析
- 最好最坏情况复杂度分析
- 最坏情况例子
字母表={0,1}
成功次数m-1次,失败次数1
-
特点
-
蛮力算法为何低效
- 有重复匹配的前缀:即一个字符串T中的一个位置需要比对多次。
- 不变性:模板P总是右移一位去比对。不会根据情况改变
KMP算法
next【】表
理解next【】表
理解N ( p , j )={ 0 <= t<j | P [ 0, t ) == P[ j-t , j ) };
- 具有特点:
快速右移
避免回溯
next【0】=-1
构造next【】表
next【j】的意思感觉下图说的不对,应该是最大自匹配的真前缀和真后缀的长度减1
- 当p【j】== p【 next [ j ] 】
计算next【j+1】
- 当p【j】!= p【 next [ j ] 】时
next【】表代码实现
KMP复杂度准确分析 ——线性时间O(n)
设一个k=2i-j (分析方法忽略吧)
KMP算法改进版
改进的原因:在当前模式串P的元素重复,使得连续与文本串T的元素进行对比失败,产生冗余,故可跳过相同元素与T的重复比对,提高KMP算法性能
BM_BC算法
动机——bc【】表
因为字符串配对过程中,大部分情况失败的概率远远大于成功的概率,故可采用bc表。next表善于利用配对成功的字符(经验),而bc表 注重配对失败的教训可以帮助我们排除更多字符。
坏字符
-
如何右移
试图从模式串P中找到一个X(与文本串T相同的X),使这个X能与T中的X对齐。P串偏移的距离存入bc【】表中。 -
偏移距离如何确定
位移量取决于适配位置 j,以及X在模式串P中的秩,而与T和i无关。- 由此引申因偏移距离确定发生的三种情况
- 从模式串P中能找到一个或多个X(与文本串T相同的X) , 要位移量尽可能小,则要选择模式串P中X的 秩最大的(也可以说从最大秩开始 后面没有X了)且偏移量 j - bc [ ’ X ’ ] >0
- 从模式串P中不能找到一个X(与文本串T相同的X),则通过最左侧哨兵通配符" * ",将模式串P完整的移过X(文本串T中的X)这个位置。
- 虽然从模式串P找到X 但X过于靠右即秩过大,会造成偏移量 j - bc [ ’ X ’ ] <0 ,故只需将模式串P 向后一个位置。
- 由此引申因偏移距离确定发生的三种情况
构造bc【】
画家策略:模式串P中自左向右,每次出现对应的字符则更新最大秩
/*DSA*/#include "string_pm/string_pm_test.h"
//*****************************************************************************************
// 0 bc['X'] m-1
// | | |
// ........................X***************************************
// .|<------------- 'X' free ------------>|
//*****************************************************************************************
int* buildBC ( char* P ) { //构造Bad Charactor Shift表:O(m + 256)
int* bc = new int[256]; //BC表,与字符表等长
for ( size_t j = 0; j < 256; j ++ ) bc[j] = -1; //初始化:首先假设所有字符均未在P中出现
for ( size_t m = strlen ( P ), j = 0; j < m; j ++ ) //自左向右扫描模式串P
bc[ P[j] ] = j; //将字符P[j]的BC项更新为j(单调递增)——画家算法
/*DSA*/printBC ( bc );
return bc;
}
最好情况—— O(n/m)
在当前情况下,p一共移动 n/m次就停止,所以最好情况是 O(n/m)
最坏情况—— O(n*m)
因为p从最末尾开始
可以发现BM算法也并非完美,故将BM_BC+KMP算法结合则会产生完美的算法
BM_GS算法
好后缀策略
概念
实例
构造GS表
- 构造gs表的过程:MS【】——> ss【】——>gs【】
- MS【】的含义↓
注意3 和 4的含义。
-
“I C E”与后缀最长匹配者
ss【j】= MS【j】的长度= 3 -
“RICE”与后缀最长匹配者
ss【j】= MS【j】的长度= 4
性能
注:n为文本串T的长度,m为模式串P的长度
蛮力算法BF:单次比对成功率越高其复杂度越高,最好O(n+m),最坏O(nm)
KMP算法:稳定一直保持线性时间复杂度O(n+m)
BM算法_BC:适合大字符集、单次比对失败率较高的情况,最好O(n/m),最坏O(nm)
BM算法_BC+GS:充分补偿了BC的缺陷。故最好是O(n/m),最坏是O(n+m)
Karp-Rabin 算法:串即是数
凡物皆数
任何串都可以用 素数序列转化为自然数 N 表示出来。
串亦是数
若视作自然数比对,只需O(1)时间。
数位溢出
70bit???????不懂
70位的128进制才能表示十个位的自然数。咋算的?
散列压缩
基本构思:通过对比压缩后的指纹确定匹配位置,故只需要O(1)时间比对
散列冲突
有散列就有概率发生冲突,即有两个自然数%M 得到的值相等。
因此经过hash()筛选之后,还需要进行严格比对,方可确定是否匹配。
离Karp-Rabin算法只剩最后一步。。。
快速指纹计算
Karp-Rabin代码解析参考:https://blog.csdn.net/Shine__Wong/article/details/102095474
代码涉及将串转为数且要除余,来限定范围。