אחד ממבני הנתונים הראשונים שדיברנו עליהם בפרק של מבני נתונים ליניאריים היה תור . תור תומך ב
מבחינה מתמטית אנחנו מתאים את המבנה הזה עם priority queue. תור עדיפויות הוא מבנה נתונים מופשט כמו מחסנית או תור , כלומר שהוא מייצר איזשהם תכונות שעבורם כל מימוש שניתן עם התוכנות האלה ייקרא ״תור עדיפויות״. בתור עדיפויות כל ערך יחזיק מידע שיתאר את העדיפות שלו באופן מתמטי. כמו כן, נרצה לתמוך בהוצאה והכנסה מהמבנה ושינוי עדיפות בזמן יעיל. כמו כן, נרצה שליפה מהירה לאובייקטים בעדיפות גבוהה.
אחד הפתרונות אולי, יכול להיות מערך ממויין בסדר יורד כך שהעדיפות הגבוהה ביותר נמצאת במקום ה
פתרון נוסף יכול להיות עץ חיפוש שלוקח
המימוש שאנחנו נתמקד בו בפרק זה נקרא ערימה, והוא סוג מיוחד של עץ בינארי המאפשר לנו לעדכן ב
השם ערימה נובע מהעובדה שבמבט ראשוני, הכל נראה די מבולגן ולא ברור.
אבל ברגע שמבינים את הסדר של הדברים , מקבלים הרבה כוח עליו נדבר מיד.
הגדרה ערימה היא עץ בינארי שהערך בכל קודקוד גדול יותר או שווה לזה של ילדיו אם הם קיימים.
מסקנה מיידית מהגדרה זו היא שהערך הגדול ביותר של ערימה תמיד יהיה בשורש שלה, כמו בערימה שלמעלה. מה שכן חשוב לשים לב זה שאין משמעות לסדר בכל קומה בעץ מבחינת ההגדרה הזאת .
אם נרצה למחוק את הערך המקסימלי למשל, נמחק את הערך מהשורש, אבל הפעם אין שום צורך לבדוק כמה ילדים יש או לחפש את האיבר בהדפסת inorder, פשוט מאוד מחליפים את ערך הקודקוד בערך המקסימלי של אחד מילדיו. התהליך הזה מתבצע באופן ריקורסיבי עד להגעה לעלה. אותו נוכל להסיר בלי לדאוג.
אם נרצה להכניס אלמנט חדש אם כן , נרצה להכניס אותו כעלה בזמן
למשל אם נרצה להכניס 52 לערימה מעל (אחרי שמחקנו את 83).
כמו בעץ בינארי גם פה פעולות ההכנסה וההוצאה חסומות בגובה העץ, שבמקרים מסויימים יכול להיות
הכנסה
המטרה של ההגדרה הזאת היא להגביל את חופש התנועה בהחלטה היכן למקם ערך חדש שנכנס. כעת הגבלנו את האופצייה הזאת. כעת אופן ההכנסה של ערכים עדיין יהיה מלמטה למעלה, רק שהפעם את העלה נכניס במטרה למלא את הקומה ה
גם אם הכנסנו ערך שלא מתאים, פשוט נבצע החלפות כמו מקודם.
הוצאה
כדי להדגים מה לעשות בהליך הוצאה נסתכל על הערימה הבאה
לא משנה איזה איבר נרצה להוציא , כל אחד מהם פרט ל
למשל , אם נרצה להוציא את 41, נשים את
כעת הובלנו לכך שגובה העץ יהיה חסום ב logn וכל הפעולות עדיין חסומות בגובה העץ ולכן פעולות ההכנסה וההוצאה יהיו
אומנם דיברתי במונח הכולל של ערימה אבל חשוב לציין שיש שני סוגים של ערימות שנוכל לבנות (את שניהם נבנה בידיוק באותו האופן עם הבדל אחד קטן שתיכף אסביר אותו).
זאת הערימה שאיתה עבדנו עד כה, ערימה שבה לקודקוד עם ערך מסויים, אם יש ילדים אז הם יותר קטנים מהקודקוד. כל הפעולות הנ״ל אלה הפעולות שדיברנו עליהם למעלה.
הערימה הזאת עובדת אותו הדבר ההבדל היחיד זה שלכל קודקוד עם ערך מסויים , אם יש לו ילדים, אז ערכיהם יותר גדולים ממנו למשל:
שני הבדלים עיקריים , הראשון , ששליפת הערך ב
ההבדל השני הוא שכל פעולות ההחלפה בערימת מקסימום מבוצעות על יחס הסדר ״גדול או שווה״. בערימת מינימום פעולות ההחלפה יבוצעו בהתאם ליחס הסדר ״קטן שווה״.
עד כה דיברנו על ערימה כעץ בינארי כמעט מלא. אבל גם ציינו שיש כמה נקודות בעייתיות שצריך לעלות. למשל , איך אני מכניס איבר לתחתית העץ ב
נוכל לנצל את המגבלה ששמנו על ערימה, כדי לממש את הערימה בלי מצביעים בכלל, אם יש
כמו שאמרנו , עבור
האיברים במערך יהיו לפי הקומות משמאל לימין (ככה גם הגדרנו את ה״הירכייה״ בעץ בינארי כמעט מלא). סך הכל המערך יהיה ככה:
במינוח מתמטי , לכל
כמו כן עבור
חשוב לשים לב שמהסיבה הזאת האינדקס מתחיל ב1 לכן ניתן לזוז בחופשיות במערך כדי להשיג את הערכים המבוקשים.
על כל מספר
הסיבה שאני מזכיר את זה פה כי בצורה הבינארית אפילו יותר קל לגשת לאינדקסים של הילדים בערימה עבור הקודקוד באינדקס
ואם נרצה לעלות ברמה ולהגיע לאינדקס של קודקוד האבא של
מורידים פשוט את הספרה האחרונה.
אם נרצה למחוק איבר אנחנו יודעים איפה נמצא האיבר האחרון שאפשר למחוק באופן תקין (בסוף המערך) ונוכל פשווט להחליף בין הערכים בזמן קבוע, לזוז ולבצע החלפות כמו שהגדרנו בזמן לוגריתמי.
נקודה חשובה, המחיר שאנחנו משלמים על הבנייה הזאת של ערימה, היא חיפוש, בניגוד לעץ חיפוש בינארי, כאן לא מוגדר באמת יחס סדר על האיברים ולכן נצטרך לחפש איבר בזמן ליניארי בתוך המערך, רק לאחר מציאת האינדקס שלו נוכל לבצע את שאר הפעולות . ההערה הזאת חשובה, כי בעצם אם המבנה שלנו נועד למחוק ולחפש איברים בתוכו אולי ערימה היא לא המצב המתאים, ערימה נועדה לממש באופן האידיאלי את התור עדיפויות שהגדנו, ומטרתו העיקרית היא הסרה של הראש וזמן לוגריתמי ומשיכתו בזמן קבוע.
לכאורה, נוכל לבנות את הערימה על ידי הכנסה של האיברים למערך ריק או עץ ריק וזה ייקח לנו בדומה למימוש של עץ בינארי
לשם הבנייה של הערימה ב
בסופו של דבר, המטרה היא להפוך את הערכים של האינדקסים האפורים (אלה שהם חלק מתת העץ של תת המערך שנתנו בקלט) לערימה תקינה.
לשם הנוחות שבהגדרת הפעולה הזאת, נניח שכל תתי העצים של ששורשם הם הילדים של
כעת הפעולה תעבוד באופן הבא
בפסודו קוד:
המשתנה maxing מחזיק בתוכו את האינדקס של השורש המקורי של תת העץ איתו אנחנו עובדים (גם אם הוא משתנה כתוצאה מהחלפה).
אנקדוטה החלפת משתנים:
אנחנו מכירים את השיטת הקלאסית להחלפת ערכים משתנים באמצעות משתנה עזר
אך ניתן לעשות את זה בלי משתנה עזר על ידי האופרטור הבינארי
function swapWithXOR (a, b) {
a = a^b;
b = a^b;
a=a^b;
console.log("result => a: " + a + ", b: " + b)
}
(להסבר נוסף על אופרטורים בינאריים לחצו על הלינק).
הוכחה של הטענה:
בשורה השנייה אנחנו מבצעים את זה
ובשורה השלילית
נבין רגע איך בונים את הערימה באופן כללי על ידי הפסודו קוד הבא
אנחנו בונים את הערימה מלמטה למעלה ויש לזה חשיבות רבה. אנחנו יודעים מ הוכחות בעצים שיש לעץ כמעט מלא
באופן מתמטי, אם עומק העץ המלא הוא
אני לא אפרט למה זה נכון, בגדול מדובר פשוט בסכום של סדרה חשבונית ונקבל
ולכן פעולה הבנייה שייכת ל
אנחנו עוד נדבר על סוגי מיונים חשובים בהמשך, אבל הייתי רוצה לדבר על המיון הזה בפרק המתאים לו כיוון שהקונספט עוד ברור לכל מי שקרא עד כה.
נניח שיש לנו מערך עם
סיבוכיות אחרי כל בניית הערימה והחלפת האיבר הראשון אנחנו יודעים בוודאות שכל תתי העצים לאחר החלפה בין השורש לבין האיבר האחרון הם ערימות בעצמם, ולכן התיקון יהיה חסום במספר האיטרציות שביצענו כלומר