http://www.matrix67.com/blog/archives/366

考虑一个事件,它有两种概率均等的结果。比如掷硬币,出现正面和反面的机会是相等的。现在我们希望知道,如果我不断抛掷硬币,需要多长时间才能得到一个特定的序列。

序列一:反面、正面、反面
序列二:反面、正面、正面

首先,我反复抛掷硬币,直到最近的三次抛掷结果形成序列一,然后我记下这次我抛掷了多少次才得到了我要的序列。重复执行这个过程,我可以算出得到序列一平均需要的抛掷次数。同样地,反复抛掷硬币直到序列二产生,它所需要的次数也有一个平均值。你认为这两个平均值哪一个大哪一个小?换句话说,出现序列一平均所需的抛掷次数少还是出现序列二平均需要的次数少?

大多数人会认为,两个序列会以同样快的速度出现,因为在所有“正”和“反”的8种三元组合里,“反正反”和“反正正”各占1/8,其概率是均等的。而事实上,我们将会看到掷出序列二所需的次数更少一些。不妨考虑这样一个问题:在由“正”和“反”构成的n位01序列中,有多少个序列以序列一结尾但之前不曾出现过序列一?有多少个序列以序列二结尾但之前不曾出现过序列二?当n比较小时,两者答案是一样的(例如n=3时符合要求的情况都是唯一的),但到后来n越大时,两者的差距越明显:后者的个数总比前者的个数要多一些。不妨看一看n=6的情况。对于序列一,只有以下5个序列是符合要求的:

  • 反反反反正反
  • 反正正反正反
  • 正正正反正反
  • 正反反反正反
  • 正正反反正反

但对于序列二来说,符合条件的序列就有7个:

  • 反反反反正正
  • 反正反反正正
  • 反反正反正正
  • 正反反反正正
  • 正正反反正正
  • 正正正反正正
  • 正反正反正正

你可以通过计算机编程枚举,计算一下n为其它值的情况。计算结果和刚才也一样:在n位01序列中,以序列二结尾但之前不含序列二的情况不会少于以序列一结尾但之前不含序列一的情况。这说明,抛掷第n次硬币后恰好出现了序列二,其概率不会小于恰好出现序列一的概率。显然,当n渐渐增大时,这个概率应该呈下降趋势;同时,随着n的增长,两个序列各自出现的概率由相等开始慢慢拉开差距,第n次抛掷产生序列二的概率下降得要缓慢一些,或者说更多的情况集中发生在n更小的时候。因此总的来说,出现序列二所需要的抛掷硬币次数的期望值更小。
    虽然我们通过一系列的观察验证了这个结论,并且我们也相信这个结论是正确的(虽然没有严格的证明),但我们仍然不是很接受这个结论。这种情况是有悖于我们的直觉的,它与我们的生活经验不相符合。此刻,我们迫切需要一个解释,来说明这种出人意料的反常现象产生的原因。

如果不亲自做几次试验的话,你很难体会到这种微妙的差距。考虑整个游戏的实际过程,“反正正”序列显然会出现得更早一些。假如某一次我们得到了序列“反正”。如果我们需要的是“反正反”序列,那么下一次抛掷结果为反面将结束本轮的抛掷,而下一次是正面则前功尽弃,你必须再次从零开始。如果我们需要的是“反正正”序列,那么下一次抛掷结果为正面将结束本轮的抛掷,而下一次是反面的话我至少不会惨到一切归零,这相当于我已经有了一个反面作为新的开头,只需再来两个正面即可。这样看的话,提前掷出“反正正”的可能性更大一些。
    反复体会上面的想法,了解KMP算法的网友会恍然大悟:这就是KMP算法的基本思路!考虑这样一个问题:我们在当前字串中寻找子串“反正正”第一次出现的位置。假如当前已经能匹配模式串的前两个字“反正”,主串中的下一个字是“正”则匹配成功,主串的下一个字是“反”则将使模式串的当前匹配位置退到第一个字。考虑一个更复杂的例子:我们希望在主串中寻找子串abbaba,现在已经在主串中找到了abbab。如果主串下一个字符是a,则成功匹配;如果主串下一个字符是b,则模式串最多能匹配到的位置退到了第三个字符,我只需要从abb开始继续匹配,而不必一切从头再来。
    我们可以用KMP算法完美地解决上面的问题。首先预处理出一个数组c,c[i,0]表示模式串匹配到了第i个字符,主串下一个字符为0(反)时,模式串的匹配位置将退到哪里;同样地,c[i,1]表示模式串匹配到了第i个字符,主串下一个字符为1(正)时,新的模式串匹配位置在什么地方。设f[i,j]表示第i次抛掷硬币后恰好匹配到模式串第j位有多少种情况,则f[i,j]=Σf(i-1,k) + Σf(i-1,l),其中k满足c[k,0]=j,l满足c[l,1]=j。将f[i,j]除以2的i次方,我们就得到了相应的概率值。或者更直接地,设P[i,j]表示第i次抛掷硬币后,最远能匹配到的模式串位置是第j位的概率,则P[i,j]=Σ( P(i-1,k)/2 ) + Σ( P(i-1,l)/2 )。注意,我们还应该添加一种特殊的概率值P[i,*],它表示在主串第i个字符以前已经成功匹配过的概率,这样的话下表中每一列的和才能为1。

