字串匹配

2021-10-06 14:35:23 字數 4157 閱讀 3361

給定乙個目標字串string,和乙個模式字串sub,求sub是否是string的子串的問題就叫做字串匹配,也叫做模式匹配。

列舉string的每乙個長度為sub.length()的子串,然後檢查其是否和sub相同。

//    判斷a[i:j)與b[l:r)是否相同

public

boolean

isequals

(string a,

int i,

int j, string b,

int l,

int r)

return

true;}

public

boolean

ismatch

(string string, string sub)

return

false

;}

rabin-karp演算法是利用的hash演算法,將乙個字串對映成乙個唯一的hash值,如果兩個字串的hash值不同,那麼這兩個字串一定不相同;如果hash值相同,因為可能存在衝突(當然這個衝突的可能很小),所以為了確定是否真的相同,還需要對兩個字串進行一次遍歷,進行一一比對。

將字串對映成hash的方法:

設字串為s[0,1,2,…,n],則s[0,1,2,…,i]的hash值這麼計算:

h as

h(s[

0,1,

2,..

.,i]

)=∑j

=0ib

i−j∗

s[j]

(mod

p)

,hash(s[0,1,2,...,i]) = \sum_^b^*s[j] (mod\quad p),

hash(s

[0,1

,2,.

..,i

])=j

=0∑i

​bi−

j∗s[

j](m

odp)

,其中,b表示乙個基底(base),一般選用乙個大於s中元素表示範圍的最小質數(對於純字母組成的字串,字母的表示範圍為0-25共26個,因此本人選用的是31這個質數)。s[j]在參與運算時表示它的ascii值,例如』a』=97,『b』=98。p是乙個較大的質數(本人用的16777619),之所以求模是因為計算機的基本資料結構能表示的資料範圍有限,容易溢位。而選用質數為模的原因是可以減少衝突的發生。

有了s[0,1,2,…,i]的hash值,當我們要計算s[1,2,3,…,i,i+1]的hash值時,就不用重新逐個遍歷它裡面的每乙個字元了,根據上面的式子,我們可以得到:

h as

h(s[

1,2,

3,..

.,i,

i+1]

)=(h

ash(

s[0,

1,2,

...,

i])−

s[0]

∗bi)

∗b+s

[i+1

](mo

dp),

hash(s[1,2,3,...,i,i+1]) = (hash(s[0,1,2,...,i]) - s[0] * b^i) * b + s[i+1] (mod \quad p),

hash(s

[1,2

,3,.

..,i

,i+1

])=(

hash

(s[0

,1,2

,...

,i])

−s[0

]∗bi

)∗b+

s[i+

1](m

odp)

,因此可以在o(1)的時間複雜度內計算出hash值(除了第一次),這個演算法快就快在這裡。

public

class

stringdemo

return

true;}

public

boolean

rabinkarp

(string string, string sub)

// system.out.println(hash+", "+hashsub+", "+pow);

if(hash == hashsub &&

isequals

(string,

0, sub.

length()

, sub,

0, sub.

length()

))for(

int i = sub.

length()

; i < string.

length()

; i++)}

return

false;}

public

static

void

main

(string[

] args)

}

其中有乙個細節需要注意一下,就是計算機的求餘%運算和數學中的求模運算有一點區別:在對負數求餘%時,得到的是乙個負數,例如-12%10 = -2,但是-12 mod 10 = 8,為了消除負數的影響,當對負數求模時,這麼算,-12 mod 10 =(-12%10 + 10)%10。

下午的時候看了不少部落格都沒有具體的實現,公式倒是講的比我清楚,但是都沒說溢位怎麼處理,要是沒有溢位的處理辦法,那麼只能計算長度比較小的子串了,稍微大一點就容易溢位。為了防止溢位,我們使用了求模運算。

求模運算有幾個性質(四則運算的,即加減乘除的模等於模的加減乘除再求模),然後因為冪運算是由乘法變過來的,因此也是符合的,這些運算的混合也還是符合求模的性質。可能說的有點混亂,什麼意思呢,就是對乙個大式子求模時可以先將其單項求模然後運算再求模。為什麼要步步求模,就是因為害怕溢位,等你求出了大式子的結果再求模,很可能中間已經發生了溢位,那麼結果就不會正確了。

大質數p要選用的合適,也不能太大,最好是小於int型最大值的2次方根。為什麼呢?例如a*b (mod p) = (a mod p) * (b mod p) (mod p),如果p大於int型最大值的2次方根,那麼等式後面的(a mod p) * (b mod p)就面臨著溢位的風險,造成求模結果錯誤。

那麼為什麼我還是選用了16777619這麼大的乙個數?那是因為經過我的計算,122 * 16777619 = 2046869518恰好不會發生溢位,122是』z』的ascii值,這是整個算式中最有可能發生溢位的地方,這裡都沒有溢位的話,其他的地方也不會再發生溢位了。

我們來分析一下:

首先是這裡

for

(int i =

0; i < sub.

length()

; i++

)

**第2行hashsub最大值×31不會大於231-1,因為hashsub每一步都是模後的結果,因此其最大值為16777619-1,而sub.charat(i)的最大值為』z』的ascii值122,因此這個式子的最大結果為520106280,可以看到並沒有溢位。

**第3行同上。

**第4行分析同上。

然後看這裡,

hash =

(hash *

31+ string.

charat

(i))

% prime;

hash =((

(hash - string.

charat

(i - sub.

length()

)* pow)

% prime)

+ prime)

% prime;

第1行的分析同上面一樣;

第2行最容易發生溢位的地方在於這個乘法運算:string.charat(i - sub.length()) * pow,但是上面也已經分析過,這個式子並不會發生溢位。不過這一行和前面不一樣的地方在於這裡有乙個減法運算,可能會出現負數,因此要按照前面提到的方法進行修正。

因此最終我們得到了這樣的結論,我選用的這個質數並不會造成溢位。而且因為它足夠的大,基本上不會發生碰撞。

有的部落格裡面提到可以直接使用231作為p,正好直接將模運算當成溢位運算,但是我是沒想明白,這種情況a*b (mod p) = (a mod p) * (b mod p) (mod p)他們是怎麼得出正確的結果的。

字串匹配

題目描述 讀入資料string 然後讀入乙個短字串。要求查詢string 中和短字串的所有匹配,輸出行號 匹配字串。匹配時不區分大小寫,並且可以有乙個用中括號表示的模式匹配。如 aa 123 bb 就是說aa1bb aa2bb aa3bb都算匹配。輸入 輸入有多組資料。每組資料第一行輸入n 1 n ...

字串匹配

time limit 1000ms memory limit 65536k 給定兩個字串string1和string2,判斷string2是否為string1的子串。輸入包含多組資料,每組測試資料報含兩行,第一行代表string1,第二行代表string2,string1和string2中保證不出現...

字串匹配

面試題 給一串很長的字串,要求找到符合要求的字串,例如目的串 123 1 3 2 12 3 這些都要找出來 思路一 利用兩層迴圈,逐個查詢目的串中的字元,比如先查詢字元 1 是否在長字串中,再查詢 2 是否在長字串中,直到目的串遇到 0 是 include include include int m...