数据结构(六)什么是好的算法

数据结构(六)什么是好的算法

要点:好的算法的最坏复杂度低

思考:如何降低复杂度

01 什么是复杂度

  • 复杂度分为时间复杂度和空间复杂度
    • 时间复杂度是T(n),空间复杂度是S(n)
    • 时间复杂度可以简单视为函数实现目标过程中执行的乘除法次数
    • 空间复杂度可以简单视为函数实现目标过程中占用的系统内存

02 例子:递归和多项式

1.空间复杂度是S(n)

例子:递归

  • 循环,设for循环占用的空间为N,则S(n)=N;
public static void printN1(int n) {
    for (int i = 1; i <= n; i++) {
        System.out.println(i);
    }
}
  • 递归,设函数占用的空间为N,函数执行的次数为C次,则S(n)=CN;所以递归的空间复杂度随调用次数线性增加
public static void printN2(int n) {
		while (n>0) {
			printN2(n-1);
			System.out.println(n);
			return;
		}
}
  • 当递归执行10万次时,报错且程序终止,是因为当一个函数在调用的时候,会把函数的参数记录在栈空间(简单理解为系统空间),栈的空间是有序的,函数调用次数过多导致栈空间爆满,程序就终止了。
Exception in thread "main" java.lang.StackOverflowError

2.时间复杂度是T(n)

例子:多项式

  • 循环,设for循环了n次,每次循环执行i(i的值为0到n)乘法,则T(n)=(n²+n)/2;
public static double f1(int n,double[] a,double x) {
		double rs=a[0];
		for (int i = 1; i < a.length; i++) {
			rs+=a[i]*Math.pow(x, i);
		}
		return rs;
}
  • 循环,设for循环了n次,每次循环执行1次乘法,则T(n)=n;
public static double f1(int n,double[] a,double x) {
		double rs=a[0];
		for (int i = 1; i < a.length; i++) {
			rs+=a[i]*Math.pow(x, i);
		}
		return rs;
}

