Menu
Coddy logo textTech

Hash Table (טבלת גיבוב)

עודכן לאחרונה

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

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

סיבוכיות זמן

פעולהממוצעהמקרה הגרוע
הכנסהO(1)O(n) (כל המפתחות מתנגשים)
חיפושO(1)O(n)
מחיקהO(1)O(n)
זיכרוןO(n)O(n)

מושגי מפתח

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

דוגמה מפורטת

הכנסת המפתחות 20, 34, 9, 13 לטבלה עם 7 דליים, בעזרת hash(k) = k % 7:

צעדמבנהפעולה
הכנסת 20דלי 6: [20]20 % 7 = 6, דלי 6 ריק, שומרים את 20
הכנסת 34דלי 6: [20, 34]34 % 7 = 6, התנגשות עם 20, מוסיפים את 34 לשרשרת
הכנסת 9דלי 2: [9]9 % 7 = 2, דלי 2 ריק, שומרים את 9
הכנסת 13דלי 6: [20, 34, 13]13 % 7 = 6, התנגשות, מוסיפים את 13 לשרשרת של דלי 6
חיפוש 34דלי 6: [20, 34, 13]34 % 7 = 6, סורקים את השרשרת: 20 לא, 34 תואם: נמצא

מתי להשתמש בטבלת גיבוב

השתמשו בה כאשרהימנעו ממנה כאשר
אתם צריכים הכנסה, חיפוש ומחיקה לפי מפתח של O(1) בממוצעאתם צריכים שהמפתחות יישמרו בסדר ממוין: השתמשו ב-BST מאוזן
למפתחות אין סדר שימושי ואתם בודקים רק שייכות בהתאמה מדויקתאתם צריכים שאילתות טווח או חיפוש של המפתח הקרוב ביותר
אתם יכולים להרשות לעצמכם קצת זיכרון נוסף לדליים ולמקדם עומס נמוךהזיכרון מוגבל מאוד וכל בית חשוב
קיימת פונקציית גיבוב טובה לטיפוס המפתח שלכםזמן התגובה במקרה הגרוע חייב להיות חסום: התנגשויות הופכות אותו ל-O(n)

קוד Hash Table

מימוש נקי של Hash Table שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.

קוד Hash Table ב-Python

Python
1class HashTable:2    def __init__(self, size=8):3        self.size = size4        self.buckets = [[] for _ in range(size)]5
6    def _index(self, key):7        # Hash the key to a bucket; different keys can collide8        return sum(ord(ch) for ch in key) % self.size9
10    def set(self, key, value):11        bucket = self.buckets[self._index(key)]12        for i, (k, _) in enumerate(bucket):13            if k == key:14                bucket[i] = (key, value)  # update existing key15                return16        bucket.append((key, value))  # chain on collision17
18    def get(self, key):19        for k, v in self.buckets[self._index(key)]:20            if k == key:21                return v22        raise KeyError(key)23
24    def delete(self, key):25        bucket = self.buckets[self._index(key)]26        for i, (k, _) in enumerate(bucket):27            if k == key:28                del bucket[i]29                return30        raise KeyError(key)31
32
33table = HashTable()34table.set("apple", 3)35table.set("banana", 7)36table.set("cherry", 5)37
38print("apple  ->", table.get("apple"))39print("banana ->", table.get("banana"))40table.set("apple", 10)41print("apple  ->", table.get("apple"))42table.delete("banana")43print("bucket sizes:", [len(b) for b in table.buckets])
להריץ את הקוד הזה בעורך ה-Python אונליין

שאלות נפוצות על טבלת גיבוב

מהי התנגשות גיבוב ואיך פותרים אותה?
התנגשות קורית כששני מפתחות שונים מגובבים לאותו דלי. שני הפתרונות הנפוצים הם שרשור נפרד (כל דלי מחזיק רשימה, ומפתחות מתנגשים מתווספים אליה, כמו כאן) ומיעון פתוח (חיפוש התא הריק הבא במערך). שניהם שומרים על חיפושים נכונים; הם שונים בפריסת הזיכרון ובביצועים תחת עומס גבוה.
מהי סיבוכיות הזמן של טבלת גיבוב?
הכנסה, חיפוש ומחיקה הם O(1) בממוצע כשפונקציית הגיבוב מפזרת את המפתחות באופן שווה ומקדם העומס נשמר נמוך. במקרה הגרוע, כשכל מפתח מתנגש לאותו דלי, הם מידרדרים ל-O(n), ולכן פונקציית גיבוב טובה ושינוי גודל חשובים.
מהו מקדם עומס?
מקדם העומס הוא מספר הרשומות השמורות חלקי מספר הדליים. ככל שהוא גדל, השרשראות מתארכות והפעולות מאטות, ולכן רוב טבלאות הגיבוב משנות גודל (מגבבות מחדש למערך גדול יותר) ברגע שהוא חוצה סף כמו 0.75.
מתי כדאי להשתמש בטבלת גיבוב במקום בעץ חיפוש בינארי?
השתמשו בטבלת גיבוב כשצריך רק חיפושים בהתאמה מדויקת ורוצים מהירות של O(1) בממוצע בלי דרישת סדר. השתמשו בעץ חיפוש בינארי מאוזן כשצריך מפתחות בסדר ממוין, שאילתות טווח או חיפוש קודם ועוקב, שטבלת גיבוב לא יכולה לבצע ביעילות. BST נותן פעולות מובטחות של O(log n), ואילו טבלת גיבוב מוותרת על ההבטחה הזו בתמורה לביצועים ממוצעים מהירים יותר.
מה ההבדל בין שרשור נפרד למיעון פתוח?
שרשור נפרד שומר מפתחות מתנגשים ברשימה לכל דלי, כך שדלי יכול להחזיק רשומות רבות והטבלה אף פעם לא באמת מתמלאת. מיעון פתוח שומר הכל בתוך המערך עצמו ומחפש את התא הפנוי הבא בהתנגשות, מה שידידותי למטמון אבל מידרדר בחדות כשמקדם העומס מתקרב ל-1 ודורש טיפול זהיר במחיקות. שרשור עומד במקדמי עומס גבוהים יותר; מיעון פתוח משתמש בזיכרון בצורה דחוסה יותר.
למה אי אפשר לסמוך על טבלת גיבוב שתשמור את המפתחות בסדר ההכנסה או בסדר ממוין?
פונקציית גיבוב מפזרת את המפתחות בין הדליים בכוונה כדי למנוע התקבצות, ולכן סדר המעבר משקף את פריסת הדליים, לא את סדר ההכנסה או סדר המיון. אם צריך סדר, השתמשו במפה או עץ מסודרים, או במבנה כמו מפה ששומרת על סדר ההכנסה (למשל dict של Python שומר על סדר ההכנסה, אבל זו הבטחה של השפה, לא תכונה מובנית של טבלת גיבוב). לעולם אל תניחו שסדר המעבר תואם את סדר ההכנסה אלא אם השפה מבטיחה זאת במפורש.
איור של שפות התכנות ב-Coddy

לשלוט באלגוריתמים עם Coddy

להתחיל