Z算法
Z算法
Z算法是一种用于字符串匹配的算法。此算法的核心在于\(z\)数组以及它的求法。
(以下约定字符串下标从\(1\)开始)
\(z\)数组和Z-box
定义\(z\)数组:\(z_{a,i}\)表示从字符串\(a\)的第\(i\)位开始,往后能与\(a\)的前缀匹配的最长长度。显然,\(z_{a,1}=|a|\)恒成立。
一个Z-box是一个区间。给定一个字符串\(a\),那么\(a\)上存在一个Z-box\([l,r]\)当且仅当满足以下全部条件:
- \(l\ne1\);
- \(z_{a,l}\ne0\);
- \(r=l+z_{a,l}-1\)。
通俗来说,若从\(a\)的第\(i\)位开始能与\(a\)的前缀匹配至少\(1\)位,那么能匹配的最长的串覆盖过的区间就是一个Z-box。(\(l\ne1\)是因为位置\(1\)很特殊,本身就是前缀,单独考虑)
例如若\(a=\texttt{acactaac}\),那么\(z_{a}=[8,0,2,0,0,1,2,0]\),Z-box有\([3,4],[6,6],[7,8]\)。
\(z\)数组的求法
给定字符串\(a\),现在我们需要求出\(z_{a}\)。
由于\(z_{a,1}\)的值不用求,而且位置\(1\)比较特殊,就是前缀,所以我们单独处理。
假设我们现在已经知道了\(z_{a,2\sim i-1}\)和使得\(zr\)最大的Z-box\([zl,zr]\),要求出\(z_{a,i}\)并更新\(zl,zr\),那么分\(2\)种情况:
- \(zr<i\)。此时我们直接暴力地从第\(i\)位向后匹配求出\(z_{a,i}\)。如果\(z_{a,i}\ne0\),则令\(zl=i,zr=i+z_{a,i}-1\);
- \(zr\ge i\)。设\(i-zl+1=i'\),即\(i'\)是把跨越\(i\)的Z-box\([zl,zr]\)平移至\(a\)的前缀处后\(i\)的位置。此时又分\(2\)种情况:
- \(i+z_{a,i'}\le zr\)。显然\(\left[i,i+z_{a,i'}\right]\subsetneq[zl,zr]\)。根据Z-box的定义,\(\forall j\in\left[i,i+z_{a,i'}\right],a_j=a_{j-zl+1}\)。那么从\(a\)的第\(i\)位开始与\(a\)的前缀匹配的情况和从第\(i'\)位开始是一样的,直接令\(z_{a,i}=z_{a,i'}\),\(zl,zr\)不变;
- \(i+z_{a,i'}>zr\)。同理,\(\forall j\in[i,zr],a_j=a_{j-zl+1}\)。那么\(a\)的第\(i\sim zr\)位与\(a\)的前缀匹配的情况和第\(i'\sim zr-zl+1\)位是一样的,显然\(z_{a,i}\)至少有\(zr-i+1\)这么多,于是直接从第\(zr+1\)位开始暴力向后匹配求出\(z_{a,i}\),并令\(zl=i,zr=i+z_{a,i}-1\)(因为\(z_{a,i}\)不可能为\(0\))。
这样先令\(z_1=|a|\),然后按上述方法从\(i=2\)递推到\(i=|a|\),便可求出\(z_a\)数组。
下面是求\(z\)数组的代码:
//|a|=n
void z_init(){//求z数组
z[1]=n;//特殊处理z[1]
int zl=0,zr=0;//右端点最大的Z-box
for(int i=2;i<=n;i++)//从i=2递推到i=n
if(zr<i){//第1种情况
z[i]=0;
while(i+z[i]<=n&&a[i+z[i]]==a[1+z[i]])z[i]++;//直接向后暴力匹配
if(z[i])zl=i,zr=i+z[i]-1;//更新右端点最大的Z-box
}
else if(i+z[i-zl+1]<=zr)z[i]=z[i-zl+1];//第2种情况的第1种情况
else{//第2种情况的第2种情况
z[i]=zr-i+1;//z[i]至少有zr-i+1这么多
while(i+z[i]<=n&&a[i+z[i]]==a[1+z[i]])z[i]++;//后面再暴力匹配
zl=i;zr=i+z[i]-1;//更新右端点最大的Z-box
}
}
时间复杂度
按上述方法求\(z\)数组的时间复杂度是线性的\(\mathrm{O}(|a|)\)。
证明(感性):观察上述方法可发现,只有当\(i>zr\)时,才可能将这个位置的字符与前缀匹配,而匹配结束后会把\(zr\)更新至最后一个匹配成功的位置,所以每个字符最多会和前缀成功匹配\(1\)次,所以匹配成功的总次数为\(\mathrm{O}(|a|)\);算\(z_{a,i}\)时,如果往后暴力匹配(即遇到的不是第\(2\)种情况的第\(1\)种情况),那么第\(1\)次匹配失败就会停下来,所以匹配失败的总次数也为\(\mathrm{O}(|a|)\)。因此总时间就是匹配所花的时间\(\mathrm{O}(|a|)+\mathrm{O}(|a|)=\mathrm O(|a|)\)再加上一些赋值、更新\(zl,zr\)等一些\(1\)次只要\(\mathrm O(1)\)的操作,就还是\(\mathrm O(|a|)\)了。得证。
应用
Z算法和ExKMP算法是完全等价的,因为它们求的数组的意思是一样的。但是哈希、KMP能求的东西却有Z算法力所不及的。
Z算法最常用的用法就是字符串模式匹配(这个哈希和KMP也可以做到线性复杂度)。考虑把模式串\(b\)隔一个不常用字符接到文本串\(a\)前面,即令\(c=b+\texttt{!}+a\)。然后求出\(z_c\),从\(i=|b|+2\)到\(i=|c|\)扫一遍,如果\(z_i=|b|\),那么在该位置匹配成功。注意:所谓不常用字符一定不能在串中出现,不然会出bug。如果要用模式串\(c\)去匹配两个文本串\(a,b\),可以令\(d=c+\texttt{!}+a+\texttt @+b\),这时两个分隔符不能相同,不然也会出bug。
为什么Z算法在字符串模式匹配上花的时间和哈希相同呢?Z算法算出了从每一位开始能与前缀匹配的最长长度,但是字符串模式匹配只需要知道能否与前缀\(c_{1\sim|b|}\)匹配,并未完全使用\(z\)数组的价值。如果你就是想知道某一位开始能与前缀匹配的最长长度,哈希可就要二分的帮助了,复杂度是带\(\log\)的,不如用Z算法预处理一下。具体的可以参考下面\(3\)道例题。
不仅如此,Z算法的常数比哈希小(因为为了使哈希不被卡、不在CodeForces上FST,一般要写双重哈希),正确率也比哈希高(Z算法正确率当然是\(100\%\)啦)。
例题
CodeForces 526D - Om Nom and Necklace
CodeForces 427D - Match & Catch
CodeForces 955D - Scissors
Z算法的更多相关文章
- 【算法】字符串匹配之Z算法
求文本与单模式串匹配,通常会使用KMP算法.后来接触到了Z算法,感觉Z算法也相当精妙.在以前的博文中也有过用Z算法来解决字符串匹配的题目. 下面介绍一下Z算法. 先一句话讲清楚Z算法能求什么东西. 输 ...
- Codeforces 126B Password(Z算法)
题意 给定一个字符串 \(s\) ,求一个子串 \(t\) 满足 \(t\) 是 \(s\) 的前缀.后缀且在除前缀后缀之外的地方出现过. \(1 \leq |s| \leq 10^6\) 思路 \( ...
- CodeForces - 1051E :Vasya and Big Integers(Z算法 & DP )
题意:给定字符串S,A,B.现在让你对S进行切割,使得每个切割出来的部分在[A,B]范围内,问方案数. 思路:有方程,dp[i]=Σ dp[j] (S[j+1,i]在合法范围内). 假设M和 ...
- Z算法板子
给定一个串$s$, $Z$算法可以$O(n)$时间求出一个$z$数组 $z_i$表示$s[i...n]$与$s$的前缀匹配的最长长度, 下标从$0$开始 void init(char *s, int ...
- Zbar和Z*算法对比
博客转载自:https://blog.csdn.net/qishandaxue/article/details/45481387 移植zbar和zxing源码到linux平台,zbar移植的是C源码, ...
- [转] Manacher算法详解
转载自: http://blog.csdn.net/dyx404514/article/details/42061017 Manacher算法 算法总结第三弹 manacher算法,前面讲了两个字符串 ...
- 计算字符串的最长回文子串 :Manacher算法介绍
转自: http://www.open-open.com/lib/view/open1419150233417.html Manacher算法 在介绍算法之前,首先介绍一下什么是回文串,所谓回文串,简 ...
- CF #93 div1 B. Password KMP/Z
题目链接:http://codeforces.com/problemset/problem/126/B 大意:给一个字符串,问最长的既是前缀又是后缀又是中缀(这里指在内部出现)的子串. 我自己的做法是 ...
- 最长回文子串问题-Manacher算法
转:http://blog.csdn.net/dyx404514/article/details/42061017 Manacher算法 算法总结第三弹 manacher算法,前面讲了两个字符串相算法 ...
随机推荐
- Nginx代理和负载均衡实验
一.构建两个tomcat容器并启动 [root@localhost bin]# ps -ef|grep tomcat root : pts/ :: /usr/bin/java -Djava.util. ...
- NET Core CSharp初级篇 1-3面向对象
.NET Core CSharp初级篇 1-3 本节内容为面向对象初级教程 类 简介 面向对象是整个C#中最核心最有特色的一个模块了,它很好的诠释了程序与现实世界的联系. 面向对象的三大特征:继承.多 ...
- java反射构建对象和方法的反射调用
Java反射技术应用广泛,其能够配置:类的全限定名,方法和参数,完成对象的初始化,设置是反射某些方法.可以增强java的可配置性. 1.1 通过反射构建对象(无参数): 例如我们使用 ReflectS ...
- 我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3cp8ng15g94wc
我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3cp8ng15g94wc
- 百度OCR 文字识别 Android安全校验
百度OCR接口使用总结: 之前总结一下关于百度OCR文字识别接口的使用步骤(Android版本 不带包名配置 安全性弱).这边博客主要介绍,百度OCR文字识别接口,官方推荐使用方式,授权文件(安全模式 ...
- 小白学python-day05-作业(购物车程序)
购物车需求: 开始输入工资,然后出现购买商品的相关信息,输入编号进行购买 价格>工资提示余额不足,价格<工资提示 成功加入购物车,并且显示余额 然后将购买环节进行循环,直到用户退出购买 然 ...
- python课堂整理5---元组
一.元组 Tuple tu = (111, 22, 33, "alex", (11,22), [(33, 44)], True, ) 元组元素不可被修改,不能被增加或删除 一般 ...
- 完整使用JDBC访问数据库
Connection con = null; PreparedStatement statement = null; ResultSet res = null; List<Student> ...
- UltraEdit不自动生成保存备份文件(.bak)
UltraEdit修改文件或格式化文件保存后会生成烦人的.bak文件. 去掉该功能办法如下: 高级 -> 配置 -> 文件处理 -> 备份 “保存时备份文件”选择“不备份” (Adv ...
- Apache Flink 1.9 重大特性提前解读
今天在 Apache Flink meetup ·北京站进行 Flink 1.9 重大新特性进行了讲解,两位讲师分别是 戴资力/杨克特,zhisheng 我也从看完了整个 1.9 特性解读的直播,预计 ...