אלגוריתם בויאר-מור

במדעי המחשב, **אלגוריתם בויאר-מור** הוא אלגוריתם חיפוש מחרוזות יעיל המשמש מדד השוואה סטנדרטי עבור אלגוריתמי חיפוש. האלגוריתם פותח על ידי החוקרים האמריקאים רוברט בויאר וג' סטרותר מור ב-1977. האלגוריתם מבצע עיבוד מקדים למחרוזת החיפוש (התבנית), אך לא לטקסט שבו יבוצע החיפוש. האלגוריתם מתאים היטב אפוא ליישומים שבהם תבנית החיפוש קצרה בהרבה מהטקסט או למקרים שבהם נעשה שימוש חוזר בתבנית החיפוש. אלגוריתם בויאר-מור משתמש במידע הנאסף בשלב העיבוד המקדים על מנת לדלג על חלקים בטקסט, והודות לכך הוא משיג ביצועים טובים ביחס לאלגוריתמי חיפוש אחרים. באופן כללי, האלגוריתם רץ מהר יותר ככל שמחרוזת החיפוש ארוכה יותר. התכונות החשובות של האלגוריתם הן שהוא מחפש מסוף מחרוזת החיפוש ולא תחילתה, והוא מדלג בטקסט על מספר תווים במקום לחפש תו-תו.

## הגדרות



עימודים של התבנית **PAN** על הטקסט **ANPANMAN**, מ **k=3** ועד **k=8**. התאמה נמצאת ב **k=5**.


