KMP(三) 字符串快速匹配示例


概述:本文主要讲解KMP实现字符串快速查找的一个Demo;不了解KMP的同学可以参考:
KMP(一) 模式匹配算法推导 --《部分匹配表》
KMP(二) 模式匹配算法实现
KMP(三) 字符串快速匹配示例
字符串快速匹配Demo下载


一. 目的效果:

QQ20170905-155723-HD.gif

对,就是这样一个类似于网页检索的效果;实现起来也比较简单;
Talk is cheap ,Show me your code!

二. 单纯KMP的缺陷:

先来温习下KMP(二) 模式匹配算法实现当中的实现方案:

#pragma mark - getNext
-(NSArray<NSNumber *> *)getNextWithString:(NSString *)string{
    NSMutableArray<NSNumber *> * next = [NSMutableArray arrayWithCapacity:string.length];
    next[0] = @(-1);//初始化
    int j= 0,k= -1; //j:记录当前下标; k记录当前位的next

    while (j <  string.length - 1) {
        if (k== -1 || [string characterAtIndex:j] == [string characterAtIndex:k]) { // 比较当前(j)字符与当期位next处字符是否相等
            next[++j] = @(++k); // 移动下标,并求解下一位的next;
        }else{
            k = [next[k] intValue]; // 回溯当前位的next
        }
    }
    return next;
}
-(NSArray<NSString *> *)indexKMPwithParentString:(NSString *)pString andSubstring:(NSString *)sString{
    NSArray *next = [self getNextWithString:sString]; // 计算next数组
    int index_s = 0 ,index_p = 0;    //标记 pString 和 sString 的当前比对位置
    NSMutableArray<NSString *> *ranges = [NSMutableArray array]; //用于保存匹配结果的range位置

    while ((index_p < pString.length) && (index_s < sString.length)) { // 一直比对至两个字符串结尾
        if ([pString characterAtIndex:index_p] == [sString characterAtIndex:index_s]) { // 当前比对位置的字符相等,则移动P和S继续下一位比对
            if ((index_s == sString.length - 1) && (index_p != pString.length -1)) { // 字串S比对至末尾,但是主串P未到末尾,即是说字串匹配成功,但是尚需确定主串的后续位置是否能匹配,故继续比对
                index_s = 0; //将字串S当前比对位置移至起始位置
                [ranges addObject:NSStringFromRange(NSMakeRange(index_p - sString.length + 1 , sString.length))]; //保存本次匹配的位置结果
                continue; // 完成一次匹配,跳出本次循环 index_p 不再移动
            }
            index_p ++; // 向后移动P的当前位置
            index_s ++; // 向后移动S的当前位置
        }else{  //当前比对位置的字符不相等,则保持主串P位置不回溯,根据next数组回溯字串S
            if (index_s != 0) { //特别处理 next[0] = "-1"的情况
                index_s = [next[index_s] intValue]; // 根据next回溯当前字串S的比对位置
            }else{
                 index_p ++; //如果起始位置不匹配,则直接移动主串P
            }
        }
    }
    return ranges;
}

当目标子串仅有一个字符时候,显然-(NSArray<NSString *> *)indexKMPwithParentString: andSubstring:方法存在缺陷,效率低于朴素查找方法;So,我们要做下改进,当字串仅有一个字符时,采用朴素算法,当然,也顺便对上节中的代码稍加整理规范;

三.字符串快速匹配完整实现:

  • 首先制作UI界面,一个Label一个TextField 就略过了,给TextField添加一个监听:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldTextDidChangeNotification) name:UITextFieldTextDidChangeNotification object:nil];
  • 实现TextFieldTextDidChangeNotification方法:
#pragma mark - UITextFieldTextDidChangeNotification,AttributedString相关
-(void)textFieldTextDidChangeNotification{
    //    重置字体背景色
    [_attString setAttributes:@{NSBackgroundColorAttributeName:[UIColor whiteColor]} range:NSMakeRange(0, _label.text.length)];
    _label.attributedText = _attString;
    _ranges = [self indexKMPwithParentString:_label.text andSubstring:_tf.text];
    //    标记匹配的字体背景色
    if (_ranges) {
        for (NSString *string  in _ranges) {
            [_attString setAttributes:@{NSBackgroundColorAttributeName:[UIColor yellowColor]} range:NSRangeFromString(string)];
            _label.attributedText = _attString;
        }
    }
}

