給定乙個目標字串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...