来看一看程序的输出结果:
Pattern 1: s[]="aba"
主串位置       1        2       3       4       5       6       7       8       9      10
匹配到s[0]  0.5000  0.2500  0.2500  0.2500  0.2188  0.1875  0.1641  0.1445  0.1270  0.1113
匹配到s[1]  0.5000  0.5000  0.3750  0.3125  0.2813  0.2500  0.2188  0.1914  0.1680  0.1475
匹配到s[2]  0.0000  0.2500  0.2500  0.1875  0.1563  0.1406  0.1250  0.1094  0.0957  0.0840
匹配到s[3]  0.0000  0.0000  0.1250  0.1250  0.0938  0.0781  0.0703  0.0625  0.0547  0.0479
已找到匹配  0.0000  0.0000  0.0000  0.1250  0.2500  0.3438  0.4219  0.4922  0.5547  0.6094

Pattern 2: s[]="abb"
主串位置       1        2       3       4       5       6       7       8       9      10
匹配到s[0]  0.5000  0.2500  0.1250  0.0625  0.0313  0.0156  0.0078  0.0039  0.0020  0.0010
匹配到s[1]  0.5000  0.5000  0.5000  0.4375  0.3750  0.3125  0.2578  0.2109  0.1719  0.1396
匹配到s[2]  0.0000  0.2500  0.2500  0.2500  0.2188  0.1875  0.1563  0.1289  0.1055  0.0859
匹配到s[3]  0.0000  0.0000  0.1250  0.1250  0.1250  0.1094  0.0938  0.0781  0.0645  0.0527
已找到匹配  0.0000  0.0000  0.0000  0.1250  0.2500  0.3750  0.4844  0.5781  0.6563  0.7207

这下我们可以清楚地看到,序列二提前出现的概率要大得多。注意到,根据我们的概率定义,表格中每一列的数字之和都是1。同时,倒数第二行的数字之和(有无穷多项)也应该为1,因为最后一行的概率就是倒数第二行的概率值累加的结果,而根据最后一行概率的定义,主串无穷长时已找到匹配的概率应该为1。因此,我们也可以把倒数第二行看作是模式串在主串第i个位置首次匹配成功的概率。我们可以根据这一结果近似地计算出抛掷次数的期望值。

Matrix67原创
转贴请注明出处

