程序員的算法課(12)-使用通配符*,?等來查找字符串

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接: https://blog.csdn.net/m0_37609579/article/details/100026156

1、前言

相信你們都用過文本編輯器(EditPlus,notepad++,sublime..)、Word或者開發IDE工具(IDEA,Eclipse..);甚至於你應該也寫過很多SQL語句;你也用過百度、谷歌(怎麼上谷歌我也不會,不要問我)搜過你要的內容。裏面都會有個很重要的功能,那就是搜索,而搜索的方式中,對於通配符搜索我想你們也不會陌生。html

【百度百科】通配符是一種特殊語句,主要有星號(*)問號(?),用來模糊搜索文件。當查找文件夾時,可使用它來代替一個或多個真正字符;當不知道真正字符或者懶得輸入完整名字時,經常使用通配符代替一個或多個真正的字符,其中,「?」能夠代替一個字符,而「*」能夠代替零個或多個字符。 java

2、通配符問題描述

 題目描述:
    Str1中可能包含的字符:任意字符串。(注意:字符串中能夠帶?*這樣的通配符)
    Str2中可能包含的字符:任意字符。其中,'?'表示匹配任意一個字符,'*'表示匹配任意字符0或者屢次。
    給出這樣兩個字符串,判斷Str2是不是Str1的子串,若是是輸出第一個匹配到的子串,若是不是,輸出"不是子串"。面試

3、算法實現

1.正則表達式

import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringDemo {
    //使用正則表達式 通配符匹配字符串
    public static boolean isMatching(String src,String des){
        String des1 = des.replace("*", "\\w*");
        des1 = des1.replace("?", "\\w{1}");
        Pattern p = Pattern.compile(des1);
        Matcher m = p.matcher(src);
        return m.matches();
    }
}

 

 

這種方法是熱身的,看上去也是最簡單的,代碼最少,也比較好理解,但在面試的時候可能不容許使用正則,他想讓你分析下正則表達式的內部實現,那就尷尬了;並且,這個方法是有缺陷的,若是咱們要查找的字符串中包含?*這樣的字符,那就沒法匹配,因此咱們繼續看下面的方法。正則表達式

2.嚴格匹配(暴力匹配)

    對於'?'的處理,只要在匹配的時候將代碼由:if(str1[i]==str2[j]) 改成 if(str1[i]==str2[j] || str2[j]=='?')便可。
    對於'*'的處理,能夠將str2根據其中的'*'分爲若干個片斷,而後依次在str1中分別匹配這幾個片斷便可,並且對於這幾個片斷分別匹配,若是第k個片斷在str1中匹配不到,後面也能夠結束了。這裏舉例說明一下:對於str1="Oh year.Totay is weekend!",str2=*ye*a*e*",實際上就是在str1中匹配"ye","a","e"這三個片斷。算法

    public static boolean isMatching2(String s, String p) {
        int i = 0;
        int j = 0;
        int starIndex = -1;
        int iIndex = -1;

        while (i < s.length()) {
            if (j < p.length() && (p.charAt(j) == '?' || p.charAt(j) == s.charAt(i))) {
                ++i;
                ++j;
            } else if (j < p.length() && p.charAt(j) == '*') {
                starIndex = j;
                iIndex = i;
                j++;//'*' can match 0 or above 0 characters
            } else if (starIndex != -1) {
                //such as "abggggb","*b"
                //so every time matching starts form the fisrt index of *
                //can avoid the case above
                j = starIndex + 1;
                i = iIndex+1;
                iIndex++;
            } else {
                return false;
            }
        }

        while (j < p.length() && p.charAt(j) == '*') {
            ++j;
        }

        return j == p.length();
    }

 

3.KMP算法

  KMP自己不復雜,但網上絕大部分的文章把它講混亂了。編程

【百度百科】KMP算法是一種改進的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,所以人們稱它爲克努特莫里斯普拉特操做(簡稱KMP算法)。KMP算法的核心是利用匹配失敗後的信息,儘可能減小模式串與主串的匹配次數以達到快速匹配的目的。具體實現就是經過一個next()函數實現,函數自己包含了模式串的局部匹配信息。KMP算法的時間複雜度O(m+n)。微信

算法說明架構

設主串(下文中咱們稱做T)爲:a b a c a a b a c a b a c a b a a b b併發