* ***[S''[''i*** יסמן את התו באינדקס ה-*i* של המחרוזת *S*, החל מ-1.
* ***[S''[''i''..''j*** יסמן את תת-המחרוזת ב *S* אשר מתחילה בתו *i* ומסתיימת בתו *j*, כולל.
* **תחילית של ''S*** היא תת-מחרוזת *S*[1..*i*] עבור *i* כלשהו בטווח <math>[1, n]</math>, כאשר *n* היא אורך המחרוזת *S*.
* סיפא של *S* היא תת-מחרוזת *S*[*i*..*n*] עבור *i* כלשהו בטווח [1, *n*], כאשר *n* היא אורך המחרוזת *S*.
* מחרוזת החיפוש תסומן באמצעות ***P*** ותקרא לעיתים תבנית החיפוש. האורך שלה יסומן באמצעות ***n***.
* הטקסט שבו מחפשים את מחרוזת החיפוש מסומן באמצעות ***T***. האורך שלו יסומן באמצעות ***m***.
* עימוד של *P* ל *T* הוא אינדקס *k* ב *T* שבו התו האחרון של *P* מועמד מול האינדקס k של *T*.
* התאמה של *P* קוראת בעימוד אם *P* שווה ל *T*[(*k*-*n*+1)..*k*].

== תיאור ==
אלגוריתם בויאר-מור מחפש מופעים של מחרוזת חיפוש P בטקסט שבו מחפשים T באמצעות השוואת תווים בעימודים שונים. במקום שימוש בחיפוש [[כוח גס]] של כל העימודים (ישנם <math>m - n + 1</math> כאלו), בויאר-מור משתמש במידע מעיבוד מקדים של תבנית החיפוש P על מנת לדלג על מספר עימודים גדול ככל האפשר.

לפני שהוצג אלגוריתם זה, השיטה הנהוגה לחיפוש טקסט הייתה לבדוק כל תו בטקסט ביחס לתו הראשון במחרוזת החיפוש, ואם נמצאה התאמה נעשה חיפוש בהתאם לתו הבא במחרוזת החיפוש וכן הלאה. בצורה זו נדרשים להשוות כל תו בטקסט.

ההבחנה החשובה באלגוריתם זה היא שאם סוף מחרוזת החיפוש מושוות לטקסט, ניתן לדלג על תווים בחיפוש ולא לבדוק כל תו בטקסט. הסיבה לכך היא שאם משווים את התו האחרון של תבנית החיפוש לתו בטקסט ואין התאמה, אין צורך לחפש אחורנית בתבנית החיפוש. אם התו בטקסט לא מתאים לאף אחד מהתווים בתבנית החיפוש, אז התו הבא להשוואה בטקסט נמצא n תווים בהמשך, כאשר n הוא אורך תבנית החיפוש. אם התו נמצא בתבנית החיפוש, נדרשת הזזה חלקית של תבנית החיפוש אל התו המתאים והתהליך חוזר על עצמו. החיפוש בטקסט באמצעות דילוגים לעריכת השוואות במקום לבדוק כל תו ותו מצמצם את מספר ההשוואות הנדרשות ובכך תורם ליעילות האלגוריתם.

באופן פורמלי יותר, האלגוריתם מתחיל בעימוד <math> k = n</math>, כך שהתחלת תבנית החיפוש P מועמדת מול ההתחלה של הטקסט T. נערכת השוואה בין התווים ב-P וב-T אשר מתחילה מהאינדקס n בתבנית P ו-k ב-T, והולכת אחורה: המחרוזות מושוות ביחס לסופה של P ועד לתחילתה. ההשוואות ממשיכות עד הגעה לתחילתה של תבנית החיפוש P (במקרה של התאמה) או עד לאי התאמה שבעקבותיה העימוד מוזז קדימה בהתאם לערך המותר המרבי בהתאם למספר כללים. ההשוואות נערכות פעם נוספת בעימוד החדש, והתהליך חוזר על עצמו.

חוקי ההזזה ממומשים באמצעות טבלת חיפוש המוגדרת באמצעות עיבוד מקדים של תבנית החיפוש P.

== כללי ההזזה ==
ההזזה מחושבת באמצעות שני כללים: כלל התו הלא מתאים, וכלל הסיפא הטובה. הזזה בפועל היא הערך המרבי המוגדר על ידי שני כללים אלו.

=== כלל התו הלא מתאים ===

==== תיאור ====
<div class="thumb tleft" style="width: 160px;">
<div>

<div class="thumbcaption" style="direction:rtl;width: 160px;">הדגמה של הכלל עבור תבנית החיפוש *'NNAAMAN**.</div>
</div>
</div>
כלל התו הלא מתאים מתייחס לתו ב-T שבו ההשוואה נכשלת. המופע הקודם של תו זה ב-P נמצא, ומוצעת הזזה המעבירה את המופע אל מול האי התאמה ב-P. אם התו הלא מתאים לא נמצא מלפני P, מוצעת הזזה המזיזה את P מעבר לתו הלא מתאים.

==== עיבוד מקדים ====
ישנן שיטות שונות בנוגע לטבלאות חיפוש עבור כלל התו הלא מתאים, להלן גרסה פשוטה הפועלת בזמן קבוע: יוצרים מערך דו־ממדי שבו האינדקס הראשון הוא התו c באלפבית והאינדקס השני הוא האינדקס i בתבנית החיפוש. טבלת החיפוש תחזיר את המופע של  c ב P עם האינדקס השני בגודלו <math>j < i</math> או <math>-1</math> אם לא נמצא מופע. ההסטה המוצעת תהיה <math>i - j</math>, כשסיבוכיות הזמן הנדרשת היא  <math>O(1)</math>  וסיבוכיות המקום היא <math>O(kn)</math>, בהנחה של אלפבית סופי באורך k.

=== כלל הסיפא הטובה ===

==== תיאור ====
<div class="thumb tleft" style="width: 200px;">
<div>

<div class="thumbcaption" style="width: 200px;">הדגמה של הכלל עבור תבנית החיפוש **ANAMPNAM**.</div>
</div>
</div>
כלל הסיפא הטובה הוא כלל מורכב יותר ביחס לכלל הקודם. הוא הסיבה לכך שההשוואות נעשות מסוף תבנית החיפוש ולא מתחילה. הכלל מוגדר כך:

<blockquote>
נניח כי עבור עימוד מסוים של תבנית החיפוש ***P*** ו ***T***, תת-מחרוזת ***t*** של ***T*** מתאימה לסיפא של ***P***, אך יש אי התאמה בהשוואה הבאה (בתו הקודם). במקרה זה מחפשים, במידה וקיים, את העותק האחרון *****'' של ***t*** ב ***P***  כך ש ****** אינה סיפא של   ***P*** והתו הקודם ל ****** ב ***P*** שונה מהתו הקודם ל ***t*** ב ***P***. נעשית הסטה של ***P*** קדימה כך שתת-המחרוזת ****** ב ***P*** מועמדת מול תת-המחרוזת ***t*** ב ***T***. אם ****** לא קיימת,  מזיזים את ההתחלה של ***P*** כך שתעבור את סוף *****' ב ***T*** כך שהתחילית של התבנית המוסטת מתאימה לסיפא של ***t*** ב ***T***. אם לא קיימת הסטה כזו, מסיטים את ***P*** ב ***n*** מקומות  קדימה. אם נמצא מופע של ***P***, אז מסיטים את ***P*** כך שהתחילית של P המוסטת תתאים לסיפא של המופע של  ***P*** ב ***T***. אם לא קיימת הסטה כזו, מסיטים את ***P*** ב ***n*** מקומות, כלומר, מסיטים את ***P*** שתעבור את ***t***.
</blockquote>

==== עיבוד מקדים ====
כלל הסיפא הטובה דורש שתי טבלאות: אחת לשימוש במקרה הכללי, ואחת לשימוש כאשר המקרה הכללי לא מחזיר תוצאה משמעותית או שמתקיימת התאמה. טבלאות אלה יסומנו L ו - H בהתאמה. ההגדרות שלהן הם כדלקמן:

<blockquote>
עבור כל תו i, <math>L[i]</math> הוא העמדה הגדולה ביותר שקטנה מ-n כך שהמחרוזת <math>P[i..n]</math> מתאימה לסיומת של <math>P[1..L[i]]</math> כך שהתו הקודם לסיומת לא שווה <math>P[i-1]</math>. <math>L[i]</math>  מוגדר להיות אפס אם אין עמדה המקיימת את התנאי.

נגדיר את [H[i כסיומת הארוכה ביותר של <math>P[i..n]</math> שהיא גם קידומת של P, אם קיימת כזו. אם לא קיימת כזו, הערך של <math>H[i]</math> יהיה אפס.
</blockquote>

את שתי הטבלאות האלה ניתן לבנות בזמן <math>O(n)</math> ובמקום <math>O(n)</math>. עימוד ההזזה עבור האינדקס i ב P מוגדר להיות <math>n - L[i]</math> או <math>n - H[i]</math>.ב-H נעשה שימוש רק אם <math>L[i]</math> הוא אפס או שנמצאה התאמה.

== כלל גליל ==
אופטימיזציה פשוטה אבל חשובה של בויאר-מור הוצעה על ידי [[צבי גליל]] ב-1979. כלל גליל עוסק בהאצת ההשוואות שנעשות בכל עימוד, באמצעות דילוג על קטעים שידוע על התאמתם. נניח כי בעימוד ''k''<sub>1</sub>, נעשית השוואה בין **P** ל ***T*** עד לתו ***c*** של **T**. אז אם ***P*** מוסט ל- ''k''<sub>2</sub> כך שתחילתו נמצאת בין ***c*** לבין ''k''<sub>1</sub>, בשלב השוואה הבא הקידומת של ***P*** חייבת להתאים לתת המחרוזת [***T***[(''k''<sub>2</sub> - ''n'')..''k''<sub>1</sub>. לכן, אם ההשוואות מגיעות עד לעמדה ''k''<sub>1</sub> של ***T***, ניתן להניח כי נמצא מופע של ***P**' מבלי לבדוק מעבר ל *k*<sub>1</sub>. בנוסף לשיפור היעילות של בויאר-מור, כלל גליל נדרש על מנת להוכיח זמן ריצה ליניארי במקרה הגרוע ביותר.

== זמן ריצה ==
זמן הריצה במקרה הגרוע ביותר של אלגוריתם בויאר-מור כפי שהוצג במאמר המקורי הוא <math>O(n+m)</math>, רק אם תבנית החיפוש אינה מופיעה בטקסט. תוצאה זו הוכחה לראשונה על ידי [[דונלד קנות'|קנות']], מוריס ופראט בשנת 1977,
ואחרי כן על ידי גוביאס ואודליזקו ב-1980 עם חסם עליון של 5*n* השוואות במקרה הגרוע ביותר. ריצ'רד קול סיפק הוכחה עם חסם עליון של 3*n'' השוואות במקרה הגרוע ביותר ב-1991.

כאשר תבנית החיפוש נמצאת בטקסט, זמן ריצה של האלגוריתם המקורי הוא O(nm) במקרה הגרוע ביותר. ניתן לראות זאת כאשר תבנית החיפוש והטקסט מורכבים מתו שחוזר על מנת. עם זאת, הכללתו של כלל גליל תורמת לזמן ריצה ליניארי בכל המקרים.

## מימושים
קיימים מימושים שונים במגוון שפות תכנות. ב - C++, ספריית Boost מספקת מימוש כללי של חיפוש בויאר-מור תחת ספריית Algorithm. ב-Go קיים מימוש בsearch.go.

אלגוריתם בויאר-מור  משמש גם את תוכנית החיפוש grep.

## קישורים חיצוניים

* המאמר המקורי של אלגוריתם בויאר-מור
* דוגמה של אלגוריתם בויאר-מור מדף הבית של סטרותר מור
* המאמר של ריצ'רד קול מ-1991 המוכיח את ליניאריות זמן הריצה

## הערות שוליים

בויאר-מור