字串查找算法總結及MS的strstr源碼

http://www.cnblogs.com/ziwuge/archive/2011/12/09/2281455.htmlhtml

首先來講說字串的查找,即就是在一個指定的字串A中查找一個指定字串B出現的位置或者統計其餘B在A中出現的次數等等相關查找。
  MS本身提供了一個strstr函數原型:extern char *strstr(char *str1, char *str2);頭文件<string.h>。但也可不包含頭文件直接使用下面代碼: ios

複製代碼

char * __cdecl strstr (
        const char * str1,
        const char * str2
        )
{
        char *cp = (char *) str1;
        char *s1, *s2;
        if ( !*str2 )
            return((char *)str1);
        while (*cp)
        {
                s1 = cp;
                s2 = (char *) str2;
                while ( *s1 && *s2 && !(*s1-*s2) )
                        s1++, s2++;
                if (!*s2)
                        return(cp);
                cp++;
        }
        return(NULL);
}

複製代碼

 

  可直接將char 替換爲 wchar_t用於寬字符。不過在字串的查找中效率是最低。
  KMP算法 詳細介紹可參考下面:一、KMP算法   二、KMP算法詳解
  其實KMP算法可對裏面的數組而後會更優化一些。
  Sunday 是BM查找算法的變種,可是效率更好。下面摘自博客園園友Aga.J的文章:
  Sunday算法是一種比KMP和BM更加高效的匹配算法,它的思想跟BM算法類似,在匹配進行時,Sunday算法失敗時關注的是源字符串中當前參加匹配的長度爲M(模式串的長度)的最後一位字符的下一個字符。若是該字符不在模式串中出現,那麼就將直接跳過,即移動步長=模式串的長度+1,不然,則移動步長=模式串中最右端的該字符到末尾的距離加1。
  假設咱們要匹配」HEREISASIMPLEEXAMPLE」和」EXAMPLE」
      Text:HERE  IS  A  SIMPLE EXAMPLE(此處中間是一個空格,用兩個空格是爲了對齊.下同)
  Pattern:EXAMPLE算法

  咱們從源串的初始位置開始和模式串比較,發現第一個就不匹配了,這時候若是使用樸素算法,那麼咱們就只是將模式串右移一位,而使用KMP算法的話,則根據自身的模式數組來肯定移動的步長,使用Sunday算法時,咱們會比較源串對齊後的下一個字符,也就是text中IS後面的空格,由於不管咱們用什麼方法去移動模式串,這個字符老是要參與下一次匹配(假設咱們只移動1位或者小於模式串長度位,這個空格所在的位必定要參與匹配,否則咱們就可能遺漏掉可能的匹配,而假設在小於模式串長度的位的移動中沒有匹配,那麼下一個起始匹配點就是空格的所在位)。
  既然知道該爲必定要匹配,那麼就和模式串進行比較,若是模式串中不存在該字符(這裏是空格),那麼就直接跳到空格字符的下一個字符進行匹配(這時候從E和A開始匹配)
      Text:HERE  IS  A  SIMPLE  EXAMPLE
  Pattern:EXAMPLE
  這時候再次進行匹配的判斷,發現第一個字符又不匹配,因此和上面的方法同樣,咱們直接看對齊後的下一個字符E,這時候拿E從右往左和模式串比較,若是模式串中存在E,那麼就移動模式串知道兩個E對齊,這樣獲得(這種從後往前,從右往左的匹配思想是從BM算法借鑑過來的,從後往前比較的優勢是一旦遇到不匹配的時候,能夠跳躍的距離更大,由於後面都不匹配了,前面再匹配都沒有用了,因此這裏拿E從模式串中從右往左匹配,找到第一個和E匹配的位置—詳細看BM算法的分析!)
      Text:HERE  IS  A  SIMPLE  EXAMPLE
  Pattern:EXAMPLE
  接下來仍是從模式串的首位和源串的新起始位開始比較,發現,又沒法匹配,而再判斷對齊的後一位,此次仍是將整個模式串移動到新的不匹配位空格以後,最後完成匹配。
上述例子是從網上摘錄下來的,不是很典型,由於在第二次匹配的時候,恰好源串對齊後的下一位就和模式串的最後一位匹配,因此體現不出Sunday算法的模式串移動的過程。
 數組

複製代碼

/*
* 算法分析:
* 1 從第一個字符開始,對pattern和source逐個字符比較
* 2 若是出現匹配失敗,則檢查source的在pattern末尾位置的後一個字符是否和pattenr的某個一個字符相等
*    若是是,則對其到那個相等字符(從右往左對齊),從新執行
*    若是不是,則將pattern和source的比較初始位設置在source的後後個字符,再執行
* 3 若是匹配,則成功
*/
#include<iostream>
using namespace std;

// 調用前先檢測源串是否超過長度,函數返回 以源串的sourceStartPos爲比較的起始點,第一個和模式串不匹配的字符的位置,
int compare(char* source, char* pattern, int sourceStartPos, int patternLength)
{
    int i = 0;
    for(; ((i < patternLength) && (source[sourceStartPos+i] == pattern[i])); i++)
    {
        ;
    }
    return i;    // 返回pattern中沒法匹配的元素的位置i,pattern[i]
}