KMP算法与一个经典概率问题相关推荐

  1. 利用KMP算法判断一个树是否是另一个树的子树

    方法:利用KMP算法,将两个树进行序列化,分别遍历一遍树,得到两个字符串,时间复杂度是O(n) KMP问题: String1,String2,如果String2为String1的子串,则返回Strin ...

  2. KMP算法的一个C++实现

    本文参考阮一峰老师的KMP算法,重点是"部分匹配表"的建立.算法可参考 http://kb.cnblogs.com/page/176818/ . /* * kmp.cpp * Au ...

  3. 题解 | #F. 一个经典概率问题(提供一个好想好实现不用积分的方法)#

    题解 | #判断链表中是否有环# #include <stdbool.h>/** * struct ListNode { * int val; * struct ListNode * #通 ...

  4. 数据结构与算法之美笔记——基础篇(下):图、字符串匹配算法(BF 算法和 RK 算法、BM 算法和 KMP 算法 、Trie 树和 AC 自动机)

    图 如何存储微博.微信等社交网络中的好友关系?图.实际上,涉及图的算法有很多,也非常复杂,比如图的搜索.最短路径.最小生成树.二分图等等.我们今天聚焦在图存储这一方面,后面会分好几节来依次讲解图相关的 ...

  5. leetcode 028.实现strStr(),即查找重复字符串(KMP算法)

    前言 本题是经典的字符串单模匹配的模型,因此可以使用字符串匹配算法解决,常见的字符串匹配算法包括暴力匹配.Knuth-Morris-Pratt 算法.Boyer-Moore 算法.Sunday 算法等 ...

  6. 字符串的模式匹配 (朴素模式匹配算法 ,KMP算法)

    字符串的模式匹配 寻找字符串p在字符串t中首次出现的起始位置 字符串的顺序存储 typedef struct {char str[MAXSIZE];int length; }seqstring; 朴素 ...

  7. 【★】KMP算法完整教程

    2019独角兽企业重金招聘Python工程师标准>>> KMP算法完整教程 全称:                               Knuth_Morris_Pratt ...

  8. kmp算法详解php,php中字符串匹配KMP算法实现例子

    KMP算法是一个比较高级的算法了,加了改进了,下面我们来在php中实现KMP算法,希望例子对各位同学会带来帮助哦. kmp算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J. ...

  9. 时序分析:KMP算法用于序列识别

    考研基础资料之一的<算法与数据结构>,KMP算法作为串匹配的基本算法,为必考题目之一.对于算法入门来说,也是复杂度稍高的一个基本算法. KMP算法作为串匹配的非暴力算法,是为了减少回溯而设 ...

最新文章

  1. docker命令行解析以及如何向服务器端发送请求(docker源码学习一)
  2. linux下安装apache tomcat,Linux CentOS 7下 Apache Tomcat 7 安装与配置
  3. 使用Win32汇编开发一个dll并在C#中调用
  4. HTTP 错误 500.19 - Internal Server Error
  5. 笔记-项目沟通管理-规划沟通管理
  6. oracle中触发器的语法,解析Oracle触发器的语法
  7. 明显调用的表达式前的括号必须具有指针函数类型_基于指针对象的方法
  8. c# 正则获取html标签内容,c# – 使用正则表达式在多个HTML标记之间获取文本
  9. AndroidManifest.xml清单文件要点
  10. 信息学奥赛一本通 1344:【例4-4】最小花费 | 洛谷 P1576 最小花费
  11. Androidstudio设置Ctrl+W关闭当前编辑的页面窗口
  12. bzoj 5016: [Snoi2017]一个简单的询问(莫队)
  13. 计算机组成原理第五版(白中英)第三章多层次存储器 习题
  14. 计算机行显示性能调整,win7系统怎么调节为最佳性能模式-win7性能,win7运行速度提高90%...
  15. 在硅谷,中国工程师为什么干不过印度人?
  16. Flink 实时计算 - 进阶篇(如何自定义 Data Source 与 Data Sink)
  17. error ‘for’ loop initial declarations are only allowed in C99 mode的报错原因和两种解决办法
  18. marshmallow——快速入门
  19. ACT - 动作游戏
  20. 行云管家:专注多云混合云,树立一站式云管平台行业标杆

热门文章

  1. 八、深入JavaScript的条件语句和循环语句(四)
  2. 七、深入JavaScript函数,对象和作用域(三)
  3. 额外篇 | ggplot (上)
  4. 计算机里硬盘图标,计算机中在硬盘图标下面有个其他里PPS图标肿么删?
  5. 人工智能助力生命科学新发展 | 飞桨博士会第十一期
  6. 今日arXiv精选 | 14 篇 ICCV 2021 最新论文
  7. 快醒醒,一大波最新 AI 论文加开源代码来袭!
  8. poj3070 Fibonacci 矩阵快速幂
  9. linux tcp 包大小,linux – 通过大量连接和小数据包流量高的千兆网络提高TCP性能...
  10. 绝地求生手游7月服务器维护,绝地求生维护公告7月22 | 手游网游页游攻略大全...