אוניברסיטת בן גוריון בנגב המחלקה למדעי המחשב מבני נתונים תשע"ה סמסטר ב מבני נתונים תרגיל 4 תאריך פרסום: 51.2.12 תאריך הגשה: 12...5 מרצה ומתרגלים אחראים: פרופ' איתן בכמט, ענר בן-אפרים, דולב סוקר נושאי העבודה : רקע: אלגוריתם תכנון מבנה נתונים יעיל לאלגוריתם Lempel-Ziv ניתוח זמן הריצה של החלקים השונים באלגוריתם מימוש המבנה ב- Java ובדיקתו :Lempel-Ziv אלגוריתם לדחיסת נתונים. האלגוריתם מאפשר לשחזר את המידע הדחוס במלואו. משפחה של אלגוריתמים שמקורם בשני אלגוריתמים עיקריים שפותחו על ידי יעקב זיו ואברהם למפל בשנת 7711-8 יעקב זיו אברהם למפל GIF image אלגוריתמים ששייכים למשפחה זו של אלגוריתמים, מיושמים באפליקציות כגון ZIP ו -.compression
.LZ78 - בעבודה זו נראה את אחת מהגרסאות של האלגוריתם שמקורו באלגוריתם משנת 7718 הרעיון שעומד בבסיסו של הקידוד הוא: כל מילה בקידוד היא המילה הארוכה ביותר שנראתה עד כה בתוספת של אות אחת. על מנת לעשות חיפוש יעיל של המילים שכבר קודדו נממש מבנה נתונים עצי שנקרא Trie או Prefix tree )ראו דוגמה בהמשך(. העץ יהיה מעין מילון שנבנה בצורה דינמית ומייצג מילים שכבר קודדו בצורה יעילה. מבנה הנתונים הוא היררכי. לכל צומת יש אבא ורשימה של ילדים. בנוסף כל צומת צריכה לשמור שני שדות: אינדקס המסמן באיזה שלב הצומת הזאת נוצרה ותו שמחבר את הצומת הזאת לאבא שלו )מידע שמופיע על הצלעות בדוגמה המופיעה מטה(. צלעות שיוצאות מצומת מסוימת מייצגות אותיות שונות ששיכות לא"ב של הטקסט המקודד. השרשור של האותיות במסלול מהשורש לצומת מסוימת מייצגות מילים או רישאות של מילים שקודדו. לפניכם הפסאודו קוד של אלגוריתם הקידוד המקבל Text כקלט. מומלץ לעבור עליו במקביל עם הדוגמה המופיעה בהמשך. הפסאודו קוד של האלגוריתם: 1. אינדקס= 0 צור רשימת זוגות ריקה. צור שורש של העץ. סמן אותו בערכו של האינדקס. כל עוד אורך ה > Text 0: 4.7. קדם את האינדקס ב- 7. 4.2. מצא את הרישא )prefix( הארוכה ביותר של Text שמופיעה בעץ, במסלול כלשהו מהשורש לאיזושהי צומת v 4.3. אם אורך הטקסט > אורך הרישא 4.3.7. הוסף לצומת v בן חדש, סמן אותו באינדקס, חבר ביניהם בתו שבא אחרי הרישא ב-.Text 4.3.2. קצץ מתחילת הטקסט את הרישא והאות שבאה אחריה. 4.4. הוסף לרשימת הזוגות, זוג שמורכב מהאינדקס של צומת v ומהאות שבאה אחרי הרישא 2 )במידה ואין אות כזו, כלומר הגענו לסוף המילה הוסיפו תו '*' (..7.2.3.4 1 הניחו שהטקסט המקורי לא יכיל את התו '*'. החיפוש יתבצע על ידי טיול במקביל על הטקסט ועל הצמתים של העץ: מתחילים מהאות הראשונה של הטקסט ומהשורש של העץ. נסמן את האות הנוכחית בטקסט ב- c ואת הצומת הנוכחית בעץ ב- v. מחפשים מבין הבנים של v האם מישהו מחובר אליו באות c. אם כן, מתקדמים אליו )עכשיו הוא ) v ומתקדמים בטקסט לאות הבאה )עכשיו היא c(. חוזרים על התהליך עד שלא ניתן להתקדם מצומת v עם האות c. כשמסיימים, מחזירים את הצומת v )זו הצומת שאליה נחבר בן חדש בשלב 4.3.1(. 2 ראו את השלב האחרון של הדוגמה למטה.
הדגמה של ריצת האלגוריתם על הטקסט הבא B: בונים עץ התחלתי ששורשו מסומן ב- 0. מאתחלים את רשימת הזוגות אשר מייצגת את הקידוד להיות רשימה ריקה ואת האינדקס הרץ מאתחלים לאפס. מקדמים את האינדקס ל- 7 )אנו כעת במילה הראשונה שמקודדת(. עוברים על הטקסט. מתחילים עם האות הראשונה בטקסט ועם השורש של העץ. לשורש אין בן שמחובר אליו ב- a ולכן עוצרים את החיפוש כאשר v זה השורש והרישא היא מילה ריקה. מוסיפים לצומת v בן חדש המסומן באינדקס 7, אשר מחובר לאבא שלו באות a. מוסיפים לרשימת הזוגות זוג חדש (a,0) אשר מסמן שהמילה הראשונה מורכבת מהרישא שנמצאת על המסלול מהשורש לצומת המסומנת ב- 0 )מילה ריקה במקרה זה( בתוספת של האות a. מורידים מהטקסט את הרישא ואת האות a וחוזרים עם הטקסט המקוצץ acgacgat לתחילת הלולאה.
מקדמים את האינדקס ל- 2. מחפשים את הרישא הארוכה ביותר של הטקסט שנמצאת בעץ a. הרישא מסתיימת בצומת המסומנת ב- 7. מוסיפים לצומת בן חדש עם אינדקס 2 ומחברים אותה לצומת האב שלה עם האות שבאה אחרי הרישא c. מוסיפים לרשימת הזוגות את הזוג (c,1) שמסמן שהמילה השנייה מורכבת מהרישא שנמצאת על המסלול מהשורש לצומת 7, כלומר a ותוספת של האות c. מקצצים את המילה, ונשארים עם.gacgat מקדמים את האינדקס ל- 3. מחפשים את הרישא הארוכה ביותר של הטקסט שנמצאת בעץ מילה ריקה. הרישא מסתיימת בצומת המסומנת ב- 0 )בשורש(. מוסיפים לצומת בן חדש עם אינדקס 3 ומחברים אותה לצומת האב שלה עם האות שבאה אחרי הרישא g. מוסיפים לרשימת הזוגות את הזוג (g,0) שמסמן שהמילה השלישית מורכבת מהרישא שנמצאת על המסלול מהשורש לצומת 0, כלומר מילה ריקה ותוספת של האות g. מקצצים את המילה, ונשארים עם.acgat
מקדמים את האינדקס ל- 4. מחפשים את הרישא הארוכה ביותר של הטקסט שנמצאת בעץ.ac הרישא מסתיימת בצומת המסומנת ב- 2. מוסיפים לצומת בן חדש עם אינדקס 4 ומחברים אותה לצומת האב שלה עם האות שבאה אחרי הרישא g. מוסיפים לרשימת הזוגות את הזוג (g,2) שמסמן שהמילה הרביעית מורכבת מהרישא שנמצאת על המסלול מהשורש לצומת 2, כלומר ac ותוספת של האות g. מקצצים את המילה, ונשארים עם.at מקדמים את האינדקס ל- 5. מחפשים את הרישא הארוכה ביותר של הטקסט שנמצאת בעץ a. הרישא מסתיימת בצומת המסומנת ב- 7. מוסיפים לצומת בן חדש עם אינדקס 5 ומחברים אותה לצומת האב שלה עם האות שבאה אחרי הרישא t. מוסיפים לרשימת הזוגות את הזוג (t,1) שמסמן שהמילה החמישית מורכבת מהרישא שנמצאת על המסלול מהשורש לצומת 7, כלומר a ותוספת של האות t. מקצצים את המילה, ונשארים עם מילה ריקה. הקידוד הסופי עבור המילה B הינו רשימת הזוגות שאגרנו במשתנה.Pairs
כעת נניח שהטקסט B הוא הטקסט הקודם בתוספת של האות a: עושים את חמשת השלבים הקודמים. בשלב השישי, מקדמים את האינדקס ל- 6. מחפשים את הרישא הארוכה ביותר של הטקסט שנמצאת בעץ a. הרישא מסתיימת בצומת המסומנת ב- 7. הרישא הארוכה ביותר מגיעה לסוף הטקסט ואין איך להאריך אותה יותר באות אחת. ולכן, לא עושים יותר שינויים על העץ. כדי לא לאבד אינפורמציה על הקידוד של המילה הזו אנחנו מוסיפים את הזוג (*,1) לרשימת הזוגות. שחזור: ניתן לשחזר את הטקסט המקודד מתוך רשימת הזוגות בלבד בדרך הבאה: נחזיק מערך. ואינדקס שמתחיל מ 7. האיבר ה 0 במערך יהיה ריק. עבור כל זוג,(i,x) נשרשר למילה שבמקום הi המערך את האות x ונכתוב את התוצאה במקום האינדקס במערך. כמו כן נוסיף את התוצאה לטקסט המשחוזר ונקדם את אינדקס ב 7. )במידה ונתקלנו ב* רק נוסיף את המילה לטקסט המשוחזר( דוגמא:
מימוש האלגוריתם: בעבודה זו תתכננו ותממשו את האלגוריתם המוצג. על המימוש להיות מבוסס Trie. )http://en.wikipedia.org/wiki/trie( לצורך מימוש האלגוריתם סיפקנו לכם את הקבצים הבאים: המחלקה Pair מחלקה המייצגת זוג המורכב מאינדקס ותו. שימו לב כי הערכים מתקבלים בבנאי ואין במחלקה.setters אין לשנות את המחלקה!Pair המחלקה Test מחלקה עם מספר פונקציות לבדיקת נכונות המבנה. הבדיקות אינן מכסות את כל המקרים ומומלץ להוסיף בדיקות משלכם. הממשק LZ הממשק אותו עליכם לממש. אין לשנות את הממשק!LZ המחלקה LempelZiv במחלקה זו תממשו את הממשק.LZ ניתן להוסיף במחלקה פונ' עזר. אין להוסיף בנאים במחלקה!LempelZiv.7.2.3.4 # 1 2 3 4 5 6 7 8 9 10 OPERATION void encode(int[] input) void encode(string input) Pair[] getpairs () String reconstruct(pair[] pairs) String reconstruct () void add(string input) * int maxcodelength() String maxcode() int codes(string prefix) int[] randombinaryseries(int length,double ratio) פעולות הממשק :LZ Running Time O(input) O(input) O(number of pairs) O(pairs) O(n) O(input) O(1) O(h) O(prefix) O(length) * שימו לב: הפעולה add תקראנה רק לאחר קריאה אחת לפחות לencode. רשימת הפעולות הנ"ל נמצאת בממשק Interface( )Java בשם LZ אותו עליכם לממש )implement( בעזרת מחלקה שאתם תכתבו בשם.LempelZiv עליכם לחשוב כיצד אתם ממשים את מבנה הנתונים. תכננו היטב את המחלקות שלהן תידרשו.
פירוט הפעולות * ראו דוגמאות למטה. 1. void encode(int[] input) קלט: מערך input של מספרים בינארים )7,0(. הפונקציה מקודדת את קלט המספרים לפי קידוד.Lempel-Ziv זמן ריצה: ) (. 2. void encode(string input) קלט: מחרוזת.input הפונקציה מקודדת את מחרוזת הקלט לפי קידוד.Lempel-Ziv זמן ריצה: ) (. שימו לב : הפונקציה encode נדרשת לאפס את מבנה הנתונים שלכם ולהתחיל קידוד מחדש למחרוזת/ מערך החדש. 3. Pair[] getpairs() פלט: מערך של Pair או null אם לא הורץ האלגוריתם. הפונקציה מכינה מערך בו יש את כל זוגות הקידוד לפי אלגוריתם.Lempel-Ziv זמן ריצה: ) ( ) (. 4. String reconstruct(pair[] pairs) קלט: מערך של זוגות Pair המייצג קידוד טקסט לפי אלגוריתם.Lempel-Ziv פלט: המחרוזת המשוחזרת ממערך הזוגות הנתונים. הפונקציה משחזרת את הטקסט ממערך הזוגות שהתקבל לפי אלגוריתם.Lempel-Ziv זמן ריצה: ) (. 5. String reconstruct() פלט: המחרוזת המשוחזרת ממערך הזוגות הנתונים, או null אם לא קודד עדיין טקסט. הפונקציה משחזרת את הטקסט ממערך הזוגות שקודד לפי אלגוריתם.Lempel-Ziv זמן ריצה: ) ( מספר הזוגות שקודדו. 6. void add(string input) קלט: מחרוזת.input הפונקציה מקודדת את מחרוזת הקלט לפי קידוד.Lempel-Ziv שימו לב כי נדרש מכם להמשיך את הקידוד הקודם, כאילו היו המחרוזות מחוברות. זמן ריצה: ( ). שימו לב כי בזמן ריצה זה, אינכם יכולים לקודד את כל המחרוזת )החדשה והישנה( יחד. עליכם להמשיך את הקידוד הקיים ולא לבנות את המבנה מחדש. 7. int maxcodelength() 8. String maxcode() פלט: אורך הרישא הארוכה ביותר בקידוד. זמן ריצה: ) (. פלט: הרישא הארוכה ביותר בקידוד. זמן ריצה: ) ( - גובה עץ הקידוד. 9. int codes(string prefix) קלט: מחרוזת.prefix פלט: מספר המופעים של מחרוזת הקלט prefix כרישא של מחרוזת בקידוד. ראו דוגמת הרצה למטה. זמן ריצה: ) (.
10. int[] randombinaryseries(int length,double ratio) קלט: אורך length ויחס.ratio פלט: מערך בינארי באורך.length הפונקציה יוצרת מערך רנדומלי באורך length אשר היחס בין 0 ל 7 במערך הוא.ratio כלומר, ההסתברות לכך שבכל תא במערך יהיה 0 הוא ratio וההסתברות שיהיה בתא 7 היא.1-ratio זמן ריצה: ) (. דוגמת הרצה: פקודה: encode("aacgacgat"); getpairs(); הערות: The answer [(0,a),(1,c),(0,g),(2,g),(1,t)(0,*)] will also be accepted The codes "a","ac","at","acg" The codes "ac", "acg" The new code is the unfinished code starting with "a". The answer 4 will also be accepted. פלט: The Pair array [(0,a),(1,c),(0,g),(2,g),(1,t)] "aacgacgat" 1 1 4 2 0 3 "acg" The Pair array [(0,a),(1,c),(0,g),(2,g),(1,t)(1,*)] "aacgacgata" 5 reconstruct(); codes("acg"); codes("g"); codes("a"); codes("ac"); codes("t"); maxcodelength(); maxcode(); add("a"); getpairs(); reconstruct(); codes("a");
חלק א' סיכום התרגיל: א. תארו בקצרה את המימוש שלכם במסמך מוקלד במילים, אין צורך לכתוב את הקוד מחדש. לדוגמא "הגדרתי מבנה נתונים של מחסנית משורשרת, במחסנית שמרתי את שמות השחקנים והשופטים. בנוסף שמרתי בעץ AVL את תוצאות המשחקים." ב. ג. הסבירו בקצרה את זמן הריצה של כל אחת מן הפעולות הסבירו כיצד אתם עומדים בזמן הריצה לפי המימוש שלכם. גם במקרה זה עליכם להסביר במילים ולא בחלקים מהקוד. לדוגמא "את תוצאות המשחקים שמרתי בעץ AVL לפי תאריך המשחק ולכן שליפה של משחק אחד היא ב( (. הריצו את האלגוריתם שמימשתם עם קלט רנדומלי לפי הפרמטרים הבאים: Length Ratio 200 0.5 200 0.7 200 0.9 200,000 0.5 200,000 0.7 200,000 0.9 לכל שורה בטבלה, הריצו את האלגוריתם כ 700 פעמים וכיתבו את הממוצע של מספר הזוגות בקידוד שקיבלתם בכל אחת מההרצות. מלאו טבלה בדומה לזו: Length 200 200 200 200,000 200,000 200,000 Ratio 0.5 0.7 0.9 0.5 0.7 0.9 Average number of pairs Minimum number of pairs Maximum number of pairs ד. בחרו מחזה/רומן או כל טקסט אחר באורך מעל 70,000 תווים באנגלית ובעברית הריצו את האלגוריתם על כל אחד מהקלטים הנ"ל. הערה: אם הטקסט מכיל את התו '*' התעלמו ממנו. a. כתבו את המקור שממנו לקחתם את הטקסטים. b. כתבו את תוצאות הניסויים. c. נסו להסביר את ההבדלים באורך הקידוד. המסמך צריך להיות בפורמט.PDF
חלק ב' תרגיל מעשי עליכם לממש את האלגוריתם.Lempel-Ziv לתרגיל מצורף קובץ ZIP ובו: המחלקה Pair שאותה אסור לכם לשנות. הממשק LZ שאותו אסור לכם לשנות. המחלקה LempelZiv שאותה עליכם להשלים. מחלקה בשם Test עם מספר פונקציות לבדיקת נכונות המבנה. הבדיקות אינן מכסות את כל המקרים ומומלץ להוסיף בדיקות משלכם. מותר לכם להוסיף קבצים ומחלקות בהתאם לצרכי המימוש שבחרתם. אתם מקבלים את קובץ המחלקה LempelZiv עם מימוש ריק. ניתן להוסיף לה מתודות, ושדות. המחלקה מגיעה עם בנאי ריק שאותו אתם נדרשים למלא. אין להוסיף בנאים נוספים! ניתן וכדאי להוסיף מחלקות נוספות. הערות חשובות ודרישות הגשה: 7. ניתן להניח שהקלט יהיה תקין. לא תקבלו מתכנית הבדיקה שלנו ערך null או טקסט ריק כפרמטר לאף אחת מהפונקציות בממשק. 2. הבדיקות יתבצעו על קלטים בעברית או אנגלית בנפרד, כלומר, לא יהיה טקסט משולב בשתי שפות שונות. 3. הפונקציה add תיקרא רק לאחר קריאה לפונקציה.encode 4. מומלץ להמיר את המספרים הבינאריים למחרוזות ולהשתמש בקידוד ממחרוזת ולא ממספר. 5. בין הקבצים, תקבלו גם קובץ Test.java שישמש אתכם לבדיקה. לאחר שתסיימו את התכנית אתם יכולים להריץ את פונקציית ה- main כדי לדעת אם התכנית עובדת כמו שצריך. הבדיקות אינן מכסות את כל המקרים ומומלץ להוסיף בדיקות משלכם. 6. את העבודה יש להגיש ל.Submission system 1. עליכם להגיש קובץ zip בשם assignment4.zip המכיל בתוכו: Test.java, LZ.java, Pair.java ובה קבצי הג'אווה של העבודה, ללא הקבצים src תיקיית a. parta.pdf לפי הנדרש, בשם PDF מסמך b. EnglishText.txt שני קבצים עם הטקסטים אותם הרצתם בסעיף ד', הטקסט באנגלית בשם c. והטקסט בעברית בשם.HebrewText.txt 8. סביבת העבודה בה תיבדקנה העבודות הינה JavaSE-1.7/8 7. עליכם לדאוג כי עבודותיכם יתקמפלו וירוצו בסביבת eclipse תחת גרסאות Java הנזכרות לעיל. 70. עבודות שלא יתקמפלו יקבלו ציון 0. 77. עבודותיכם יבדקו באמצעות כלי בדיקה אוטומטים הבודקים קורלציה בין עבודות. נא לא להעתיק! להזכירכם, המחלקה רואה בחומרה רבה העתקות. 72. נרצה לראות קוד מתועד, מתוכנן היטב ויעיל שמייצג הבנה.