Prefix Code - Huffman Code

קודי-הפמן שימושיים מאוד וטכניקה אפיקטיבית לדחיסת מידע , שחוסכת 90% − 20% בד"כ , תלוי במאפייני המידע שצריכים לדחוס . נסתכל על המידע כסדרה של אותיות , האלגוריתם החמדן Huffman משתמש בטבלה של תדיריות עבור כל אות לבנות דרך אופטימלית לייצג את את האותיות כמחרוזת בינארית .

נגדיר קוד בינארי חוקי לא״ב Σ כפונקצייה c:Σ{0,1}+ כך שהתכונות הבאות מתקיימות

  1. חח״ע c(σ)=c(σ)σ=σ
  2. שרשור של ביטים יכול לייצר רק שרשור אחד של תווים מהא״ב.

דוגמה טובה לכך היא ASCII שבו כל תו משתמש ב 8 ביטים ולכן יש לכלהיותר 28 תווים אפשריים ויש רק דרך אחת לקודד כל תו. באופן כללי, תמיד קיים קוד בינארי לא״ב כלשהו כך שאורך כל קידוד הוא log|Σ| .

דוגמה לקוד שלא מקיים את התכונה השנייה היא 0001110001 עבור הקידוד :

σ c(σ)
a 0
b 01
c 11
d 1

באופן הזה נקבל ש aaadddaaad ו aabcaab יקודדו לאותה מחרוזת בינארית.

prefix-free code

קוד תחילי עבור Σ א״ב הוא פונקצייה c:Σ{0,1}+ כך ש:

  1. חח״ע
  2. לכל שתי תווים שונים בא״ב יתקיים שהפעלת c על אחד מהם היא לא רישא של השני ולהיפך
σ c(σ)
a 000
b 01
c 1111
d 001
e 1110
f 110

בטבלה הראשונה c(d) הוא רישא של c(c) וכאן זה תרחיש שלא יתקיים.

prefix-free tree

נוכל להתייחס ל prefix-free code כעץ בינארי באופן הבא:

  1. התווים הם עלים
  2. הבן השמאלי משוייך ל 0 והימני ל 1
  3. c(σ) הוא שרשור של הביטים מהשורש לעלה.

למשל עבור הטבלה למעלה העץ ייראה כך
Pasted image 20221119161628.png|350

Info

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

נסמן: dT(σ) את העומק של σ ב עץ T שמייצג קוד תחיליות c . יתקיים:

|c(σ)|=dT(σ)

(הסימון מייצג את מספר האיברים במחרוזת הבינארית)ֿ

optimal prefix-free code

נניח שיש לנו קובץ מאוד גדול ואנחנו רוצים לקודד את האותיות בקובץ על ידי שימוש ב prefix-free code שמביאה את ההגודל הקובץ המקודד למינימום.
נסמן את קבוצת האותיות בקובץ ב Σ ונסמן לכל σΣ את fσ שזה מספר הפעמים שהתו מופיע בקובץ.
אם נשתמש בקוד תחילי c עם עץ T אז הגודל הכולל לקובץ אחרי קידוד יהיה :

COST(T)=σΣfσ|c(σ)|=σΣfσdT(σ)

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

השאיפה שלנו היא להגיע למצב שבוא אנחנו בונים Variable length code שזה אומר שנשים קוד קטן יותר לאותיות עם תדירות גבוהה וקוד ארוך לאותיות עם תדירות נמוכה. למשל עבור הקידוד והתדירויות
Pasted image 20221119182230.png|300
נרצה להגיע למצב הבא
Pasted image 20221119182301.png|300

השאלה היא איך מגיעים למצב כזה?

כיוון אינטואיטיבי היה למיין את כל התווים לפי התדירות ולכל αi שזה התו בדירוג ה i להגדיר שקוד שלו יהיה 1111i times  0 ככה נבטיח שהתו עם הקידוד ה i יהיה בעומק ה i בעץ. עם זאת, זה לא פתרון אופטימלי למשל עבור הקידוד הנ״ל
Pasted image 20221119182347.png|450
העץ השמאלי הוא הרגיל והעץ הימני הוא האופטמילי וזה בניגוד לאינטואיצייה שתיארתי למעלה שהייתה נותנת עץ אחר.

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

למה
עץ קוד תחילי אופטימלי לא מכיל קודקוד פנימי עם ילד אחד, כלומר הוא עץ שלם
נניח בשלילה שלעץ תחילי אופטימלי T ישנו קודקוד פנימי k עם ילד אחד x. נזכיר רק שקודקוד פנימי לא משוייך לאף תו ולכן מחיקה שלו לא אמורה להשפיע על מספר התווים בקידוד אלא רק על אורך של קידוד כלשהו.
לפי אלגוריתם מחיקה מעץ בינארי , זאת מחיקה די פשוטה שכל מה שהיא עושה זה להקטין את עומק הילד , במקרה הזה x, ב1. נעשה זאת ונסתכל על אחד העלים ש x הוא חלק מהמסלול שלו נסמנו m ואת T כעץ המתקבל מהפעולה הזאת

COST(T)=(σΣ/mfσdT(σ))+fm(dT(m)1)<σΣfσdT(σ)=COST(T)

בסתירה לכך ש T עץ קוד תחילי אופטימלי.

בניית 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)