#pragma mark - indexKMP
-(NSArray<NSString *> * _Nullable)indexKMPwithParentString:(NSString *)pString andSubstring:(NSString *)sString{
    NSArray<NSString *> *ranges = [NSArray array];
    if (sString.length > 1) { //检索目标字符串为多个字符
        ranges =  [[self indexKMPwithParentString:pString andMultipleChar:sString] copy];
    }else{ //检索目标字符串为单个字符
        ranges = [[self indexKMPwithParentString:pString andSingleChar:sString] copy];
    }
    return ranges;
}

  • next数组的计算:
#pragma mark - getNext
-(NSArray<NSNumber *> *)getNextWithString:(NSString *)string{
    NSMutableArray<NSNumber *> * next = [NSMutableArray arrayWithCapacity:string.length];
    next[0] = @(-1);//初始化
    int j= 0,k= -1; //j:记录当前下标; k记录当前位的next

    while (j <  string.length - 1) {
        if (k== -1 || [string characterAtIndex:j] == [string characterAtIndex:k]) { // 比较当前(j)字符与当期位next处字符是否相等
            next[++j] = @(++k); // 移动下标,并求解下一位的next;
        }else{
            k = [next[k] intValue]; // 回溯当前位的next
        }
    }
    return next;
}

  • 检索匹配的实现:
  1. 用于字串不是单个字符的情况,KMP
#pragma mark - 用于字串不是单个字符的情况
-(NSArray<NSString *> * _Nullable)indexKMPwithParentString:(NSString *)pString andMultipleChar:(NSString *)sString{
    
    NSUInteger sLength = sString.length;
    NSUInteger pLength = pString.length;
    if (sLength < 2 || pLength == 0) return nil;
    NSArray *next = [self getNextWithString:sString]; // 计算next数组
    int index_s = 0 ,index_p = 0;    //标记 pString 和 sString 的当前比对位置
    
    NSMutableArray<NSString *> *ranges = [NSMutableArray array]; //用于保存匹配结果的range位置
    while ((index_p < pLength) && (index_s < sLength)) { // 一直比对至两个字符串结尾
        if ([pString characterAtIndex:index_p] == [sString characterAtIndex:index_s]) { // 当前比对位置的字符相等,则移动P和S继续下一位比对
            if ((index_s == sLength - 1) && (index_p != pLength -1)) { // 字串S比对至末尾,但是主串P未到末尾,即是说字串匹配成功,但是尚需确定主串的后续位置是否能匹配,故继续比对
                index_s = 0; //将字串S当前比对位置移至起始位置
                [ranges addObject:NSStringFromRange(NSMakeRange(index_p - sString.length + 1 , sString.length))]; //保存本次匹配的位置结果
                continue; // 完成一次匹配,跳出本次循环 index_p 不再移动
            }
            index_p ++; // 向后移动P的当前位置
            index_s ++; // 向后移动S的当前位置
        }else{  //当前比对位置的字符不相等,则保持主串P位置不回溯,根据next数组回溯字串S
            if (index_s != 0) { //特别处理 next[0] = "-1"的情况
                index_s = [next[index_s] intValue];
            }else{
                index_p ++;
            }
        }
    }
    return ranges.count ? ranges:nil;    
}

2.用于字串是单个字符的情况,采用朴素匹配效率更高

-(NSArray<NSString *> * _Nullable )indexKMPwithParentString:(NSString *)pString andSingleChar:(NSString *)singleChar{
    if (singleChar.length == 0 || pString.length == 0) return nil;
    int i = 0;
    NSMutableArray<NSString *>* ranges = [NSMutableArray array];
    unichar singchar = [singleChar characterAtIndex:0];
    while (i < pString.length) {
        if (singchar == [pString characterAtIndex:i]) {
            [ranges addObject: NSStringFromRange(NSMakeRange(i, 1))];
        }
        i ++;
    }
    return ranges.count ? ranges:nil;
}

代码中用了大量的循环,应当注意两点:
1.不必要的计算不要引入循环体内;
2.实际应用中可考虑开辟子线程;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容