关于NSString的算法题目

有如下业务:

富文本中带有自定义的表情图片的转义字符(哈哈我抓到你了/大笑表情,太开心了/邪恶的表情),这段字符串中“/大笑表情”及“/邪恶的表情”分别表示一个表情图片。程序中有一个表情转义字符和表情图片名称对照的json文件。现在需要实现一个算法,可以将富文本中的转义字符通过查找json文件找出来。

思路:

1、建立树结构,将转义字符和表情图片名称对应起来,可以存储上一次查找的结果;

2、从“/”开始截取字符串,依次进行“/A”在树结构中查找,查找方法如二分法查找,查找到后记录全部吻合的查询结果(如放在数组中),如果没有查询到则返回无结果;

3、在符合查询结果的数组中继续用“/AB”进行查找,保存吻合的查询结果到数组中;

4、依次类推,最终找到符合结果的转义字符对应的图片名称;

5、字符查找中可以使用字符串搜索查找相关的算法进行(如KMP算法)。


单模式字符串匹配

1. 朴素算法

朴素算法的问题在于不够智能,有些位置明显没有必要进行比较操作,但这个算法无法区分出来,还是继续比较,浪费了资源。

2. KMP算法

在KMP算法中,引入了前缀函数的概念,从而可以更加精确的知道:当不匹配发生时,应该跳过多少个字符。下面介绍前缀函数。

字符串A = "abcde" B = "ab"。 那么就称字符串B为A的前缀,记为B ⊏ A。同理可知 C = "e","de" 等都是 A 的后缀,以为C ⊐ A。


这里模式串 P = “ababaca”,在匹配了 q=5 个字符后失配,因此,下一步就是要考虑将P向右移多少位进行新的一轮匹配检测。朴素算法中,直接将P右移1位,也就是将P的首字符'a'去和目标串的'b'字符进行检测,这明显是多余的。通过我们肉眼的观察,可以很简单的知道应该将模式串P右移到下图'a3'处再开始新一轮的检测,直接跳过肯定不匹配的字符'b',那么我们“肉眼”观察的这一结果怎么把它用语言表示出来呢?


我们的观察过程是这样的:

1. P的前缀"ab"中'a' != 'b',又因该前缀已经匹配了T中对应的"ab",因此,该前缀的字符'a1'肯定不会和T中对应的字串"ab"中的'b'匹配,也就是将P向右滑动一个位移是无意义的。
2. 接下来考察P的前缀"aba",发现该前缀自身的前缀'a1'与自身后缀'a2'相等,"a1 b a2" 已经匹配了T中的"a b a3",因此有 'a2' == 'a3', 故得到 'a1' == 'a3'......
3. 利用此思想,可推知在已经匹配 q=5 个字符的情况下,将P向右移 当且仅当 2个位移时,才能满足既没有冗余(如把'a'去和'b'比较),又不会丢失(如把'a1' 直接与 'a4' 开始比较,则丢失了与'a3'的比较)。
4. 而前缀函数就是这样一种函数,它决定了q与位移的一一对应关系,通过它就可以间接地求得位移s。
好后缀算法

这样的观察过程并不具有一般性,下面是《算法导论》中对前缀函数的形式化说明:

已知一个模式P[1. . m],模式P的前缀函数是函数π{1,2,. . . , m}->{0,1, 2,. . . ,m-1}并满足

π[q]=max{k:k<q 且Pk⊐ Pq}

即π[q]是Pq的真后缀P的最长前缀的长度(此是《算法导论》中原话,但不是很好理解,其实就是Pq中即是自己的真后缀,又是自己最长前缀的字符串的最大长度)。下面举例说明(模式P=ababababca)

i=1时,a真后缀为空;i=2时,ab真后缀为b,不是自己的前缀;i=3时,aba真后缀为a, ab,且a和ab都是aba的前缀,ab最长,故为2;。。。

KMP算法中,如果q+1时发生不匹配,则可以向前移动q-π[q]位。

#include <iostream>


using namespace std;

/*

 when searching a pattern in a string, and mismatch happened, we can skip more chars, instead of going through one by one;

 the skip rule is that:

 1. if position p mismatched, we need consider the chars in 0- (p-1);

 2. whether [0,k-1](prefix substring) matched with [p-k,p-1](suffix substring),if matched, we can align the pattern to p-k;and do comparation from p again.

 below function is get the k for different p, more information refer to comments inline;

 */

void get_skippattern(char *pattern, int* next, int len)

