קודי-הפמן שימושיים מאוד וטכניקה אפיקטיבית לדחיסת מידע , שחוסכת 90% − 20% בד"כ , תלוי במאפייני המידע שצריכים לדחוס . נסתכל על המידע כסדרה של אותיות , האלגוריתם החמדן משתמש בטבלה של תדיריות עבור כל אות לבנות דרך אופטימלית לייצג את את האותיות כמחרוזת בינארית .
נגדיר קוד בינארי חוקי לא״ב כפונקצייה כך שהתכונות הבאות מתקיימות
חח״ע
שרשור של ביטים יכול לייצר רק שרשור אחד של תווים מהא״ב.
דוגמה טובה לכך היא ASCII שבו כל תו משתמש ב 8 ביטים ולכן יש לכלהיותר תווים אפשריים ויש רק דרך אחת לקודד כל תו. באופן כללי, תמיד קיים קוד בינארי לא״ב כלשהו כך שאורך כל קידוד הוא .
דוגמה לקוד שלא מקיים את התכונה השנייה היא עבור הקידוד :
c()
a
0
b
01
c
11
d
1
באופן הזה נקבל ש ו יקודדו לאותה מחרוזת בינארית.
prefix-free code
קוד תחילי עבור א״ב הוא פונקצייה כך ש:
חח״ע
לכל שתי תווים שונים בא״ב יתקיים שהפעלת c על אחד מהם היא לא רישא של השני ולהיפך
c()
a
000
b
01
c
1111
d
001
e
1110
f
110
בטבלה הראשונה c(d) הוא רישא של c(c) וכאן זה תרחיש שלא יתקיים.
prefix-free tree
נוכל להתייחס ל prefix-free code כעץ בינארי באופן הבא:
התווים הם עלים
הבן השמאלי משוייך ל 0 והימני ל 1
הוא שרשור של הביטים מהשורש לעלה.
למשל עבור הטבלה למעלה העץ ייראה כך
Info
כמו כן נשים לב שקריאה של מחרוזת בינארית בשיטה הזאת תהיה קריאה של המחרוזת משמאל לימין עד שמגיעים לעלה וזה הופך להיות תו ואז ממשיכים מהתחלה מהשורש כדי להגיע לתו הבא.
נסמן: את העומק של ב עץ שמייצג קוד תחיליות . יתקיים:
(הסימון מייצג את מספר האיברים במחרוזת הבינארית)ֿ
optimal prefix-free code
נניח שיש לנו קובץ מאוד גדול ואנחנו רוצים לקודד את האותיות בקובץ על ידי שימוש ב prefix-free code שמביאה את ההגודל הקובץ המקודד למינימום.
נסמן את קבוצת האותיות בקובץ ב ונסמן לכל את שזה מספר הפעמים שהתו מופיע בקובץ.
אם נשתמש בקוד תחילי עם עץ אז הגודל הכולל לקובץ אחרי קידוד יהיה :
זה בעצם הסכום של כל התדירות לאות כפול העומק שלה בעץ כלומר כמה פעמים נצטרך לרדת בעץ כדי להגיע לאות הזאת.
נרצה לבנות עץ קוד תחילי אופטימלי שעבורו COST יהיה מינימלי. נשים לב שאנחנו מקבלים כקלט את הא״ב והתדירות של כל תו בא״ב.
השאיפה שלנו היא להגיע למצב שבוא אנחנו בונים Variable length code שזה אומר שנשים קוד קטן יותר לאותיות עם תדירות גבוהה וקוד ארוך לאותיות עם תדירות נמוכה. למשל עבור הקידוד והתדירויות
נרצה להגיע למצב הבא
השאלה היא איך מגיעים למצב כזה?
כיוון אינטואיטיבי היה למיין את כל התווים לפי התדירות ולכל שזה התו בדירוג ה להגדיר שקוד שלו יהיה ככה נבטיח שהתו עם הקידוד ה יהיה בעומק ה בעץ. עם זאת, זה לא פתרון אופטימלי למשל עבור הקידוד הנ״ל
העץ השמאלי הוא הרגיל והעץ הימני הוא האופטמילי וזה בניגוד לאינטואיצייה שתיארתי למעלה שהייתה נותנת עץ אחר.
בשביל שנוכל לבנות את המבנה האופטימלי הזה בצורה פורמלית ולהוכיח את נכונותו כאלגוריתם חמדני נרצה להוכיח את הטענה הבאה:
למה עץ קוד תחילי אופטימלי לא מכיל קודקוד פנימי עם ילד אחד, כלומר הוא עץ שלם
נניח בשלילה שלעץ תחילי אופטימלי ישנו קודקוד פנימי עם ילד אחד . נזכיר רק שקודקוד פנימי לא משוייך לאף תו ולכן מחיקה שלו לא אמורה להשפיע על מספר התווים בקידוד אלא רק על אורך של קידוד כלשהו.
לפי אלגוריתם מחיקה מעץ בינארי , זאת מחיקה די פשוטה שכל מה שהיא עושה זה להקטין את עומק הילד , במקרה הזה , ב1. נעשה זאת ונסתכל על אחד העלים ש הוא חלק מהמסלול שלו נסמנו ואת כעץ המתקבל מהפעולה הזאת
בסתירה לכך ש עץ קוד תחילי אופטימלי.
בניית Huffman code
נרצה ללכת על משהו דומה לאינטואיצייה שתיארנו למעלה, במקום לתת את המסלול הכי קצר לתווים עם תדירות נמוכה נרצה ללכת בגישה של להעניק את המסלול הכי ארוך לתווים עם התדירות הכי גבוהה.
נראה אלגוריתם חמדן שמממש עץ קוד תחילי אוטפימלי שמראה בידיוק את הנ״ל. הוכחת נכונות האלגוריתם מבוססת על התכונות של אלגוריתם חמדני שהם הבחירה החמדנית ו תת המבנה האופטימלי . נראה את הפסודו קוד ואז נסביר ונוכיח אותו
HUFFMAN(E)
n = |E|
Q = E
for i=1 to n-1
do allocate a new node z
z.left = x = GET-MIN(Q)
z.right = y = GET-MIN(Q)
f[z]= f[x]ִ+f[y]
INSERT(Q,z)
return GET-MIN(Q)
אם הם התווים עם התדירות הנמוכה ביותר נגדיר כך שהוא יהיה אבא של בעץ עם תדירות : וכעת ממשיכים לעבוד על ה א״ב .
האלגוריתם בונה עץ חילי אופטימלי בשיטת bottom-up, כלומר קודם כל בונים את העלים ועולים למעלה לשורש.
מתחילים בא״ב בגודל וכל פעם ממזגים באופן שתיארנו למעלה וכך מקטינים את ב אחד כל פעם עד שמגיעים לשורש. זאת בעצם ערימת מינימום בינארית לפי יחס סדר על התדירויות כך שבכל איטרציה מבצעים את המיזוג המתואר למעלה ומכניסים את לערימה ובריקרוסיה הולכים ל א״ב בלי x,y אבל כם עם z . ככה מבצעים איטרציות עד שנשאר קודקוד אחד, השורש של העץ, הוא זה שחוזר גם בשורה האחרונה.
דוגמת הרצה עבור
זמן הריצה עבור קבוצת האותיות: אתחול ערימה בינארית עם קודקודים הוא ב והלולאה מבוצעת פעמים כאשר כל פעם שולפים את שתי המינימום ב וסך הכל זמן הריצה יהיה .
הוכחת נכונות
תכונת הבחירה החמדנית
קודם כל נבין מהי הבחירה החמדנית שעשינו כאן, עבור א״ב בגודל לפחות 2 ולכל אות מוגדרת התדירות , אזי עבור שתי האותיות עם התדירות הנמוכה ביותר קיים עץ קוד תחילי אופטימלי כאשר הם עלים אחים והם העמוקים ביותר בעץ.
הוכחה:
יהי עץ קוד תחילי אופטימלי עבור והתדירויות הנכונות. ניקח את העלה העמוק ביותר בעץ נמסנו . כיוון ש אופטימלי, לאבא של יש ילד נוסף והבן הזה חייב להיות על גם הוא (אחרת הוא לא העמוק ביותר בעץ). נסמן את האח ונשים לב שהוא גם העלה הנמוך ביותר בעץ.
בעץ אנחנו יודעים ש הם עדיין עלים איפה שהוא בעץ ויתקיים
(זה עניין סמנטי בלבד כי אנחנו ל יודעים את המיקומים של בעץ אלא רק שהם יותר ״גבוהים״ מהעלים הנמוכים ביותר, כמו כן האפשרות שהם שווים לא נשללת בא״שׁ הזה).
כעת נתבונן בעץ שבו מחליפים את עם ואת עם (בלי הגבלת הכלליות) ונסמן את העץ החדש
כלומר
ולכן
נשים לב שהגורמים היחידים שלא יצטצמטו הם ההחלפות שעשינו ולכן
נשים לב שכל הביטויים האלה גדולים מ 0 ולכן קיבלנו שההפרשת בין העלויות של העצים גדול מ 0 כלומר העלות של גבוהה או שווה מהעלות של אבל עץ אופטימלי ולכן היא לא יכולה להיות גבוהה יותר כלומר
כלומר גם הוא עץ אופטימלי.
תת המבנה האופטימלי
כעת נוכל להוכיח את התכונה השנייה של אלגוריתם חמדן. בניגוד לחיפוש בינארי, באלגוריתם הזה אנחנו צריכים לזכור את כל הבחירות החמדניות המקומיות שעשינו כדי להגיע לתוצאה כיוון שאלו בונות את העץ. אנחנו נרצה לאפיין ולהוכיח שהמבנה של האלגוריתם הוא אכן תת מבנה אופטימלי בצורה שתתאים לבחירה של האלגוריתם ובפרט להראות שאם הריקורסיה מחזירה פתרון אופטימלי אז ״מיזוג״ הפתרון עם הבחירה החמדנית נותן פתרון אופטימלי למופע מקומי.
יהי א״ב מגודל לפחות 2, כאשר לכל תו מוגדרת התדירות המתאימה לו. אם ניקח שתי התווים עם התדירות הנמוכה ביותר ונגדיר איתם שיקיים ונבנה עץ אופטימלי עבור אזי : העץ המתקבל מ על ידי הוספת כבנים של הוא עץ קוד תחילי אופטימלי עבור .
הוכחה :
נשים לב שכאן המטרה היא שונה בניגוד להוכחה של התכונה הראשונה, אנחנו רוצים להראות כאן באופן כמעט אינדוקטיבי שהרחבת המבנה שלנו על ידי הבחירה החמדנית באופן ריקורסיבי תוביל לפתרון אופטימלי. הטענה הראשונה שהוכחנו תעזור לנו בכך כיוון שאנחנו יודעים בוודאות שקיים עץ אופטימלי שמקיים את תכונת הבחירה החמדנית כלומר נוכל להניח שתת המבנה האופטימלי שלנו הוא אכן כזה.
נשים לב ש וגם זאת בגלל שבכלל לא נגענו בהם. סך הכל נקבל
כלומר קיבלנו
כעת, ניקח עץ תחילי אופטימלי עבור . ע״י למת הבחירה החמדנית, נניח בלי הגבלת הכלליות ש אחים ב .
ניקח עץ קוד תחילי המתקבל מ על ידי החלפת וקודקוד האבא שלהם בעלה חדש .
מההגדרה יתקיים ש עץ קוד תחילי עבור . כמו כן, מההוכחה הקודמת ניתן לראות ש .
סך הכל מכל העצים שבנינו יתקיים
אבל
זה נכון בגלל ש אופטימלי. כלומר קיבלנו שיוויון ולכן אופטימלי. מה שעשינו כאן בעצם זה השתמשנו בעץ תחילי אופטימלי שמקיים את תכונת הבחירה החמדנית והראנו שהעלות שלו שקולה לעץ שמתקבל מהבנייה הריקורסיבית שעשינו. כלומר הראנו שהצעד החמדני אכן מביא אותנו לפתרון אופטימלי באופן ריקורסיבי. השלב הראשון היה למצוא את הקשר בין העלויות, לאחר מכן הראנו שאם עושים את זה על עץ שמקיים את למת הבחירה החמדנית אז הוא עץ תחילי אופטימלי.