美文网首页
KMP(三) 字符串快速匹配示例

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

作者: hehtao | 来源:发表于2017-09-05 16:32 被阅读150次

    概述:本文主要讲解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.实际应用中可考虑开辟子线程;

    相关文章

      网友评论

          本文标题:KMP(三) 字符串快速匹配示例

          本文链接:https://www.haomeiwen.com/subject/vpoujxtx.html