{

    int pos = 2;

    int subStrIndex = 0; //valid prefix candidate substring index;

    next[0] = -1; // when 1st char mismatched, always move 1 (p=0, k=-1);

    next[1] = 0 // when 2nd mismatched, always move 1(p=1, k=0);in fact, if the 2nd char is same as 1st char, we can move 2

    while(pos<len)

    {

        if(pattern[pos - 1] == pattern[subStrIndex]) //one char matched, then continue to match more,

        {

            subStrIndex++;            //prefix substring move ahead;

            next[pos] = subStrIndex;//for current position, the k is got;

            pos++;                    //current pos move ahead;

        }

        else if(subStrIndex>0)    //one substring found, but in the new pos, mismatched;

        {

            subStrIndex = next[subStrIndex]; //then we need fall back subStrIndex to value that still can be matched;

        }

        else

        {

            next[pos] = 0;

            pos++;

        }

    }

    

    for(int i =0;i<len;i++)

        cout<<next[i]<<"  ";

}


int KMP_search(char *src, int slen, char *pattern, int plen)

{

    int* next = (int *)malloc(sizeof(int)*slen);

    get_skippattern(pattern,next,plen);

    

    int indexInSrc = 0;

    int offset = 0;

    while((indexInSrc+offset)<slen)

    {

        if(pattern[offset] == src[indexInSrc+offset])

        {

            if(offset == (plen-1))

                return indexInSrc;

            offset++;

        }else

        {

            indexInSrc += offset-next[offset];

            if(next[offset]>-1)

                offset = next[offset];

            else

                offset = 0;

        }

    }

    return slen;

}


int main (int argc, char ** argv)

{

    //char *pat = "ABCDABDEF";

    //char *src = "ABC ABCDAB ABCDABCDABDEF";

    char *pat="ABABETTABABABYUABCD";

    char *src = "ABCDEABCDGABCDETTABCDFABCDETTABCDATYUABCD";

    int index = KMP_search(src,strlen(src),pat,strlen(pat));

    cout<<"found pattern in "<<index<<endl;

    return 0;

}

3. BM算法

BM算法的特殊之处在于BM是右向左匹配,同时结合坏字符和好后缀两个规则使得移动距离最大。下面分别介绍坏字符和好后缀规则:

如果程序匹配了一个好后缀, 并且在模式中还有另外一个相同的后缀, 那 

把下一个后缀移动到当前后缀位置。好后缀算法有两种情况: 

Case1:模式串中有子串和好后缀安全匹配,则将最靠右的那个子串移动到好后缀的位置。继续进行匹配。 

Case2:如果不存在和好后缀完全匹配的子串,则在好后缀中找到具有如下特征的最长子串,使得P[m-s…m]=P[0…s]。说不清楚的看图。

给一些具体的例子 

当出现一个坏字符时, BM算法向右移动模式串, 让模式串中最靠右的对应字符与坏字符相对,然后继续匹配。坏字符算法也有两种情况。 

Case1:模式串中有对应的坏字符时,见图。 

Case2:模式串中不存在坏字符。见图。

BM算法的移动规则是: 

将概述中的++j,换成j+=MAX(shift(好后缀),shift(坏字符)),即BM算法是每次向右移动模式串的距离是,按照好后缀算法和坏字符算法计算得到的最大值。 

shift(好后缀)和shift(坏字符)通过模式串的预处理数组的简单计算得到。好后缀算法的预处理数组是bmGs[],坏字符算法的预处理数组是BmBc[]。 

下面先解释这两个数组的意义:

BmBc 的定义:

1、 字符在模式串中没有出现:,如模式串中没有字符p,则BmBc[‘p’] = strlen(模式串)。 

2、 字符在模式串中有出现。如下图,BmBc[‘k’]表示字符k在模式串中最后一次出现的位置,距离模式串串尾的长度。

如果只考虑坏字符,应该移动多少呢?下面的图里有3个例子:

示例1中,在b和c比较时发生了不match,这时,我们的BmBc[‘c’] = 3,这时我们应该移动多少呢,移动-1,如何计算的呢? BmBc[‘c’] – strlen(pat) +1 + i (index of pattern string)

实例2中,b和a发生不match,这时,BmBc[‘a’] = 6, 应该移动6-7+1+2 = 2;

实例3中,b和y发生不match,这时,BmBc[‘y’] = 7,应该移动7-7+1+1 = 2;