אם x,y הם התווים עם התדירות הנמוכה ביותר נגדיר zΣ כך שהוא יהיה אבא של x,y בעץ עם תדירות : fz=fx+fy וכעת ממשיכים לעבוד על ה א״ב Σ=Σ{z}/{y,x} .
האלגוריתם בונה עץ חילי אופטימלי בשיטת bottom-up, כלומר קודם כל בונים את העלים ועולים למעלה לשורש.
מתחילים בא״ב בגודל n וכל פעם ממזגים באופן שתיארנו למעלה וכך מקטינים את n ב אחד כל פעם עד שמגיעים לשורש.
Q זאת בעצם ערימת מינימום בינארית לפי יחס סדר על התדירויות כך שבכל איטרציה מבצעים את המיזוג המתואר למעלה ומכניסים את z לערימה ובריקרוסיה הולכים ל א״ב בלי x,y אבל כם עם z . ככה מבצעים n1 איטרציות עד שנשאר קודקוד אחד, השורש של העץ, הוא זה שחוזר גם בשורה האחרונה.

דוגמת הרצה עבור
Pasted image 20221119182301.png|300

Pasted image 20221119232410.png

זמן הריצה עבור |Σ|=n קבוצת האותיות: אתחול ערימה בינארית עם n קודקודים הוא ב O(n) והלולאה מבוצעת n1 פעמים כאשר כל פעם שולפים את שתי המינימום ב logn וסך הכל זמן הריצה יהיה O(nlogn).

הוכחת נכונות

תכונת הבחירה החמדנית

קודם כל נבין מהי הבחירה החמדנית שעשינו כאן, עבור א״ב Σ בגודל לפחות 2 ולכל אות σ מוגדרת התדירות fσ , אזי עבור x,yΣ שתי האותיות עם התדירות הנמוכה ביותר קיים עץ קוד תחילי אופטימלי כאשר x,y הם עלים אחים והם העמוקים ביותר בעץ.

הוכחה:
יהי עץ קוד תחילי אופטימלי T עבור Σ והתדירויות הנכונות. ניקח את העלה העמוק ביותר בעץ נמסנו a . כיוון ש T אופטימלי, לאבא של a יש ילד נוסף והבן הזה חייב להיות על גם הוא (אחרת a הוא לא העמוק ביותר בעץ). נסמן את האח b ונשים לב שהוא גם העלה הנמוך ביותר בעץ.
בעץ T אנחנו יודעים ש x,y הם עדיין עלים איפה שהוא בעץ ויתקיים

dT(x)dT(a)dT(y)dT(b)

(זה עניין סמנטי בלבד כי אנחנו ל יודעים את המיקומים של x,y בעץ אלא רק שהם יותר ״גבוהים״ מהעלים הנמוכים ביותר, כמו כן האפשרות שהם שווים לא נשללת בא״שׁ הזה).
כעת נתבונן בעץ שבו מחליפים את x עם a ואת y עם b (בלי הגבלת הכלליות) ונסמן את העץ החדש T

Pasted image 20221120183153.png|350
כלומר

dT(x)=dT(a)dT(a)=dT(x)dT(y)=dT(b)dT(b)=dT(y)

ולכן

COST(T)COST(T)=σΣfσdT(σ)σΣfσdT(σ)

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

=i{a,b,x,y}fidT(i)i{a,b,x,y}fidT(i)=fa(dT(a)dT(a))+fb(dT(b)dT(b))+fx(dT(x)dT(x))+fy(dT(y)dT(y))=fa(dT(a)dT(x))+fb(dT(b)dT(y))+fx(dT(x)dT(a))+fy(dT(y)dT(b))=(fafx)(dT(a)dT(x))+(fbfy)(dT(b)dT(y))

נשים לב שכל הביטויים האלה גדולים מ 0 ולכן קיבלנו שההפרשת בין העלויות של העצים גדול מ 0 כלומר העלות של T גבוהה או שווה מהעלות של T אבל T עץ אופטימלי ולכן היא לא יכולה להיות גבוהה יותר כלומר

COST(T)COST(T)COST(T)COST(T)COST(T)=COST(T)

כלומר גם T הוא עץ אופטימלי.

תת המבנה האופטימלי

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

יהי Σ א״ב מגודל לפחות 2, כאשר לכל תו מוגדרת התדירות המתאימה לו. אם ניקח x,yΣ שתי התווים עם התדירות הנמוכה ביותר ונגדיר איתם zΣ שיקיים
fz=fx+fy ונבנה עץ T אופטימלי עבור Σ=E{z}{x,y} אזי : העץ T המתקבל מ T על ידי הוספת x,y כבנים של z הוא עץ קוד תחילי אופטימלי עבור Σ .

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

נשים לב ש dT(x)=dT(y)=dT(z)+1 וגם σx,yΣ:dT(σ)=dT(σ) זאת בגלל שבכלל לא נגענו בהם. סך הכל נקבל

COST(T)COST(T)=σΣfσdT(σ)σΣfσdT(σ)=fxdT(x)+fydT(y)fzdT(z)=fxdT(x)+fydT(y)(fx+fy)(dT(x)1)=fxdT(x)+fydT(y)fxdT(x)+fyfxdT(x)+fy=fydT(y)+fxfydT(y)+fy=fy+fx=fz

כלומר קיבלנו

COST(T)=COST(T)+fz

כעת, ניקח T עץ תחילי אופטימלי עבור Σ. ע״י למת הבחירה החמדנית, נניח בלי הגבלת הכלליות ש x,y אחים ב T .
ניקח T עץ קוד תחילי המתקבל מ T על ידי החלפת x,y וקודקוד האבא שלהם בעלה חדש z .

Pasted image 20221127211946.png|450

מההגדרה יתקיים ש T עץ קוד תחילי עבור Σ . כמו כן, מההוכחה הקודמת ניתן לראות ש COST(T)=COST(T)+fz .
סך הכל מכל העצים שבנינו יתקיים

COST(T)COST(T)

אבל

COST(T)=COST(T)+fzCOST(T)+fz=COST(T)

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