模式串(下文中咱們稱做W)爲:a b a c a b編輯器

用暴力算法匹配字符串過程當中,咱們會把T[0] 跟 W[0] 匹配,若是相同則匹配下一個字符,直到出現不相同的狀況,此時咱們會丟棄前面的匹配信息,而後把T[1] 跟 W[0]匹配,循環進行,直到主串結束,或者出現匹配成功的狀況。這種丟棄前面的匹配信息的方法,極大地下降了匹配效率。

而在KMP算法中,對於每個模式串咱們會事先計算出模式串的內部匹配信息,在匹配失敗時最大的移動模式串,以減小匹配次數。

好比,在簡單的一次匹配失敗後,咱們會想將模式串儘可能的右移和主串進行匹配。右移的距離在KMP算法中是如此計算的:在已經匹配的模式串子串中,找出最長的相同的前綴後綴,而後移動使它們重疊。

在第一次匹配過程當中

T: a b a c a a b a c a b a c a b a a b b

W: a b a c a b

在T[5]與W[5]出現了不匹配,而T[0]~T[4]是匹配的,其中T[0]~T[4]就是上文中說的已經匹配的模式串子串,移動找出最長的相同的前綴和後綴並使他們重疊:

T: a b a c aa b a c a b a c a b a a b b

W: a b a c a b

而後在從上次匹配失敗的地方進行匹配,這樣就減小了匹配次數,增長了效率。

    static boolean isEmpty(final String str) {
        return str == null || str.isEmpty();
    }

    // str may contain '?'
    static int[] getNextArray(final String str) {
        if (isEmpty(str)) {
            return null;
        }
        int[] next = new int[str.length()];
        int k = -1;
        int j = 0;
        next[0] = -1;
        while (j < str.length() - 1) {
            if (k == -1 || str.charAt(k) == str.charAt(j) || str.charAt(k) == '?' || str.charAt(j) == '?') {
                k++;
                j++;
                next[j] = k;
            } else {
                k = next[k];
            }
        }
        return next;
    }

    // pattern may contain '?'
    static int kmpFind(final String str, final String pattern, int start) {
        if (isEmpty(str)) {
            return -1;
        }
        int[] next = getNextArray(pattern);
        if (next == null) {
            return -1;
        }
        int i = start;
        while (i < str.length()) {
            int j = 0;
            while (j < pattern.length()) {
                if (str.charAt(i) == pattern.charAt(j) || pattern.charAt(j) == '?') {
                    i++;
                    j++;
                } else {
                    break;
                }
            }
            i -= j;
            if (j == pattern.length()) {
                return i;
            }
            int move = j - next[j];
            i += move;
        }
        return -1;
    }

 

 

4.KMP算法擴展

    // pattern may contain '*' and '?'
    // pattern按*分割後,子串裏可能含有?,無法用String.find, 因此針對含?的字符串,
    // 結合KMP算法,實現了find函數,以後再將pattern按*分割,
    // 在輸入字符串中按順序查找子串,已實現find含有*和?的字符串
    static int find(final String str, final String pattern) {
        if (isEmpty(str)) {
            return -1;
        }
        if (isEmpty(pattern)) {
            return -1;
        }
        String[] items = pattern.split("\\*");
        int i = 0;
        int ret = -1;
        for (String s : items) {
            int index = kmpFind(str, s, i);
            if (index < 0) {
                return -1;
            }
            if (i == 0) {
                ret = index;
            }
            i = index + s.length();
        }
        return ret;
    }

 

 


個人微信公衆號:架構真經(id:gentoo666),分享Java乾貨,高併發編程,熱門技術教程,微服務及分佈式技術,架構設計,區塊鏈技術,人工智能,大數據,Java面試題,以及前沿熱門資訊等。每日更新哦!

參考資料:

  1. https://www.cnblogs.com/pangxiaodong/archive/2011/09/07/2169588.html
  2. https://bbs.csdn.net/topics/390941031
  3. https://www.iteye.com/blog/sunlujing-1706919
  4. https://www.jianshu.com/p/15865bac6a1b
  5. http://www.javashuo.com/article/p-kcxmpgmn-ge.html
  6. https://baike.baidu.com/item/kmp%E7%AE%97%E6%B3%95/10951804?fr=aladdin
相關文章
相關標籤/搜索