bool sundayMatch(char* source, char* pattern, int sourceLength, int patternLength)
{
    int startPos = 0;            // 源串的起始比較點
    int failMatchPos = 0;        // 失效點
    int j = 0;
    while( (startPos+patternLength -1) <= sourceLength) // 源串中剩下的子串的長度比模式串短,即還能夠繼續比較
    {
        failMatchPos=compare(source,pattern,startPos,patternLength);    // 得到首次失配的字符在源串中的位置
        cout<<"failMatchPos:"<<failMatchPos<<" ";
        if( failMatchPos == patternLength)                // 若是恰好和模式串同樣長,即匹配成功
            return true;
        else
        {
            for( j = patternLength-1; j >= 0; j--)        // 查看源串當前匹配子串的下一個字符是否和模式串中的任意一個字符匹配,注意是從右往左查找
                if( source[startPos+patternLength] == pattern[j])
                {
                    startPos += patternLength - j;
                    break;        // 一旦存在,則初始化下一個起始匹配點,即上述所謂的對齊
                }
                if(j < 0)        // 若是不存在,則跳過
                    startPos = startPos+patternLength + 1;
        }
        cout<<"newStartPos"<<startPos<<endl;
    }
    return false;
}
void main()
{
    char *s1 = "THIS IS A EXAMPLE";        // HERE_IS_A_SIMPLE_EXAMPLE 24
    char *s2 = "EXAMPLE";
    bool result = sundayMatch(s1, s2, 17, 7);
    if( result )
        cout<<"yes";
    int i = 0;
    cin>>i;
}

複製代碼

  網絡上的實現方法是這樣的,先作一個預處理,針對子串中每一個出現的字符,保存源串對齊後的下一位一旦出現匹配或者不匹配所須要移動的距離,而後在匹配過程當中就能夠直接使用,不須要像我寫的方法那樣重複的判斷某個位的字符是否和模式串中出現及其出現位置是哪裏。(這種方法花費了空間但贏得了時間)
  安全

複製代碼

/* 採用BM/KMP的預處理的作法,事先計算好移動步長,等到遇到不匹配的值直接使用 */
#include <iostream>
#include <string.h>
using namespace std;

#define MAX_CHAR_SIZE 256    //一個字符8位 最大256種
/*
* 設定每一個字符最右移動步長,保存每一個字符的移動步長
* 若是大串中匹配字符的右側一個字符沒在子串中,大串移動步長=整個串的距離+1
* 若是大串中匹配範圍內的右側一個字符在子串中,大串移動距離=子串長度-這個字符在子串中的位置
*/
int *setCharStep(char *subStr)
{
    int *charStep = new int[MAX_CHAR_SIZE];        // 代碼沒有注意安全:)
    int subStrLen = strlen(subStr);
    for(int i = 0; i < MAX_CHAR_SIZE; i++)
        charStep[i] = subStrLen + 1;
    // 若是大串中匹配字符的右側一個字符沒在子串中,大串移動步長=整個串的距離+1
    // 從左向右掃描一遍 保存子串中每一個字符所需移動步長
    for(int j = 0; j < subStrLen; j++)
    {
        charStep[(unsigned char)subStr[i]] = subStrLen - i;
        // 若是大串中匹配範圍內的右側一個字符在子串中,大串移動距離=子串長度-這個字符在子串中的位置
    }
    return charStep;
}
/*
* 算法核心思想,從左向右匹配,遇到不匹配的看大串中匹配範圍以外的右側第一個字符在小串中的最右位置
* 根據事先計算好的移動步長移動大串指針,直到匹配
*/
int sundaySearch(char* mainStr,char* subStr,int* charStep)
{
    int mainStrLen = strlen(mainStr);
    int subStrLen = strlen(subStr);
    int main_i = 0;
    int sub_j = 0;
    while (main_i < mainStrLen)
    {
        int tem = main_i;    // 保存大串每次開始匹配的起始位置,便於移動指針
        while(sub_j < subStrLen)
        {
            if(mainStr[main_i] == subStr[sub_j])
            {
                main_i++;
                sub_j++;
                continue;
            }
            else{    //  若是匹配範圍外已經找不到右側第一個字符,則匹配失敗
                if( (tem + subStrLen) > mainStrLen)
                    return -1;
                // 不然,移動步長,從新匹配
                char firstRightChar = mainStr[tem+subStrLen];
                main_i = tem+charStep[(unsigned char)firstRightChar];
                sub_j = 0;
                break; // 退出本次失敗匹配 從新一輪匹配
            }
        }
        if(sub_j == subStrLen)
            return (main_i - subStrLen);
    }
    return -1;
}
int main()
{
    char* mainStr = "absaddsasfasdfasdf";
    char* subStr = "dd";
    int* charStep = setCharStep(subStr);
    cout<<"位置:"<<sundaySearch(mainStr,subStr,charStep)<<endl;
    system("pause");
    return 0;
}

複製代碼

【參考資料 感謝做者】
以上部分摘自: 字符串匹配算法之Sunday算法的學習筆記  網絡

另外例舉幾篇關於字串查找的文章:
一、KMP算法 
二、KMP 算法並不是字符串查找的優化 [轉]
三、精確字符串匹配(BM算法) [轉] 
四、sunday算法簡介 函數

相關文章
相關標籤/搜索