الگوریتم جستجوی رشته رابین-کارپ

الگوریتم جستجوی رشته رابین-کارپ یکی از الگوریتم‌های تطابق رشته است که در سال 1987 توسط مایکل او. رابین و ریچارد ام. کارپ ارائه شد. از این الگوریتم برای پیدا کردن مجموعه‌ای از رشته‌ها (الگوها ) در یک متن با به‌کارگیری درهم‌سازی استفاده می‌شود. این کار در دو مرحله پیش‌پردازش و تطابق انجام می‌گیرد که اگر یک متن به طول n و رشته‌ای به طول m داشته باشیم که ، مرحله اول در زمان و مرحله دوم در بدترین حال در زمان انجام می‌شود. هر چند زمان میانگین اجرای مرحله دوم بر اساس فرضیات خاصی می‌تواند به بهبود یابد.
جستجو با استفاده از شیفت دادن زیررشته
در صورتی که متنی به طول n به صورت T1..n و رشته‌ای به طول m به صورت P1..m داشته باشیم که ، آسان‌ترین و ناکارآمدترین راه برای جستجو، الگوریتم جستجوی رشته ساده‌لوحانه است که همه حالت‌های ممکن را به این صورت بررسی می‌کند:
در هر مرحله رشته P1..m با زیررشته‌ای از متن T مقایسه می‌گردد. زیررشته موردنظر در مرحله اول m حرف اول از متن (T1..m)، در مرحله دوم m حرف دوم از متن (T2..m+1) و به همین ترتیب در مرحله s ام m حرف s ام از متن (Ts+1..s+m) می‌باشد. در این مقایسه‌ها، هر بار که دو رشته مقایسه‌شده یکسان باشند، نشانگر یک‌بار رخ‌دادن رشته P در متن T است.
procedure NaiveStringMatcher (T,P)
begin
n=length(T);
m=length(P);
for s=0 to n-m do
if P =T then
print «Pattern occurs with shift» s;
end
هر مقایسه در زمان انجام می‌شود. از آنجا که 1+n-m مقایسه داریم این الگوریتم از مرتبه زمانی خواهد بود.
الگوریتم رابین-کارپ سعی دارد با به‌کارگیری تابع درهم‌سازی، سرعت مقایسه رشته الگو و زیررشته‌های متن را افزایش داده و از این طریق زمان اجرا را بهبود ببخشد.
استفاده از درهم‌سازی
یک تابع درهم‌سازی، تابعی است که مجموعه بزرگی از داده‌ها را به مجموعه کوچکتری از داده‌ها نگاشت می‌کند. در اینجا تابع درهم‌سازی هر رشته را به یک مقدار عددی که به آن مقدار درهم‌سازی می‌گویند، تبدیل می‌کند. برای مثال اگر تابع را برابر طول رشته تعریف کنیم hash("hello") = 5 و hash("cat") = 3 خواهد بود.
الگوریتم رابین-کارپ با بهره‌گیری از این حقیقت که اگر دو رشته یکسان باشند، مقدار درهم‌سازی آن‌ها نیز با هم برابر خواهد بود، سعی دارد به جای مقایسه حرف یه حرف دو رشته، مقادیر درهم‌سازی آنان را مقایسه کند. برای این کار کافی است مقدار درهم‌سازی رشته الگو را حساب نماید و سپس در متن به دنبال زیررشته‌ای با مقدار درهم‌سازی برابر با آن بگردد.
نکته‌ای که باید به آن توجه کرد این است که رشته‌های مختلف می‌توانند مقادیر درهم‌سازی یکسان داشته باشند. به این رویداد تصادم گفته می‌شود. مثلا در مثال بالا مقدار درهم‌سازی هر رشته پنج حرفی برابر 5 خواهد بود. پس با اینکه یکسان بودن دو رشته به معنای یکسان بودن مقدار درهم‌سازی آنان است اما عکس این موضوع لزوما برقرار نیست.
در نتیجه در صورت برابر نبودن مقدار درهم‌سازی رشته الگو و زیررشته قطعا می‌توان نتیجه گرفت که خود رشته‌ها نیز با هم برابر نیستند ولی در صورت برابر بودن این دو مقدار لزوما نمی‌توان برابری رشته‌ها را نتیجه گرفت (برابری مقدار درهم‌سازی شرط لازم است ولی کافی نیست و ممکن است دو رشته با مقدار درهم‌سازی برابر یافت کرده باشیم در حالی که اصل رشته‌ها برابر نیستند). پس در این حالت یکسان بودن زیررشته با رشته الگو را باید با مقایسه حرف به حرف تحقیق کنیم. حال درصورتی که واقعا تصادم رخ داده باشد (به رشته‌های متفاوت با مقدار درهم‌سازی یکسان برخورد کرده باشیم) این کار زمان اجرا را بیهوده افزایش داده است. خوشبختانه اگر تابع درهم‌سازی را خوب تعریف کنیم می‌توانیم امیدوار باشیم این رویداد زیاد رخ ندهد و زمان اجرای میانگین، مطلوب باشد.
تابع درهم‌سازی استفاده شده در رابین-کارپ به این صورت تعریف می‌شود:
hash(T ) = (T1×d m-1 + T2×d m-2 + … + Tm×d 0 ) mod q
که در آن و دو مقدار ثابت هستند. اگر مجموعه الفبا باشد، به طور معمول اینگونه تعریف می‌شود:
و نیز عموما یک عدد اول بزرگ در نظر گرفته می‌شود به طوری که در یک کلمه از کامپیوتر جا بگیرد.
برای مثال اگر فرض کنیم ، متن موردنظر ما و برای راحتی کار باشد، خواهیم داشت: و مقدار درهم‌سازی زیررشته "31415" به طول 5 از متن برابر است با:
( 3×10 4 + 1×10 3 + 4×10 2 + 1×10 1 + 5×10 0 ) mod 1 = 31415 mod 1 = 31415
مقدار تابع درهم‌سازی تعریف شده را برای هر رشته به طول m می‌توان با استفاده از روش هورنر در زمان به این صورت محاسبه کرد:
hash(T ) = (Tm + d(Tm-1 + d(Tm-2 + … + d(T2 + dT1)…))) mod q
حال اگر بخواهیم برای هر زیررشته یک‌بار مقدار این تابع را حساب کنیم از آنجا که n-m+1 زیررشته داریم، این کار طول می‌کشد که همان زمان اجرای الگوریتم ساده‌لوحانه می‌باشد و در حقیقت زمان اجرا بهبود نیافته است. برای حل این مشکل از درهم‌سازی چرخشی استفاده می‌کنیم. درهم‌سازی چرخشی به ما امکان می‌دهد تا مقدار درهم‌سازی زیررشته هر مرحله را از روی مقدار درهم‌سازی زیررشته مرحله قبلی در زمان ثابت به دست بیاوریم. این روند در حال کلی به صورت زیر است:
s = s - si + si+m
در الگوریتم رابین-کارپ اگر مقدار درهم‌سازی زیررشته در مرحله s ام را بنامیم، رابطه درهم‌سازی چرخشی به این صورت خواهد بود:
t s+1 = ( d( t s - d m-1 Ts+1 ) + Ts+m+1 ) mod q
در مثال گفته شده در بالا اگر بخواهیم مقدار درهم‌سازی زیررشته مرحله بعد یعنی "14156" را از روی مقدار درهم‌سازی زیررشته قبل یعنی "31415" حساب کنیم روند کار به این صورت خواهد بود:
t s+1 = ( 10×(31415 - 10 4 × 3) + 6 ) mod 1 = 14156
در نهایت می‌توان گفت در الگوریتم رابین-کارپ کافی است تنها مقدار درهم‌سازی رشته الگو P1..m و مقدار درهم‌سازی زیررشته T1..m از متن را در بخش پیش پردازش با زمان محاسبه کرد و سپس محاسبه مقادیر درهم‌سازی زیررشته‌های مراحل بعد در هر مرحله با زمان قابل انجام است.
الگوریتم و زمان اجرا
شبه‌کد الگوریتم رابین-کارپ را می‌توان به این صورت نشان داد:
procedure Rabin-Karp-Matcher (T,P,d,q)
begin
1 n = length(T);
2 m = length(P);
3 h = d^(m-1) mod q;
4 p = 0;
5 t = 0;
6
7 for i = 1 to m do // Preprocessing
8 p = (d*p + Pi) mod q;
9 t = (d*t + Ti) mod q;
10
11 for s = 0 to n-m do // Matching
12 if p = t then
13 if P = T then
14 print «Pattern occurs with shift» s;
15 if s <n-m then
16 t = ( d * (t - Ts+1*h ) + Ts+m+1 ) mod q;
end
خطوط 8 و 9 در زمان ثابت اجرا می‌شوند و کل حلقه در خط 7، m بار تکرار می‌گردد پس مرحله پیش‌پردازش از مرتبه زمانی خواهد بود.
خط 13 نیز در زمان اجرا می‌شود و از آنجا که احتمال اجرای آن در هر بار تکرار حلقه وجود دارد، مرتبه زمانی اجرای حلقه در بدترین حالت برابر است.
اما در بسیاری از حالت‌ها انتظار می‌رود تطابق رشته الگو و زیررشته، دفعات کمی رخ دهد (مثلا به تعداد ثابتی مانند c از کل دفعات تکرار حلقه). در این صورت زمان اجرای موردانتظار مرحله تطابق برابر با خواهد بود.
الگوریتم رابین-کارپ و جستجوی رشته چندالگویی
الگوریتم رابین-کارپ در جستجوی رشته تک‌الگویی نسبت به الگوریتم کنوث-موریس-پرت، الگوریتم جستجوی رشته بویر-مور و دیگر الگوریتم‌های سریع جستجوی رشته تک‌الگویی، به علت زمان اجرای کند خود در بدترین حالت، عملکرد ضعیف‌تری دارد اما برای جستجوی رشته چندالگویی الگوریتم مناسبی به شمار می‌رود.
اگر بخواهیم هر یک از الگوهای با طول ثابت را در متن جستجو کنیم، کافی است گونه‌ای از الگوریتم رابین-کارپ را بسازیم که با استفاده از داده ساختار مجموعه تعیین می‌کند که آیا مقدار درهم‌سازی زیررشته موردنظر از متن در مجموعه مقادیر درهم‌سازی الگوها وجود دارد یا خیر. فرض می‌کنیم همه زیررشته‌ها به طول ثابت m هستند:
function RabinKarpSet(string s , set of string subs, m):
set hsubs := emptySet
for each sub in subs
insert hash(sub ) into hsubs
hs := hash(s )
for i from 1 to n-m+1
if hs ∈ hsubs and s ∈ subs
return i
hs := hash(s )
return not found
برای جستجوی الگو در متن با روش ساده‌لوحانه باید بار جستجوی رشته تک‌الگویی ساده‌لوحانه اجرا گردد که با توجه به فرض ثابت بودن m می‌توان گفت این کار از مرتبه زمانی است. اما الگوریتم بالا می‌تواند همه الگو را در زمان بیابد زیرا با به‌کارگیری یک جدول درهم‌سازی در زمان می‌توان مشخص کرد که آیا مقدار درهم‌سازی یک زیررشته با مقادیر درهم‌سازی هر یک از الگوها برابر است یا خیر.
الگوریتم تطابق رشته‌ها
الگوریتم جستجوی رشته
تابع درهم‌سازی