数据结构--串

来源:互联网 发布:淘宝卖家开花呗条件 编辑:程序博客网 时间:2024/06/07 17:58

1、基本概念

串是由零个或多个字符组成的有限序列,又名字符串。
1.1 串大小的比较
给定两个串
s=”a0a1an
t=”b0b1bm
当满足以下条件之一,s<t
(1)n<m 且 ai=bi (i=0,1…n) 如s=”hap” ,t=”happy”
(2)存在某个值k<=min(m,n) 使得 ai=bi (i=0,1…k-1) 且 ak<bk 如 happen < happy

2、串的匹配

主串S=”goodgoogle” , 子串T=”google”
2.1 朴素的匹配匹配算法
子串匹配主串,由子串的第一个字符与主串的第一个字符开始比较,若相同,比较下一个。
如果完全相同,匹配成功。
如果有一个不同,用子串的第一个字符与主串的第二个字符比较。如此循环。时间复杂度为O(m*n)

public static int match(String s, String t){        if(s==null || t==null || s.length()<=0 || t.length()<=0 || s.length()<t.length()){            System.out.println("输入参数不正确");            return -1;        }        int i=0,j=0;        while(i<s.length() && j<t.length()){            if(s.charAt(i) == t.charAt(j)){ //如果相同,比较下一个                i++;                j++;            } else { //如果有一个不同,i回溯到匹配前加1的位置                i = i-j+1;                j=0;            }        }        if(j == t.length()){//如果匹配的子串长度与子串相同,则完成匹配            return i-j;        }        return -1;    }
2.2 KMP算法

朴素匹配算法会一直回溯主串,导致很多不必要的比较。KMP算法就是保证主串不回溯,从而提高效率。
预处理时间复杂度为O(m),匹配时间复杂度为O(n)

2.2.1 KMP算法的简单理解

主串s=”BBC ABCDAB ABCDABCDABDE”
子串t=”ABCDABD”

2.2.2 子串的预处理

首先需要知道前缀和后缀的概念
前缀:除了最后一个字符以外,一个字符串的全部头部组合;
后缀:除了第一个字符以外,一个字符串的全部尾部组合。
预处理就是获取前缀和后缀的最长的共有元素的长度
子串处理的步骤

“A”的前缀和后缀都为空集,共有元素的长度为0;

“AB”的前缀为[A],后缀为[B],共有元素的长度为0;

“ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

“ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1;

“ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

如果将上述的元素组成数组 [0, 0, 0, 0, 1, 2, 0],成为next数组
这里写图片描述
Max即是共有元素的最大长度

2.2.3 匹配步骤

a、开始匹配
这里写图片描述
子串的第一个与主串比较,如果不同则使用主串的后一个与子串比较

b、匹配到部分相同
这里写图片描述
如图,主串的第4个元素与子串匹配相同,直到第11个不同。现在相同的部分为ABCDAB。通过上面对子串的预处理可以知道,子串有公共的元素为AB,长度为2
现在已知的条件:

主串与子串相同的部分为ABCDAB
子串有相同的部分AB
所以再次比较时 子串的AB不再与主串的ABCDAB比较 直接使用子串第一个AB后的字符C与主串第二个AB后的字符 空字符 比较。因为AB是已知相同的。这就是KMP算法的核心原理了

这里写图片描述

c、失配时再次比较
这里写图片描述
上一次子串的字符C和主串的空字符不匹配,则使用子串的第一个字符进行比较。如此循环,知道完成
这里写图片描述

步骤b 匹配到部分相同,子串C与主串空字符比较时,跳过的步骤为匹配元素的长度6 - 相同部分AB的长度2 = 4

public class Main {    public static int[] getNext(String sub) {        int[] next = new int[sub.length()];        next[0] = -1;        int k = -1;        int j = 0;        while (j < sub.length()-1) {            //k表示前缀,j表示后缀            if (k == -1 || sub.charAt(j)==sub.charAt(k)) {                ++k;                ++j;                next[j] = k;            } else {                k = next[k];            }        }        return next;    }    public static int indexOf(String src, String ptn) {        int i = 0, j = 0;        int sLen = src.length();        int pLen = ptn.length();        int[] next = getNext(ptn);        System.out.println(Arrays.toString(next));        while (i < sLen && j < pLen) {            // 如果j = -1,或者当前字符匹配成功(src.charAt(i)==ptn.charAt(j)),都让i++,j++            if (j==-1 || src.charAt(i)==ptn.charAt(j)) {                i++;                j++;            } else {                // 如果j!=-1且当前字符匹配失败,则令i不变,j=next[j],即让pattern模式串右移j-next[j]个单位                j = next[j];            }        }        if (j == pLen)            return i - j;        return -1;    }    public static void main(String[] args) {        System.out.println(""+indexOf("BBC ABCDAB ABCDABCDABDE","ABCDABD"));    }}

2.2.4 Next 数组的优化

public static int[] getNext(String sub) {        int[] next = new int[sub.length()];        next[0] = -1;        int k = -1;        int j = 0;        while (j < sub.length()-1) {            //k表示前缀,j表示后缀            if (k == -1 || sub.charAt(j)==sub.charAt(k)) {                ++k;                ++j;                if(sub.charAt(j) != sub.charAt(k))                    next[j] = k;                else//如果有与以前字符相同的字符,则使用以前字符的标记                    next[j] = next[k];            } else {                k = next[k];            }        }        return next;    }

参考:
next数组的解释:http://www.cnblogs.com/fuck1/p/6059736.html
长篇大论:http://blog.csdn.net/v_july_v/article/details/7041827#
代码:http://www.jianshu.com/p/e2bd1ee482c3

原创粉丝点击