对于这里BmBc的定义,和shift的值的计算是很难让人理解的,我们是不是可以简单一点定义BmBc[char] 表示char在pattern中最后出现的位置,如果不出现为pattern的长度,i是不match的index,shift的距离就是BmBc[char]-i。

还有就是,在实例1中,我们真的要去移动-1吗,其实没有必要了,如果只用坏字符,你可以想想怎么做;但如果考虑上好后缀就不用额外考虑了。

为了实现好后缀规则,需要定义一个数组suffix[],其中suffix[i] = s 表示以i为起点(包含i,从右往左匹配),与模式串后缀匹配的最大长度,如下图所示,用公式可以描述:满足P[i-s, i] == P[m-s, m]的最大长度s。

suffix[m-1]=m;

for (i=m-2i>=0--i)

{

    q=i;

   while(q>=0&&P[q]==P[m-1-i+q])

        --q;

    suffix[i]=i-q;

}

有了suffix[i],如何计算BmGc? 

bmGs的定义(BmGs数组的下标是数字,表示字符在模式串中位置), BmGs数组的定义,分三种情况:

1、模式串中有子串匹配上好后缀


在这个视角图1中,在i处发生不匹配,从i开始从右向左搜索子字符串,在视图2中试图找到不匹配的字符和子串匹配位置的关系。视图2中i开始的子串与后缀匹配,那可以知道后缀的长度就是Suffix[i],再往左移动一下就是不匹配的位置了,而这时应该移动的距离是m-1-i,也就是式子bmGs[m-1-suff[i]] = m- 1 – i; 

模式串中没有子串匹配上好后缀,但找到一个最大前缀


在这种情况下,空白位置发生不匹配时,其好后缀都是最前面的两个,那么其移动的距离其实跟不匹配的位置 j 没有关系,只与最好前缀的位置i有关,所以,bmGs[j] = m- 1 – i; 


模式串中没有子串匹配上好后缀,但找不到一个最大前缀


没有任何子串匹配的时候,那就移动模式串的长度。 

举例如下:


实现代码如下:


void preBmGs(char *x,int m,int bmGs[]) {

    int i, j, suff[XSIZE];

    suffixes(x, m, suff);

    for (i =0; i < m; ++i)

        bmGs[i] = m;

    j =0;

    for (i = m -1; i >= 0; --i)

        if (suff[i] == i +1)

            for (; j < m -1 - i; ++j)

                if (bmGs[j] == m)

                    bmGs[j] = m -1 - i;

    for (i =0; i <= m - 2; ++i)

        bmGs[m -1 - suff[i]] = m -1 - i;

}


下面来完整实现一下BM算法吧:

#include<stdlib.h>

#include<stdio.h>

#include<string.h>



constint CHAR_COUNT =26;// only lower case ASCII char

void calculateBmBc(constchar *s,int len, int *BmBc)

{

    int i=0;

    for(i =0; i<CHAR_COUNT;i++)

    {

        BmBc[i] = len;

    }

    for(i =0;i<len;i++)

    {

        BmBc[s[i]-'a'] = i;

    }

}

void calculateSuffix(constchar *s,int len,int *suffix)

{

    int i = len -1;

    int j =0;

    suffix[len-1] = len;

    for(;i>=0;i--)

    {

        j =0;

        while(j<(len-1) && s[i-j] == s[len-1-j])

            j++;

        suffix[i] = j;

    }

}

void calculateBmGs(constchar *s,int len,int *BmGs)

{

    int* suffix = (int *)malloc(sizeof(int)*len);//new int[len];

    int i =0;

    int j =0;

    calculateSuffix(s,len,suffix);

    for(i=0;i<len;i++)// init the array, and also cover case 3

    {

        BmGs[i] = len;

    }

    for(i=len-1;i>=0;i--)

    {

        if(suffix[i] == i+1)// prefix of the string matched with suffix, case 2

        {

            for(j =0;j<len-1-i;j++)

            {

                if(BmGs[j] == len)

                    BmGs[j] = len -1 - i;

            }

        }

    }

    for(i =0;i<=len-2;i++)// case 1;

    {

        BmGs[len-1-suffix[i]] = len-1-i;

    }

    free(suffix);

}

int BMSearch(constchar *src,int srclen,constchar *pattern,int patlen)

