אוטומט מחסנית

במדעי המחשב, אוטומט מחסנית (באנגלית: Pushdown automaton, ובראשי תיבות: PDA) הוא מודל חישובי, שמהווה הרחבה של מודל האוטומט הסופי (הדטרמיניסטי), על ידי הוספת מחסנית, שבה האוטומט מסוגל לאחסן מידע (משמע, לאוטומט יש יכולת זיכרון). ההרחבה מגדילה את כוחו החישובי של האוטומט; כלומר, את קבוצת השפות שהוא מסוגל לזהות. בגרסתו הסטנדרטית, מודל אוטומט המחסנית מסוגל לזהות בדיוק את כל השפות חסרות ההקשר. בדומה לאוטומט הסופי הדטרמיניסטי, אוטומט המחסנית מורכב מאוסף של מצבים, המקבילים למצבי הזיכרון בהם יכולה להימצא מכונת החישוב במהלך ביצוע חישוב. דרך הפעולה של האוטומט מבוססת על קריאת מילת קלט (מחרוזת, המורכבת מאלפבית סופי וידוע מראש) באופן סדרתי, אות אחת בכל פעם, עד אשר מסתיימת קריאת המילה, ואז מחזיר האוטומט תשובה – האם המילה שנקראה שייכת לשפה אותה מזהה האוטומט, או לא. ההבדל המרכזי שבין אוטומט סופי ובין אוטומט מחסנית הוא התקן הזיכרון הנוסף, שעומד לרשות אוטומט המחסנית – מחסנית, שמאפשרת אחסון (שמירת) נתונים. המחסנית מאחסנת אותיות (מעל אלפבית סופי וידוע מראש, אך לא בהכרח האלפבית שממנו בנויה מילת הקלט) בצורה סדרתית, כך שהאות האחרונה שהוכנסה למחסנית נמצאת בראשה, אחריה האות שהוכנסה לפניה למחסנית, וכן הלאה, עד האות הראשונה שהוכנסה למחסנית, הנמצאת בתחתיתה (ונגישה להצצה או שליפה אחרונה מכולן) (שיטת LIFO). בצורה זו, הגישה לזיכרון מוגבלת מאוד – ניתן לראות בכל פעם רק את האות שבראש המחסנית, וכדי לראות אות שבתוך המחסנית, יש להוציא מהמחסנית את כל האותיות שמעליה (ובכך "לאבד" אותן, שכן אין מקום אחסון נוסף עבורן). בכל צעד חישוב שלו, אוטומט המחסנית מחליט על דרך הפעולה שלו, בהתבסס על שלושה נתונים: האות שהוא קורא מהקלט (אם כי הוא אינו מחויב לקרוא אות מהקלט בכל צעד), המצב הפנימי שלו (שם המצב + מצב המחסנית), האות שנמצאת בראש המחסנית. לאחר שהחליט, הוא עובר למצב פנימי חדש (לא בהכרח אחר), תוך כדי שינוי תוכן המחסנית, באחד משלושה אופנים: הוצאת אות אחת בלבד מראש המחסנית (באנגלית, פעולת Pop). אי-ביצוע כל שינוי במחסנית (לא הכנסה ולא הוצאה של אותיות; משמע, רק "הצצנו"/הסתכלנו על האות שבראש המחסנית). הכנסת אות אחת לראש המחסנית (באנגלית, פעולת Push). המודל הסטנדרטי של אוטומט המחסנית הוא אי-דטרמיניסטי – בכל צעד חישוב, האוטומט מסוגל לבצע מספר בחירות שונות במקביל, ולכן, ניתן לתאר חישובים שונים רבים שהוא מבצע על כל מילת קלט, ודי בכך, שבאחד החישובים האוטומט יכריע כי מילת הקלט שייכת לשפה אותה הוא מזהה (כלומר, יסיים את חישובו במצב מקבל). ניתן להגדיר גם מודל דטרמיניסטי של אוטומט מחסנית, אך מתברר כי מודל זה הוא בעל יכולת חישובית פחותה ממש מזו של המודל האי-דטרמיניסטי: קיימות שפות, אותן המודל האי-דטרמיניסטי מסוגל לזהות ואילו המודל הדטרמיניסטי לא מסוגל לזהותן; דוגמה לשפה כזאת היא שפת כל הפלינדרומים. בכך נבדל אוטומט המחסנית הן מהאוטומט הסופי והן ממודל החישוב הכללי יותר, של מכונת טיורינג – בשני המודלים הללו מתקיימת שקילות, מבחינת כוח החישוב, בין המודל הדטרמיניסטי ובין המודל האי-דטרמיניסטי. אוטומט עם שתי מחסניות שקול בכוחו החישובי לזה של מכונת טיורינג. תיאור מתמטי פורמלי של אוטומט מחסנית   A {\displaystyle \ A} הוא באמצעות השביעייה הסדורה הבאה:   A = { Q , Σ , Γ , δ , q 0 , ⊣ , F } {\displaystyle \ A=\left\{Q,\Sigma ,\Gamma ,\delta ,q_{0},\dashv ,F\right\}} כאשר: Q {\displaystyle Q} היא קבוצת מצבי האוטומט. כל מצב בקבוצה זו הוא, בהכרח, אחד מהשניים: "מצב מקבל" או "מצב לא מקבל". Σ {\displaystyle \Sigma } היא (קבוצת) א"ב הקלט. כל איבר בקבוצה זו מכונה "אות". Γ {\displaystyle \Gamma } היא (קבוצת) א"ב של המחסנית. כל איבר בקבוצה זו מכונה "אות". δ : Q × ( Σ ∪ { ε } ) × Γ → P ( Q × Γ ∗ ) {\displaystyle \delta \colon Q\times \left(\Sigma \cup \left\{\varepsilon \right\}\right)\times \Gamma \to \mathrm {P} \left(Q\times \Gamma ^{*}\right)} היא פונקציית המעברים. q 0 {\displaystyle q_{0}} הוא המצב ההתחלתי (ממנו מתחיל החישוב) q 0 ∈ Q {\displaystyle q_{0}\in Q} . ⊣ {\displaystyle \dashv } היא האות ההתחלתית שבמחסנית - תמיד! זוהי אות שמורה מיוחדת, שמשמעותה בפועל היא שהמחסנית ריקה, כי למחסנית ריקה באמת יש משמעות מעשית אחרת באוטומט מחסנית. F {\displaystyle F} היא קבוצת המצבים המקבלים, F ⊆ Q {\displaystyle F\subseteq Q} . מצב מקבל הוא מצב שמסמן שהאוטומט מחזיר תשובה חיובית. כלומר: אם בסוף הקלט האוטומט נמצא במצב מקבל, זה אומר שהקלט הוא מילה בשפה שהאוטומט מזהה. משמעותה של פונקציית המעברים היא זו – בהינתן: המצב הנוכחי של האוטומט (שם המצב), האות הבאה שהוא קורא מהקלט (או המילה הריקה ε {\displaystyle \varepsilon } , אם רוצים לתאר מעבר, שאינו כולל קריאת מילה מהקלט), האות שמופיעה בראש המחסנית, פעולת האוטומט תהיה לעבור למצב אחר (או להישאר במצב שבו הוא נמצא), ולשנות את תוכן המחסנית על ידי הסרת האות שנמצאת בראש המחסנית והכנסת אות או מילה חדשה במקומה למחסנית (ייתכן, אפילו, להכניס את אותה האות שהוצאה, על-מנת לא לשנות את מצב המחסנית). מכיוון שהאוטומט אי-דטרמיניסטי, לכל שלשה סדורה של: מצב + אות קלט + אות מראש המחסנית, מותאמת קבוצה של זוגות סדורים אפשריים, שכל אחד מהם מורכב מ: המצב שאליו עוברים + המילה שמוכנסת לראש המחסנית (יכולה להיות גם המילה הריקה   ε {\displaystyle \ \varepsilon } ; דהיינו, לא להכניס אף אות למחסנית). לכן, טווח פונקציית המעברים הוא P ( Q × Γ ∗ ) {\displaystyle \mathrm {P} \left(Q\times \Gamma ^{*}\right)} – קבוצת החזקה של כל הזוגות האפשריים של מצב + המילה המוכנסת לראש המחסנית. עם זאת, מניחים כי רק תת-הקבוצות הסופיות של זוגות שכאלו יכולים להתקבל באמצעות פונקציית המעברים (כלומר, מספר דרכי הפעולה השונות של האוטומט, בהינתן מצב, אות קלט ואות מראש המחסנית הוא סופי). פונקציית המעברים אינה מוגדרת, כאשר המחסנית ריקה. על כן, ריקון של המחסנית לפני תום קריאת מלוא מילת הקלט גורר בהכרח "היתקעות" של האוטומט ודחיית המילה הנקראת (כלומר, החזרת התשובה, שהמילה אינה בשפה שהאוטומט מזהה). באופן דומה, אין הכרח להגדיר את פונקציית המעברים עבור כל השלשות הסדורות האפשריות, וניתן לחשוב על כל שלשה סדורה, שעבורה הפונקציה אינה מוגדרת, כאילו היא "תוקעת" את האוטומט (כזכור, אנו מדברים על אוטומט (מחסנית) לא דטרמיניסטי). להלן מספר דוגמאות לפעולת פונקציית המעברים δ {\displaystyle \delta } (הפועלת על שלשה סדורה ומחזירה זוג סדור), שבכולן האוטומט מתחיל במצב q {\displaystyle q} ונשאר בו: ( q , Z ) ∈ δ ( q , σ , Z ) {\displaystyle (q,Z)\in \delta (q,\sigma ,Z)} - האוטומט קורא את האות σ {\displaystyle \sigma } מהקלט ואינו משנה את ראש המחסנית (ניתן לחשוב על כך כאילו האוטומט הוציא את האות Z {\displaystyle Z} מראש המחסנית ומיד הכניס אותה בחזרה). ( q , σ Z ) ∈ δ ( q , σ , Z ) {\displaystyle (q,\sigma Z)\in \delta (q,\sigma ,Z)} - האוטומט קורא את האות   σ {\displaystyle \ \sigma } מהקלט ודוחף אותה למחסנית, מעל ראש המחסנית הקיים (ניתן לחשוב על כך כאילו האוטומט הוציא את האות Z {\displaystyle Z} מראש המחסנית ומיד דחף אותה חזרה, ואחריה דחף גם את האות   σ {\displaystyle \ \sigma } ). ( q , ε ) ∈ δ ( q , σ , Z ) {\displaystyle (q,\varepsilon )\in \delta (q,\sigma ,Z)} - האוטומט קורא את האות   σ {\displaystyle \ \sigma } מהקלט ומוציא את האות Z {\displaystyle Z} מראש המחסנית, מבלי לדחוף למחסנית שום דבר אחר במקומה. בכך, נחשפת האות שמתחתיה (זו שהוכנסה ממש לפני האות Z {\displaystyle Z} למחסנית) ומהווה את ראש המחסנית החדש. על מנת לתאר חישובים באמצעות אוטומט מחסנית, נהוג להשתמש בתיאור רגעי. תיאור רגעי הוא שלשה סדורה, שמכילה את כל המידע הנחוץ לחישוב הצעד הנוכחי (והצעדים הנותרים) של האוטומט: המצב הנוכחי של האוטומט (שם המצב), שארית מילת הקלט (החלק שטרם נקרא), מצב המחסנית המלא. בצורה פורמלית, מגדירים תיאור רגעי בתור השלשה הסדורה ( q , w , γ ) {\displaystyle \left(q,w,\gamma \right)} , כך ש:   q ∈ Q {\displaystyle \ q\in Q} הוא המצב הנוכחי,   w ∈ Σ ∗ {\displaystyle \ w\in \Sigma ^{*}} היא מילת הקלט שטרם נקראה,   γ ∈ Γ ∗ {\displaystyle \ \gamma \in \Gamma ^{*}} הוא תוכנהּ הנוכחי של המחסנית. באמצעות תיאורים רגעיים, ניתן להציג חישוב של אוטומט מחסנית כסדרה של תיאורים רגעיים, שהמעבר בין כל שניים מהם מייצג צעד חישוב אחד של האוטומט. בצורה פורמלית, נהוג לסמן: ( q , a w , Z α ) ⊢ ( p , w , β α ) {\displaystyle \left(q,aw,Z\alpha \right)\vdash \left(p,w,\beta \alpha \right)} , כדי לתאר מעבר מהמצב הרגעי ( q , a w , Z α ) {\displaystyle (q,aw,Z\alpha )} למצב הרגעי ( p , w , β α ) {\displaystyle \left(p,w,\beta \alpha \right)} . מעבר כזה הוא אפשרי אם ורק אם ( p , β ) ∈ δ ( q , a , Z ) {\displaystyle (p,\beta )\in \delta (q,a,Z)} (כאן, a ∈ Σ ∗ {\displaystyle a\in \Sigma ^{*}} , ולכן ייתכן גם ש-   a = ε {\displaystyle \ a=\varepsilon } ). בדומה, משתמשים בסימון ⊢ k {\displaystyle \vdash ^{k}} כדי לתאר מעבר בן   k {\displaystyle \ k} צעדים בין שני תיאורים רגעיים, ובסימן ⊢ ∗ {\displaystyle \vdash ^{*}} כדי לסמן שני תיאורים רגעיים, שניתן להגיע מהאחד אל השני במספר כלשהו של צעדים. בדומה לאוטומט סופי, גם באוטומט מחסנית ניתן להגדיר קבלה של מילה באמצעות קבוצה של מצבים מקבלים: האוטומט יקבל מילה, אם לאחר סיום קריאת המילה (וייתכן שגם ביצוע מספר מסעי אפסילון ( ε {\displaystyle \varepsilon } ) לאחר מכן) האוטומט נמצא (עצר) במצב מקבל. עם זאת, קיימת דרך נוספת להגדיר קבלה עבור אוטומט מחסנית – קבלה באמצעות ריקון המחסנית. באופן קבלה זה, אומרים שהאוטומט מקבל מילה, אם לאחר סיום קריאת המילה (ושוב, ייתכן שגם ביצוע מספר מסעי אפסילון לאחר מכן) המחסנית ריקה (כזכור, אם המחסנית מתרוקנת לחלוטין (כולל האות המיוחדת השמורה   ⊣ {\displaystyle \ \dashv } ) במהלך חישוב, הדבר גורם ל"תקיעת" האוטומט). בצורה פורמלית, השפה שמתקבלת על ידי הגעה למצב מקבל מוגדרת בתור: L f ( M ) = { w ∈ Σ ∗ ∣ ∃ p ∈ F , γ ∈ Γ ∗ : ( q 0 , w , ⊣ ) ⊢ ∗ ( p , ε , γ ) } {\displaystyle L_{f}(M)=\left\{w\in \Sigma ^{*}\mid \exists p\in F,\gamma \in \Gamma ^{*}:\left(q_{0},w,\dashv \right)\vdash ^{*}\left(p,\varepsilon ,\gamma \right)\right\}} ואילו השפה שמתקבלת על ידי ריקון המחסנית מוגדרת בתור: L e ( M ) = { w ∈ Σ ∗ ∣ ∃ p ∈ Q : ( q 0 , w , ⊣ ) ⊢ ∗ ( p , ε , ε ) } {\displaystyle L_{e}(M)=\left\{w\in \Sigma ^{*}\mid \exists p\in Q:\left(q_{0},w,\dashv \right)\vdash ^{*}\left(p,\varepsilon ,\varepsilon \right)\right\}} באופן כללי, יכול להיות ש- L f ( M ) ≠ L e ( M ) {\displaystyle L_{f}(M)\neq L_{e}(M)} ; כלומר, שהשפה שהאוטומט מזהה על ידי הגעה למצב מקבל שונה מהשפה שהוא מזהה על ידי ריקון המחסנית. עם זאת, קיימת שקילות בין שני אופני הקבלה: אם שפה כלשהי L {\displaystyle L} מזוהה על ידי הגעה למצב מקבל בידי אוטומט מחסנית כלשהו, קיים אוטומט מחסנית (לא בהכרח אותו אחד) שמזהה את השפה L {\displaystyle L} על ידי ריקון המחסנית, ולהפך. כמובן, שניתן גם להגדיר את השפה שהאוטומט מקבל כאיחוד שתי דרכי הגדרות הקבלה: L f ( M ) ∪ L e ( M ) {\displaystyle L_{f}(M)\cup L_{e}(M)} . שמואל זקס ונסים פרנסיז, ‏אוטומטים ושפות פורמליות א, האוניברסיטה הפתוחה, 2000 שמואל זקס ונסים פרנסיז, ‏אוטומטים ושפות פורמליות ב, האוניברסיטה הפתוחה, 2000 מדיה וקבצים בנושא אוטומט מחסנית בוויקישיתוף גדי אלכסנדרוביץ', אוטומט מחסנית, באתר "לא מדויק", 22 במרץ 2015