03 我的总结

  • 算法的优劣一般就看复杂度,平均复杂度和最坏复杂度,但平均复杂度不容易计算,一般都是计算最坏复杂度,且平均复杂度<=最坏复杂度
  • 时间复杂度过高运行时间过久,数据量越大越要考虑好T(n),空间复杂度过高会导致系统终止,这都是不被允许的
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Java基础系列课程 ppt 和 源码 Java数据结构算法第七讲.avi Java数据结构算法第三十一讲.avi Java数据结构算法第三十七讲.avi Java数据结构算法第三十三讲.avi Java数据结构算法第三十九讲.avi Java数据结构算法第三十二讲.avi Java数据结构算法第三十五讲.avi Java数据结构算法第三十八讲.avi Java数据结构算法第三十讲.avi Java数据结构算法第三十四讲.avi Java数据结构算法第三十讲.avi Java数据结构算法第九讲.avi Java数据结构算法第二十一讲.avi Java数据结构算法第二十七讲.avi Java数据结构算法第二十三讲.avi Java数据结构算法第二十九讲.avi Java数据结构算法第二十二讲.avi Java数据结构算法第二十五讲.avi Java数据结构算法第二十八讲.avi Java数据结构算法第二十讲.avi Java数据结构算法第二十四讲.avi Java数据结构算法第二十讲.avi Java数据结构算法第八讲.avi Java数据结构算法讲.avi Java数据结构算法第十一讲.avi Java数据结构算法第十七讲.avi Java数据结构算法第十三讲.avi Java数据结构算法第十九讲.avi Java数据结构算法第十二讲.avi Java数据结构算法第十五讲.avi Java数据结构算法第十八讲.avi Java数据结构算法第十讲.avi Java数据结构算法第十四讲.avi Java数据结构算法第十讲.avi Java数据结构算法第四十一讲.avi Java数据结构算法第四十三讲.avi Java数据结构算法第四十二讲.avi Java数据结构算法第四十四讲.avi Java数据结构算法第四十讲.avi 第一讲.exe 第三讲.exe 第二讲.exe 第五讲.exe 第四讲.exe
高分笔记系列书籍简介高分笔记系列书籍包括《数据结构高分笔记》《组成原理高分笔记》《操作系统高分笔记》《计算机网络高分笔记》等,是一套针对计算机考研的辅导书。它们2010 年夏天诞生于一群考生之手,其写作风格突出表现为:以学生的视角剖析知识难点;以通俗易懂的语言取代晦涩难懂的专业术语;以成功考生的亲身经历指引复习方向;以风趣幽默的笔触缓解考研压力。相信高分笔记系列书籍带给考生的将是更高效、更明确、更轻松、更愉快的复习过程。 数据结构高分笔记简介众所周知,在计算机统考的四门专业课中,最难拿高分的就是数据结构。但是这门课本身的难度并不是考生最大的障碍,真正的障碍在于考生不能独自把握复习方向和考试范围。也许有学生要问,我们不是有大纲吗?照着大纲去复习不就可以了吗?表面上看是这样的,但是当你真正开始复习的时候就会发现,其实大纲只给了考生一个大致范围,有很多地方是模糊的,这些模糊的地方可能就是你纠结的地方。比如大纲里对于栈和队列的考查中有这么一条:“栈和队列的应用”。这个知识点就说得很模糊,因为只要涉及栈和队列的地方,都是其应用的范畴,这时考生该怎么办呢?于是把所有的希望寄托于参考书,希望参考书能帮助我们理解大纲的意图。参考书分为两种:一是课本,二是与课本配套的辅导书。对于课本,考生用得最多的就是严蔚敏老师编写的“严版”《数据结构》。因为这本书的内容非常丰富,如果能把这本书中考试大纲要求的章节理解透彻,参加考研就没有任何问题,但是这个过程是漫长的,除非本科阶段就学得非常好。计算机统考后,专业课四门加上公共课三门,一共是七门,绝大多数考生复习的时间一般也就个月,而数据结构的复习需要占用多少时间,这点大家都很清楚。要在这么短的时间内掌握“严版”《数据结构》中考纲要求的知识点,基本上是不可能的,这就需要一本辅导书来依照大纲从课本中总结出考纲要求的知识点,才能使得考生在短时间内达到研究生考试的要求。市面上的参考书有两种:一种是四合一的辅导书,另一种是分册的。比如网上流行的《1800 题》及其第2 版,此书中题目极多,并且有很多老式的考研题,有些算法设计题的答案是用Pascal语言写的。这本书中的题目一般考生全做基本上是不可能的,挑着做又会把时间浪费在选题上。不可否认,这本书确实是一本非常好的题库,但是考生直接拿来用作考研辅导书却不太合适。在这种情况下,就需要有一本优质的完全针对新大纲的辅导书出现,这就是高分笔记产生的原因。 接下来详细介绍一下这本辅导书的写作过程,请看下图: 前 言VII图中所涉及的书都是大家很熟悉的。当年这些书编者都买了,花了很大心思才从中找出在考研战场上真正有用的东西。比如《1800 题》,里边既有好题,又有废题,相信很多人都希望有人能从中去掉重复的题目,选出大纲要求的题目,并能把解答写得更通俗易懂些,而现在编者所做的工作就是从这1800 道题中选出大纲要求的题目,并且修正部分解答,使其更容易理解。其次是“严版”《数据结构》,此书写得很严谨,语言表述非常专业,但对于基础稍差的学生来说读起来十分费力,要很长时间才能适应这本书的写作风格。如果有一本辅导书能把那些复杂程序的执行过程、拗口的专业术语、令人头大的符号翻译成容易理解的语言,就可以节省考生很多时间,因此,编者所做的事情就是根据自己复习的经验,以及对这本书的理解,把其中考试不需要的内容删掉,把需要的内容改造成一般考生容易接受的形式。对于李春葆老师的《数据结构习题与解析》,也做了类似的处理,并且在这本书中穿插讲解了一些考试大纲中没有明文规定,但是很多算法题目中大量用到的算法设计思想,来帮助大家提高解算法设计题的能力,比如搜索(打印图中两结点之间的所有路径)、分治法(二分法排序、求树的深度等)等算法思想。因此,相信本书会给读者的考研复习带来很大的帮助。 另外,本书配有微信公众号来收集读者的反馈,这也是本书不断更新完善的重要途径,即根据考生最需要的内容来作为调整讲解的依据。 本书特点: (1)精心挑选出适合考研的习题,并配上通俗易懂的答案,供读者自测和练习。 (2)总结出考研必备知识点,并且帮读者把其中过于专业、过于严谨的表述翻译成通俗易懂的语言。 (3)针对近年数据结构大题的出题风格(比如算法设计题目中的三段式题目:①表述算法思想;②写出算法描述;③计算算法的时间和空间复杂度),设计了独特的真题仿造部分,让读者在复习的过程中逐渐适应不同类型的题目。 参加本书编写的人员还有:章露捷,刘建萍,施伟,刘炳瑞,刘菁,孙琪,金苍宏,2019 版数据结构高分笔记VIII蔡明婉,吴雪霞,孙建兴,张继建,胡素素,邱纪虎,率方杰,李玉兰,率秀颂,刘忠艳,赵建,张兆红,张来恩,张险峰,殷凤岭,于雪友,周桂芝,张玉奎,李亚静,周莉,李娅,刘梅,殷晓红,李艳红,王中静,张洪英,王艳红
目录 第 一 章 概 论 .............................................................................................................................. 3 第 二 章 线 性 表 ....................................................................................................................... 4 第 三 章 栈 和 队 列 ................................................................................................................. 8 第 四 章 串 ................................................................................................................................ 15 第 五 章 多 维 数 组 和 广 义 表 ...................................................................................... 18 第 章 树 ................................................................................................................................ 20 第 七 章 图 ................................................................................................................................ 23 第 八 章 排 序 ........................................................................................................................... 26 第 九 章 查 找 ........................................................................................................................... 28 第 十 章 文 件 ........................................................................................................................... 32 (后附网友学习经验总结)
KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法。简单匹配算法的时间复杂度为O(m*n);KMP匹配算法。可以证明它的时间复杂度为O(m+n).。 一.简单匹配算法 先来看一个简单匹配算法的函数: int Index_BF ( char S [ ], char T [ ], int pos ) { /* 若串 S 中从第pos(S 的下标0≤pos<StrLength(S))个字符 起存在和串 T 相同的子串,则称匹配成功,返回第一个 这样的子串在串 S 中的下标,否则返回 -1 */ int i = pos, j = 0; while ( S[i+j] != '\0'&& T[j] != '\0') if ( S[i+j] == T[j] ) j ++; // 继续比较后一字符 else { i ++; j = 0; // 重新开始新的一轮匹配 } if ( T[j] == '\0') return i; // 匹配成功 返回下标 else return -1; // 串S中(第pos个字符起)不存在和串T相同的子串 } // Index_BF 此算法的思想是直截了当的:将主串S中某个位置i起始的子串和模式串T相比较。即从 j=0 起比较 S[i+j] 与 T[j],若相等,则在主串 S 中存在以 i 为起始位置匹配成功的可能性,继续往后比较( j逐步增1 ),直至与T串中最后一个字符相等为止,否则改从S串的下一个字符起重新开始进行下一轮的"匹配",即将串T向后滑动一位,即 i 增1,而 j 退回至0,重新开始新一轮的匹配。 例如:在串S=”abcabcabdabba”中查找T=” abcabd”(我们可以假设从下标0开始):先是比较S[0]和T[0]是否相等,然后比较S[1] 和T[1]是否相等…我们发现一直比较到S[5] 和T[5]才不等。如图: 当这样一个失配发生时,T下标必须回溯到开始,S下标回溯的长度与T相同,然后S下标增1,然后再次比较。如图: 这次立刻发生了失配,T下标又回溯到开始,S下标增1,然后再次比较。如图: 这次立刻发生了失配,T下标又回溯到开始,S下标增1,然后再次比较。如图: 又一次发生了失配,所以T下标又回溯到开始,S下标增1,然后再次比较。这次T中的所有字符都和S中相应的字符匹配了。函数返回T在S中的起始下标3。如图: 二. KMP匹配算法 还是相同的例子,在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,当第一次搜索到S[5] 和T[5]不等后,S下标不是回溯到1,T下标也不是回溯到开始,而是根据T中T[5]==’d’的模式函数值(next[5]=2,为什么?后面讲),直接比较S[5] 和T[2]是否相等,因为相等,S和T的下标同时增加;因为又相等,S和T的下标又同时增加。。。最终在S中找到了T。如图: KMP匹配算法和简单匹配算法效率比较,一个极端的例子是: 在S=“AAAAAA…AAB“(100个A)中查找T=”AAAAAAAAAB”, 简单匹配算法每次都是比较到T的结尾,发现字符不同,然后T的下标回溯到开始,S的下标也要回溯相同长度后增1,继续比较。如果使用KMP匹配算法,就不必回溯. 对于一般文稿中串的匹配,简单匹配算法的时间复杂度可降为O (m+n),因此在多数的实际应用场合下被应用。 KMP算法的核心思想是利用已经得到的部分匹配信息来进行后面的匹配过程。看前面的例子。为什么T[5]==’d’的模式函数值等于2(next[5]=2),其实这个2表示T[5]==’d’的前面有2个字符和开始的两个字符相同,且T[5]==’d’不等于开始的两个字符之后的第三个字符(T[2]=’c’).如图: 也就是说,如果开始的两个字符之后的第三个字符也为’d’,那么,尽管T[5]==’d’的前面有2个字符和开始的两个字符相同,T[5]==’d’的模式函数值也不为2,而是为0。 前面我说:在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,当第一次搜索到S[5] 和T[5]不等后,S下标不是回溯到1,T下标也不是回溯到开始,而是根据T中T[5]==’d’的模式函数值,直接比较S[5] 和T[2]是否相等。。。为什么可以这样? 刚才我又说:“(next[5]=2),其实这个2表示T[5]==’d’的前面有2个字符和开始的两个字符相同”。请看图 :因为,S[4] ==T[4],S[3] ==T[3],根据next[5]=2,有T[3]==T[0],T[4] ==T[1],所以S[3]==T[0],S[4] ==T[1](两对相当于间接比较过了),因此,接下来比较S[5] 和T[2]是否相等。。。 有人可能会问:S[3]和T[0],S[4] 和T[1]是根据next[5]=2间接比较相等,那S[1]和T[0],S[2] 和T[0]之间又是怎么跳过,可以不比较呢?因为S[0]=T[0],S[1]=T[1],S[2]=T[2],而T[0] != T[1], T[1] != T[2],==> S[0] != S[1],S[1] != S[2],所以S[1] != T[0],S[2] != T[0]. 还是从理论上间接比较了。 有人疑问又来了,你分析的是不是特殊轻况啊。 假设S不变,在S中搜索T=“abaabd”呢?答:这种情况,当比较到S[2]和T[2]时,发现不等,就去看next[2]的值,next[2]=-1,意思是S[2]已经和T[0] 间接比较过了,不相等,接下来去比较S[3]和T[0]吧。 假设S不变,在S中搜索T=“abbabd”呢?答:这种情况当比较到S[2]和T[2]时,发现不等,就去看next[2]的值,next[2]=0,意思是S[2]已经和T[2]比较过了,不相等,接下来去比较S[2]和T[0]吧。 假设S=”abaabcabdabba”在S中搜索T=“abaabd”呢?答:这种情况当比较到S[5]和T[5]时,发现不等,就去看next[5]的值,next[5]=2,意思是前面的比较过了,其中,S[5]的前面有两个字符和T的开始两个相等,接下来去比较S[5]和T[2]吧。 总之,有了串的next值,一切搞定。那么,怎么求串的模式函数值next[n]呢?(本文中next值、模式函数值、模式值是一个意思。) 三. 怎么求串的模式值next[n] 定义: (1)next[0]= -1 意义:任何串的第一个字符的模式值规定为-1。 (2)next[j]= -1 意义:模式串T中下标为j的字符,如果与首字符 相同,且j的前面的1—k个字符与开头的1—k 个字符不等(或者相等但T[k]==T[j])(1≤k<j)。 如:T=”abCabCad” 则 next[6]=-1,因T[3]=T[6] (3)next[j]=k 意义:模式串T中下标为j的字符,如果j的前面k个 字符与开头的k个字符相等,且T[j] != T[k] (1≤k<j)。 即T[0]T[1]T[2]。。。T[k-1]== T[j-k]T[j-k+1]T[j-k+2]…T[j-1] 且T[j] != T[k].(1≤k<j); (4) next[j]=0 意义:除(1)(2)(3)的其他情况。 举例: 01)求T=“abcac”的模式函数的值。 next[0]= -1 根据(1) next[1]=0 根据 (4) 因(3)有1<=k<j;不能说,j=1,T[j-1]==T[0] next[2]=0 根据 (4) 因(3)有1<=k<j;(T[0]=a)!=(T[1]=b) next[3]= -1 根据 (2) next[4]=1 根据 (3) T[0]=T[3] 且 T[1]=T[4] 即 下标 0 1 2 3 4 T a b c a c next -1 0 0 -1 1 若T=“abcab”将是这样: 下标 0 1 2 3 4 T a b c a b next -1 0 0 -1 0 为什么T[0]==T[3],还会有next[4]=0呢, 因为T[1]==T[4], 根据 (3)” 且T[j] != T[k]”被划入(4)。 02)来个复杂点的,求T=”ababcaabc” 的模式函数的值。 next[0]= -1 根据(1) next[1]=0 根据(4) next[2]=-1 根据 (2) next[3]=0 根据 (3) 虽T[0]=T[2] 但T[1]=T[3] 被划入(4) next[4]=2 根据 (3) T[0]T[1]=T[2]T[3] 且T[2] !=T[4] next[5]=-1 根据 (2) next[6]=1 根据 (3) T[0]=T[5] 且T[1]!=T[6] next[7]=0 根据 (3) 虽T[0]=T[6] 但T[1]=T[7] 被划入(4) next[8]=2 根据 (3) T[0]T[1]=T[6]T[7] 且T[2] !=T[8] 即 下标 0 1 2 3 4 5 6 7 8 T a b a b c a a b c next -1 0 -1 0 2 -1 1 0 2 只要理解了next[3]=0,而不是=1,next[6]=1,而不是= -1,next[8]=2,而不是= 0,其他的好象都容易理解。 03) 来个特殊的,求 T=”abCabCad” 的模式函数的值。 下标 0 1 2 3 4 5 6 7 T a b C a b C a d next -1 0 0 -1 0 0 -1 4 next[5]= 0 根据 (3) 虽T[0]T[1]=T[3]T[4],但T[2]==T[5] next[6]= -1 根据 (2) 虽前面有abC=abC,但T[3]==T[6] next[7]=4 根据 (3) 前面有abCa=abCa,且 T[4]!=T[7] 若T[4]==T[7],即T=” adCadCad”,那么将是这样:next[7]=0, 而不是= 4,因为T[4]==T[7]. 下标 0 1 2 3 4 5 6 7 T a d C a d C a d next -1 0 0 -1 0 0 -1 0 如果你觉得有点懂了,那么 练习:求T=”AAAAAAAAAAB” 的模式函数值,并用后面的求模式函数值函数验证。 意义: next 函数值究竟是什么含义,前面说过一些,这里总结。 设在字符串S中查找模式串T,若S[m]!=T[n],那么,取T[n]的模式函数值next[n], 1. next[n]= -1 表示S[m]和T[0]间接比较过了,不相等,下一次比较 S[m+1] 和T[0] 2. next[n]=0 表示比较过程中产生了不相等,下一次比较 S[m] 和T[0]。 3. next[n]= k >0 但k<n, 表示,S[m]的前k个字符与T中的开始k个字符已经间接比较相等了,下一次比较S[m]和T[k]相等吗? 4. 其他值,不可能。 四. 求串T的模式值next[n]的函数 说了这么多,是不是觉得求串T的模式值next[n]很复杂呢?要叫我写个函数出来,目前来说,我宁愿去登天。好在有现成的函数,当初发明KMP算法,写出这个函数的先辈,令我佩服得体投地。我等后生小子,理解起来,都要反复琢磨。下面是这个函数: void get_nextval(const char *T, int next[]) { // 求模式串T的next函数值并存入数组 next。 int j = 0, k = -1; next[0] = -1; while ( T[j/*+1*/] != '\0' ) { if (k == -1 || T[j] == T[k]) { ++j; ++k; if (T[j]!=T[k]) next[j] = k; else next[j] = next[k]; }// if else k = next[k]; }// while ////这里是我加的显示部分 // for(int i=0;i<j;i++) //{ // cout<<next[i]; //} //cout<<endl; }// get_nextval  另一种写法,也差不多。 void getNext(const char* pattern,int next[]) { next[0]= -1; int k=-1,j=0; while(pattern[j] != '\0') { if(k!= -1 && pattern[k]!= pattern[j] ) k=next[k]; ++j;++k; if(pattern[k]== pattern[j]) next[j]=next[k]; else next[j]=k; } ////这里是我加的显示部分 // for(int i=0;i<j;i++) //{ // cout<<next[i]; //} //cout<<endl; } 下面是KMP模式匹配程序,各位可以用他验证。记得加入上面的函数 #include <iostream.h> #include <string.h> int KMP(const char *Text,const char* Pattern) //const 表示函数内部不会改变这个参数的值。 { if( !Text||!Pattern|| Pattern[0]=='\0' || Text[0]=='\0' )// return -1;//空指针或空串,返回-1。 int len=0; const char * c=Pattern; while(*c++!='\0')//移动指针比移动下标快。 { ++len;//字符串长度。 } int *next=new int[len+1]; get_nextval(Pattern,next);//求Pattern的next函数值 int index=0,i=0,j=0; while(Text[i]!='\0' && Pattern[j]!='\0' ) { if(Text[i]== Pattern[j]) { ++i;// 继续比较后继字符 ++j; } else { index += j-next[j]; if(next[j]!=-1) j=next[j];// 模式串向右移动 else { j=0; ++i; } } }//while delete []next; if(Pattern[j]=='\0') return index;// 匹配成功 else return -1; } int main()//abCabCad { char* text="bababCabCadcaabcaababcbaaaabaaacababcaabc"; char*pattern="adCadCad"; //getNext(pattern,n); //get_nextval(pattern,n); cout<<KMP(text,pattern)<<endl; return 0; } 五.其他表示模式值的方法 上面那种串的模式值表示方法是最优秀的表示方法,从串的模式值我们可以得到很多信息,以下称为第一种表示方法。第二种表示方法,虽然也定义next[0]= -1,但后面绝不会出现-1,除了next[0],其他模式值next[j]=k(0≤k<j)的意义可以简单看成是:下标为j的字符的前面最多k个字符与开始的k个字符相同,这里并不要求T[j] != T[k]。其实next[0]也可以定义为0(后面给出的求串的模式值的函数和串的模式匹配的函数,是next[0]=0的),这样,next[j]=k(0≤k<j)的意义都可以简单看成是:下标为j的字符的前面最多k个字符与开始的k个字符相同。第三种表示方法是第一种表示方法的变形,即按第一种方法得到的模式值,每个值分别加1,就得到第三种表示方法。第三种表示方法,我是从论坛上看到的,没看到详细解释,我估计是为那些这样的编程语言准备的:数组的下标从1开始而不是0。 下面给出几种方法的例子: 表一。 下标 0 1 2 3 4 5 6 7 8 T a b a b c a a b c (1) next -1 0 -1 0 2 -1 1 0 2 (2) next -1 0 0 1 2 0 1 1 2 (3) next 0 1 0 1 3 0 2 1 3 第三种表示方法,在我看来,意义不是那么明了,不再讨论。 表二。 下标 0 1 2 3 4 T a b c A c (1)next -1 0 0 -1 1 (2)next -1 0 0 0 1 表三。 下标 0 1 2 3 4 5 6 7 T a d C a d C a d (1)next -1 0 0 -1 0 0 -1 0 (2)next -1 0 0 0 1 2 3 4 对比串的模式值第一种表示方法和第二种表示方法,看表一: 第一种表示方法next[2]= -1,表示T[2]=T[0],且T[2-1] !=T[0] 第二种表示方法next[2]= 0,表示T[2-1] !=T[0],但并不管T[0] 和T[2]相不相等。 第一种表示方法next[3]= 0,表示虽然T[2]=T[0],但T[1] ==T[3] 第二种表示方法next[3]= 1,表示T[2] =T[0],他并不管T[1] 和T[3]相不相等。 第一种表示方法next[5]= -1,表示T[5]=T[0],且T[4] !=T[0],T[3]T[4] !=T[0]T[1],T[2]T[3]T[4] !=T[0]T[1]T[2] 第二种表示方法next[5]= 0,表示T[4] !=T[0],T[3]T[4] !=T[0]T[1] ,T[2]T[3]T[4] !=T[0]T[1]T[2],但并不管T[0] 和T[5]相不相等。换句话说:就算T[5]==’x’,或 T[5]==’y’,T[5]==’9’,也有next[5]= 0 。 从这里我们可以看到:串的模式值第一种表示方法能表示更多的信息,第二种表示方法更单纯,不容易搞错。当然,用第一种表示方法写出的模式匹配函数效率更高。比如说,在串S=“adCadCBdadCadCad 9876543”中匹配串T=“adCadCad”, 用第一种表示方法写出的模式匹配函数,当比较到S[6] != T[6] 时,取next[6]= -1(表三),它可以表示这样许多信息: S[3]S[4]S[5]==T[3]T[4]T[5]==T[0]T[1]T[2],而S[6] != T[6],T[6]==T[3]==T[0],所以S[6] != T[0],接下来比较S[7]和T[0]吧。如果用第二种表示方法写出的模式匹配函数,当比较到S[6] != T[6] 时,取next[6]= 3(表三),它只能表示:S[3]S[4]S[5]== T[3]T[4]T[5]==T[0]T[1]T[2],但不能确定T[6]与T[3]相不相等,所以,接下来比较S[6]和T[3];又不相等,取next[3]= 0,它表示S[3]S[4]S[5]== T[0]T[1]T[2],但不会确定T[3]与T[0]相不相等,即S[6]和T[0] 相不相等,所以接下来比较S[6]和T[0],确定它们不相等,然后才会比较S[7]和T[0]。是不是比用第一种表示方法写出的模式匹配函数多绕了几个弯。 为什么,在讲明第一种表示方法后,还要讲没有第一种表示方法好的第二种表示方法?原因是:最开始,我看严蔚敏的一个讲座,她给出的模式值表示方法是我这里的第二种表示方法,如图: 她说:“next 函数值的含义是:当出现S[i] !=T[j]时,下一次的比较应该在S[i]和T[next[j]] 之间进行。”虽简洁,但不明了,反复几遍也没明白为什么。而她给出的算法求出的模式值是我这里说的第一种表示方法next值,就是前面的get_nextval()函数。匹配算法也是有瑕疵的。于是我在这里发帖说她错了: http://community.csdn.net/Expert/topic/4413/4413398.xml?temp=.2027246 现在看来,她没有错,不过有张冠李戴之嫌。我不知道,是否有人第一次学到这里,不参考其他资料和明白人讲解的情况下,就能搞懂这个算法(我的意思是不仅是算法的大致思想,而是为什么定义和例子中next[j]=k(0≤k<j),而算法中next[j]=k(-1≤k<j))。凭良心说:光看这个讲座,我就对这个教受十分敬佩,不仅讲课讲得好,声音悦耳,而且这门课讲得层次分明,恰到好处。在KMP这个问题上出了点小差错,可能是编书的时候,在这本书上抄下了例子,在那本书上抄下了算法,结果不怎么对得上号。因为我没找到原书,而据有的网友说,书上已不是这样,也许吧。说起来,教授们研究的问题比这个高深不知多少倍,哪有时间推演这个小算法呢。总之,瑕不掩玉。 书归正传,下面给出我写的求第二种表示方法表示的模式值的函数,为了从S的任何位置开始匹配T,“当出现S[i] !=T[j]时,下一次的比较应该在S[i]和T[next[j]] 之间进行。” 定义next[0]=0 。 void myget_nextval(const char *T, int next[]) { // 求模式串T的next函数值(第二种表示方法)并存入数组 next。 int j = 1, k = 0; next[0] = 0; while ( T[j] != '\0' ) { if(T[j] == T[k]) { next[j] = k; ++j; ++k; } else if(T[j] != T[0]) { next[j] = k; ++j; k=0; } else { next[j] = k; ++j; k=1; } }//while for(int i=0;i<j;i++) { cout<<next[i]; } cout<<endl; }// myget_nextval 下面是模式值使用第二种表示方法的匹配函数(next[0]=0) int my_KMP(char *S, char *T, int pos) { int i = pos, j = 0;//pos(S 的下标0≤pos<StrLength(S)) while ( S[i] != '\0' && T[j] != '\0' ) { if (S[i] == T[j] ) { ++i; ++j; // 继续比较后继字符 } else // a b a b c a a b c // 0 0 0 1 2 0 1 1 2 { //-1 0 -1 0 2 -1 1 0 2 i++; j = next[j]; /*当出现S[i] !=T[j]时, 下一次的比较应该在S[i]和T[next[j]] 之间进行。要求next[0]=0。 在这两个简单示范函数间使用全局数组next[]传值。*/ } }//while if ( T[j] == '\0' ) return (i-j); // 匹配成功 else return -1; } // my_KMP .后话--KMP的历史 [这段话是抄的] Cook于1970年证明的一个理论得到,任何一个可以使用被称为下推自动机的计算机抽象模型来解决的问题,也可以使用一个实际的计算机(更精确的说,使用一个随机存取机)在与问题规模对应的时间内解决。特别地,这个理论暗示存在着一个算法可以在大约m+n的时间内解决模式匹配问题,这里m和n分别是存储文本和模式串数组的最大索引。Knuth 和Pratt努力地重建了 Cook的证明,由此创建了这个模式匹配算法。大概是同一时间,Morris在考虑设计一个文本编辑器的实际问题的过程中创建了差不多是同样的算法。这里可以看到并不是所有的算法都是“灵光一现”中被发现的,而理论化的计算机科学确实在一些时候会应用到实际的应用中。 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lin_bei/archive/2006/09/20/1252686.aspx

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宣布无人罪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值