{

    int i=0;

    int * BmGs = (int *)malloc(sizeof(int)*(patlen));

    int * BmBc = (int *)malloc(sizeof(int)*(CHAR_COUNT));

    calculateBmBc(pattern, patlen, BmBc);

    calculateBmGs(pattern,patlen, BmGs);

    for(i =0;i<(srclen-patlen);)

    {

        int j = patlen-1;

        while(j>=0 && src[i+j] == pattern[j]) j--;

        if(j <0)

            return i;

        else

            i += BmGs[j]>(j-BmBc[j])?BmGs[j]:(j-BmBc[j]);

    }

    free(BmGs);

    free(BmBc);

    return srclen;

}


int main(int argc,char **argv)

{

    int i =BMSearch("GCAGAGAG",8,"AGAG",4);

    printf("found pattern at %d",i);

    return0;

}



4、SUNDAY 算法描述:

字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore)。两个算法在最坏情况下均具有线性的查找时间。但是在实用上,KMP算法并不比最简单的c库函数strstr()快多少,而BM算法则往往比KMP算法快上3-5倍。但是BM算法还不是最快的算法,这里介绍一种比BM算法更快一些的查找算法。

例如我们要在"substring searching algorithm"查找"search",刚开始时,把子串与文本左边对齐:

substring searching algorithm

结果在第二个字符处发现不匹配,于是要把子串往后移动。但是该移动多少呢?这就是各种算法各显神通的地方了,最简单的做法是移动一个字符位置;KMP是利用已经匹配部分的信息来移动;BM算法是做反向比较,并根据已经匹配的部分来确定移动量。这里要介绍的方法是看紧跟在当前子串之后的那个字符(上图中的 'i')。

显然,不管移动多少,这个字符是肯定要参加下一步的比较的,也就是说,如果下一步匹配到了,这个字符必须在子串内。所以,可以移动子串,使子串中的最右边的这个字符与它对齐。现在子串'search'中并不存在'i',则说明可以直接跳过一大片,从'i'之后的那个字符开始作下一步的比较,如下图:

substring searching algorithm

比较的结果,第一个字符就不匹配,再看子串后面的那个字符,是'r',它在子串中出现在倒数第三位,于是把子串向前移动三位,使两个'r'对齐,如下:

substring searching algorithm

哈!这次匹配成功了!回顾整个过程,我们只移动了两次子串就找到了匹配位置,是不是很神啊?!可以证明,用这个算法,每一步的移动量都比BM算法要大,所以肯定比BM算法更快。

 

代码如下:


const char* sunday(const char* str, const char* subStr)

{

    const int maxSize=256;

    int next[maxSize];

    int strLen = strlen(str);

    int subLen = strlen(subStr);

    int i,j,pos;

    for(i=0;i<maxSize;i++)

    {

        next[i] = subLen+1;

    }

    for(i=0;i<subLen;i++)

    {

        next[ (unsigned char)subStr[i] ] = subLen-i;//计算子串中的字符到字符串结尾的\0之间的距离

    }

    pos=0;

    while(pos<=(strLen-subLen))

    {

        i=pos;

        for(j=0;j<subLen;j++,i++)

        {

            

            if(str[i] != subStr[j])

            {

                pos += next[ (unsigned char)str[pos+subLen] ];//向后移动

                break;

            }

            

        }

        if(j==subLen)//找到字串,返回

        {

            return str+pos;

        }

    }

    return NULL;

}



多模式字符串匹配

1. AC

2. Wu-Manber算法

 

参考:

1. http://blog.csdn.net/zdl1016/article/details/4654061

2. http://blog.csdn.net/iJuliet/article/details/4200771

3.http://quicksort.typepad.com/blog/2010/01/%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D%E7%9A%84sunday.html

4. http://hi.baidu.com/kmj0217/blog/item/6f837f2f3da097311e3089cb.html

5. http://www.cs.utexas.edu/users/moore/best-ideas/string-searching/index.html

6. http://blog.sina.com.cn/s/blog_6cf48afb0100n561.html

7. http://blog.csdn.net/sealyao/article/details/4568167

8. http://www.cnblogs.com/v-July-v/archive/2011/06/15/2084260.html


这里模式串 P = “ababaca”,在匹配了 q=5 个字符后失配,因此,下一步就是要考虑将P向右移多少位进行新的一轮匹配检测。朴素算法中,直接将P右移1位,也就是将P的首字符'a'去和目标串的'b'字符进行检测,这明显是多余的。通过我们肉眼的观察,可以很简单的知道应该将模式串P右移到下图'a3'处再开始新一轮的检测,直接跳过肯定不匹配的字符'b',那么我们“肉眼”观察的这一结果怎么把它用语言表示出来呢?


我们的观察过程是这样的:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值