Presentation is loading. Please wait.

Presentation is loading. Please wait.

הרצאה 07 עצים קרן כליף.

Similar presentations


Presentation on theme: "הרצאה 07 עצים קרן כליף."— Presentation transcript:

1 הרצאה 07 עצים קרן כליף

2 ביחידה זו נלמד: הגדרות יצירת עץ מעברים על עצים חיפוש בעץ ממערכים לעץ
מעץ לרשימה © Keren Kalif

3 הגדרות עץ הוא מבנה נתונים המכיל נתונים היררכיים
למשל: אילן יוחסין, מבנה אירגוני מבנה-נתונים זה מורכב מצמתים (העיגולים הירוקים) ומקשתות (החצים) "שורש" הוא הצומת העליון, אף צומת אחר אינו מכיל קשת אליו "אבא": צומת שיש ממנו קשת לצומת אחר "בן" (או: ילד, או: צומת פנימי): צומת שיש לו אבא כל הצמתים הם בנים פרט לשורש "עלה": צומת שאין לו בנים 1  שורש 3 8 2  בן  אבא 4 9 6 1  בן 5 5 7  עלה © Keren Kalif

4 הגדרות (2) רמה: המרחק של צומת מהשורש מסלול: רצף של צמתים שמתחיל בשורש
השורש נמצא ברמה 0 מסלול: רצף של צמתים שמתחיל בשורש למשל: 1 39 אורך מסלול: מס' הקשתות במסלול שקול לרמה של הצומת שבסוף המסלול אורך המסלול 1 39 הוא 2 גובה העץ: אורך המסלול הארוך ביותר, הרמה המקסימלית גובה עץ זה הוא 3 רמה 1 2 3 1 3 8 2 4 9 6 5 7 © Keren Kalif

5 הגדרות (3) תת-עץ הינו עץ הנפרש מצומת מסויים X אב קדמון של Y אם:
תת-העץ הנ"ל נפרש מהצומת עם הערך 3 X אב קדמון של Y אם: Y נפרש בתת-העץ שיוצא מ- X. כלומר, במסלול מהשורש אל Y עוברים דרך X למשל, 3 הוא אב קדמון של 4, 9, 5 ו- 7 1 3 8 2 4 9 6 5 7 © Keren Kalif

6 אנחנו נתמקד בעצים בינאריים
עץ בינארי עץ בינארי הוא עץ שלכל צומת יש מקסימום 2 ילדים 1 3 2 4 9 6 8 7 typedef int type; typedef struct TreeNode { type data; struct TreeNode* left; struct TreeNode* right; } TNode; typedef struct TNode* root; } Tree; אנחנו נתמקד בעצים בינאריים © Keren Kalif

7 פונקציה היוצרת צומת חדשה
TNode* createNewTNode(type data, TNode* left, TNode* right) { TNode* newNode = (TNode*)calloc(1, sizeof(TNode)); newNode->data = data; newNode->left = left; newNode->right = right; return newNode; } void main() Tree tr; TNode* left = createNewTNode(1, NULL, NULL); TNode* right = createNewTNode(2, NULL, NULL); TNode* root = createNewTNode(3, left, right); tr.root = root; root tr 3 root 1 left 2 right © Keren Kalif

8 וכדי לייצר את העץ שלנו: root tr 1 3 2 4 9 6 8 7 Tree buildTree () {
Tree t; t.root = (TNode*)calloc(1, sizeof(TNode)); t.root->data = 1; t.root->left = (TNode*)calloc(1, sizeof(TNode)); t.root->left->data = 3; t.root->left->left = (TNode*)calloc(1, sizeof(TNode)); t.root->left->left->data = 4; t.root->left->right = (TNode*)calloc (1, sizeof(TNode)); t.root->left->right->data = 9; t.root->left->right->right = (TNode*)calloc (1, sizeof(TNode)); t.root->left->right->right->data = 7; t.root->right = (TNode*)calloc(1, sizeof(TNode)); t.root->right->data = 2; t.root->right->left = (TNode*)calloc(1, sizeof(TNode)); t.root->right->left->data = 6; t.root->right->right = (TNode*)calloc(1, sizeof(TNode)); t.root->right->right->data = 8; return t; } וכדי לייצר את העץ שלנו: root tr 1 3 2 4 9 6 8 7 © Keren Kalif

9 עצים ורקורסיות הרעיון מאחורי כל הפונקציות הקשורות לעצים הוא לבצע את העבודה עבור כל אחד מתתי-העצים של צומת, וכנ"ל עבור תתי- העצים שלו, ולכן יהיה שימוש רב ברקורסיות © Keren Kalif

10 פונקציה המחזירה את מספר הצמתים בעץ
1 3 2 int numOfNodes(const Tree* t) { return numOfNodesRec(t->root); } int numOfNodesRec(const TNode* root) if (root == NULL) return 0; return 1 + numOfNodesRec(root->left) + numOfNodesRec(root->right); 4 9 6 8 7 4 3 8 © Keren Kalif

11 פונקציה המחשבת גובה העץ
תזכורת:: root root עפ"י ההגדרה זהו עץ בגובה 0, מאחר ואורך המסלול הארוך ביותר הוא 0... 3 עפ"י ההגדרה גובהו של עץ שהוא NULL אינו מוגדר... int height(const Tree* t) { return heightRec(t->root); } int heightRec(const TNode* root) if (root == NULL) return 0; return 1 + max(heightRec(root->left), heightRec(root->right)); עבור עץ עם שורש בלבד הפונקציה תחזיר 1, ועבור עץ ריק הפונקציה תחזיר 0. © Keren Kalif

12 הערך 1- אינו באמת גובה העץ אלא מהווה אינדיקציה שהעץ ריק.
אבל... הבעייתיות: גובה של עץ המכיל רק שורש הוא 0 יחד עם זאת, תנאי העצירה של הרקורסיה הוא כאשר הצומת הוא NULL, ואם נחזיר גם במקרה זה 0, למעשה גם עץ עם שורש בלבד וגם עץ ללא צמתים בלבד מחזירים תוצאה זהה.. int height(const Tree* t) { return heightRec(t->root); } int heightRec(const TNode* root) if (root == NULL) return 1-; return 1 + max(heightRec(root->left), heightRec(root->right)); הערך 1- אינו באמת גובה העץ אלא מהווה אינדיקציה שהעץ ריק. ערך זה נבחר שרירותית ויכל להיות כל ערך אחר, אבל אז כמובן הפונקציה לא הייתה מחזירה את הערך האמיתי.. © Keren Kalif

13 פתרון שאינו מגביל את הערך המוחזר עבור עץ ריק להיות 1-
3 3 3 3 תת עץ תת עץ תת עץ תת עץ int heightRec(const TNode* root) { if (root == NULL) return -700; // can be any other value else if (root->left == NULL && root->right == NULL) return 0; else if (root->right == NULL) // only left son return 1 + heightRec(root->left); else if (root->left == NULL) // only right son return 1 + heightRec(root->right); else // has both sons return 1 + max( heightRec(root->left), heightRec(root->right) ); } © Keren Kalif

14 הדפסת ערכי העץ כמו כל מעבר על עצים, נעשה זאת ברקורסיה:
InOrder: תת-עץ שמאלי, שורש, תת-עץ ימני (LDR) PreOrder: שורש, תת-עץ שמאלי, תת-עץ ימני (DLR) PostOrder: תת-עץ שמאלי, תת-עץ ימני, שורש (LRD) 1 3 2 4 9 6 8 7 InOrder: PreOrder: PostOrder: © Keren Kalif

15 הדפסת ערכי העץ - InOrder
InOrder: תת-עץ שמאלי, שורש, תת-עץ ימני (LDR) 1 3 2 4 9 6 8 7 void printInOrder(Tree t) { printInOrderRec(t.root); } void printInOrderRec(TNode *t) if (t == NULL) return; printInOrderRec(t->left); printf ("%d ",t->data); printInOrderRec(t->right); InOrder: L D D R L D R R L R D © Keren Kalif

16 הדפסת ערכי העץ - PreOrder
PreOrder: שורש, תת-עץ שמאלי, תת-עץ ימני (DLR) 1 3 2 4 9 6 8 7 void printPreOrder(Tree t) { printPreOrderRec(t.root); } void printPreOrderRec(TNode *t) if (t == NULL) return; printf ("%d ",t->data); printPreOrderRec(t->left); printPreOrderRec(t->right); PreOrder: D R D L R D L R D L R © Keren Kalif

17 הדפסת ערכי העץ - PostOrder
PostOrder: תת-עץ שמאלי, תת-עץ ימני, שורש (LRD) 1 3 2 4 9 6 8 7 void printPostOrder(Tree t) { printPostOrderRec(t.root); } void printPostOrderRec(TNode *t) if (t == NULL) return; printPostOrderRec(t->left); printPostOrderRec(t->right); printf ("%d ",t->data); PostOrder: R D L R D L D R L R D © Keren Kalif

18 שחרור הזכרון של עץ ניתן לראות כי איברי העץ משוחררים בסדר PostOrder
השורש חייב להשתחרר בסוף מאחר והוא זה שמכיל את ההצבעות לתתי-העצים שתחתיו... void freeTree(Tree t) { freeTreeRec(t.root); } void freeTreeRec(TNode *t) if (t == NULL) return; freeTreeRec(t->left); freeTreeRec(t->right); free (t); © Keren Kalif

19 הדפסת העץ לפי רמות רמה 1 2 3 void main() { int i;
Tree tr = buildTree(); for (i=0 ; i < 6 ; i++) { printf("\nLevel %d: ", i); printLevel(tr.root, i); } printf("\n"); freeTree(tr); הדפסת העץ לפי רמות רמה 1 2 3 1 3 2 4 9 6 8 7 void printLevel(TNode* root, int level) { if (root == NULL) return; if (level == 0) printf("%d ", root->data); else { printLevel(root->left, level-1); printLevel(root->right, level-1); } © Keren Kalif

20 חיפוש בעץ TNode* find(Tree t, type val) { return findRec(t.root, val); } TNode* findRec(TNode* root, type val) TNode* item; if (root == NULL) return NULL; if (root->data == val) return root; item = findRec(root->left, val); if (item != NULL) return item; else return findRec(root->right, val); הפונקציה מחפשת ערך מסויים בעץ, ומחזירה את הצומת שבו הוא נמצא, או NULL אם הערך אינו קיים בעץ © Keren Kalif

21 העתקת עץ TNode* copyTree(TNode* root) { TNode* newRoot;
if (root == NULL) return NULL; newRoot = createNewTNode(root->data, NULL, NULL); newRoot->left = copyTree(root->left); newRoot->right = copyTree(root->right); return newRoot; } newRoot = createNewTNode(root->data, copyTree(root->left), copyTree(root->right)); © Keren Kalif

22 מציאת הערכים המינימלי והמקסימלי בעץ (1)
void getMinAndMaxFromTree(TNode* root, int* minimum, int* maximum) { if (root->left == NULL && root->right == NULL) *minimum = *maximum = root->data; else if (root->left == NULL) // has only right son getMinAndMaxFromTree(root->right, minimum, maximum); if (root->data < *minimum) *minimum = root->data; if (root->data > *maximum) *maximum = root->data; } else if (root->right == NULL) // has only left son getMinAndMaxFromTree(root->left, minimum, maximum); אם יש רק שורש, אז הוא המינימום והמקסימום אם יש רק בן ימני, נחפש בו את המינימום והמקסימום ונשווה אותם מול השורש אם יש רק בן שמאלי, נחפש בו את המינימום והמקסימום ונשווה אותם מול השורש © Keren Kalif

23 מציאת הערכים המינימלי והמקסימלי בעץ (2)
מציאת הערכים המינימלי והמקסימלי בעץ (2) void getMinAndMaxFromTree(TNode* root, int* minimum, int* maximum) { else // has both sons int leftMin, rightMin, leftMax, rightMax; getMinAndMaxFromTree(root->left, &leftMin, &leftMax); getMinAndMaxFromTree(root->right, &rightMin, &rightMax); *minimum = min(root->data, min(leftMin, rightMin)); *maximum = max(root->data, max(leftMax, rightMax)); } נמצא את המינימום והמקסימום של כל אחד מתתי-העצים, ונחזיר את המינימום והמקסימום בינהם לבין הערך שבשורש © Keren Kalif

24 מציאת אורך המסלול הארוך ביותר שאיבריו עוקבים
הרעיון: לבדוק בכל תת-עץ מה אורך המסלול הארוך ביותר שאיבריו עוקבים לבדוק מה המסלול הארוך ביותר המתחיל מהשורש שאיבריו עוקבים להחזיר את המקסימום מבין 2 הערכים שנמצאו המסלול הרציף הארוך ביותר הוא באורך 2 1 3 2 4 9 6 8 7 1 3 2 4 9 6 8 7 5 המסלול הרציף הארוך ביותר הוא באורך 1 © Keren Kalif

25 מציאת אורך המסלול הארוך ביותר שאיבריו עוקבים
תחזיר את אורך המסלול הארוך ביותר שאינו מתחיל מהשורש תחזיר כפרמטר פלט את אורך המסלול הארוך ביותר המתחיל מהשורש Int longestSequenceRec(TNode* root, int* longestFromRoot); int longestSequence(Tree tr) { int temp; if (tr.root == NULL) return ERROR; return longestSequenceRec(tr.root, &temp); } © Keren Kalif

26 מציאת אורך המסלול הארוך ביותר שאיבריו עוקבים (1)
int longestSequenceRec(TNode* root, int* longestFromRoot) { if (root->left == NULL && root->right == NULL) *longestFromRoot = 0; return 0; } else if (root->left == NULL) // only right son int innerPathLen, pathFromRootLen; innerPathLen = longestSequenceRec(root->right, &pathFromRootLen); // check if the root contains a longer sequence if (root->data + 1 == root->right->data) *longestFromRoot = 1 + pathFromRootLen; else return max(innerPathLen, *longestFromRoot); ... אם לשורש אין בנים, אז גם אורך המסלול הרציף הארוך ביותר המתחיל מהשורש הוא 0, וגם המסלול הפנימי הוא 0 © Keren Kalif

27 מציאת אורך המסלול הארוך ביותר שאיבריו עוקבים (2)
int longestSequenceRec(TNode* root, int* longestFromRoot) { ... else if (root->right == NULL) // only left son int innerPathLen, pathFromRootLen; innerPathLen = longestSequenceRec(root->left, &pathFromRootLen); // check if the root contains a longer sequence if (root->data + 1 == root->left->data) *longestFromRoot = 1 + pathFromRootLen; else *longestFromRoot = 0; return max(innerPathLen, *longestFromRoot); } © Keren Kalif

28 מציאת אורך המסלול הארוך ביותר שאיבריו עוקבים (3)
int longestSequenceRec(TNode* root, int* longestFromRoot) { ... else // has the 2 sons int leftRootLen, rightRootLen; int innerFromLeft = longestSequenceRec(root->left, &leftRootLen); int innerFromRight = longestSequenceRec(root->right, &rightRootLen); if (root->data + 1 == root->left->data) leftRootLen++; else leftRootLen = 0; if (root->data + 1 == root->right->data) rightRootLen++; else rightRootLen = 0; *longestFromRoot = max(leftRootLen, rightRootLen); return max(*longestFromRoot, max(innerFromLeft, innerFromRight)); } © Keren Kalif

29 יצירת רשימה מעץ ע"י מעבר InOrder (1)
נשתמש בפונקצית העזר המקבלת 2 רשימות ומשרשרת את הרשימה השניה לסוף הרשימה הראשונה void concatLists(List* l1, List* l2) { if (isEmpty(l1)) *l1 = *l2; //l1->head = l2->head; //l1->tail = l2->tail; } else if (!isEmpty(l2)) l1->tail->next = l2->head; l1->tail = l2->tail; © Keren Kalif

30 יצירת רשימה מעץ ע"י מעבר InOrder (2)
List treeToListInOrder(TNode* root) { if (root == NULL) return makeEmptyList(); else List left = treeToListInOrder(root->left); List right = treeToListInOrder(root->right); insertValueToTail(&left, root->data); concatLists(&left, &right); return left; } ניתוח יעילות: insertValueToTail ו- concatLists ממומשות ביעילות של O(1) בכל קריאה רקורסיבית יש פעולות קבועות לכן היעילות הכוללת של הפונקציה היא כמספר הקריאות הרקורסיביות, כלומר כמספר הצמתים בעץ © Keren Kalif

31 מילוי עץ מנתוני מערכים בהינתן מערך המייצג נתוני עץ במעבר InOrder ומערך המייצג נתוני אותו עץ במעבר PreOrder יש לבנות את העץ. ידוע כי כל ערך מופיע מקסימום פעם אחת בלבד. למשל, בהינתן המערכים הבאים וגודלם יש לייצר את העץ המתאים: InOrder: PreOrder: 1 3 2 4 9 6 8 7 © Keren Kalif

32 מילוי עץ מנתוני מערכים - הרעיון
1 3 2 4 9 6 8 7 הרעיון: ידוע כי השורש הוא האיבר הראשון במערך ה- preorder נמצא את מיקומו במערך ה- InOrder : מכך נדע מהי חלוקת האיברים בין 2 תתי-העצים וכן את כמות האיברים בכל תת-עץ (עבור תת-העץ השמאלי זה הגודל הכולל פחות האינדקס של השורש) (עבור תת-העץ הימני זה הגודל הכללי פחות 1, פחות האינדקס של השורש) InOrder: PreOrder: L R © Keren Kalif

33 הקוד int findIndex(int* arr, int size, int value) { int i;
for (i=0 ; i < size ; i++) if (arr[i] == value) return i; return -1; // should never get here.. } TNode* createTreeFromPreOrderAndInOrder(int* pre, int* in, int size) TNode* root; int index; if (size == 0) return NULL; root = createNewTNode(pre[0], NULL, NULL); index = findIndex(in, size, pre[0]); root->left = createTreeFromPreOrderAndInOrder(pre+1, in, index); root->right = createTreeFromPreOrderAndInOrder(pre+1+index, in+1+index, size-index-1); return root; הקוד © Keren Kalif

34 פונקציה המדפיסה את כל הצמתים שלהם בן אחד בלבד
void printOneSonRec(TNode* root) { if (root->left == NULL && root->right == NULL) return; else if (root->left == NULL) // only right son printf("%d ", root->data); printOneSonRec(root->right); } else if (root->right == NULL) // only left son printOneSonRec(root->left); else // has both sons 1 3 2 4 9 6 8 7 1 3 2 9 6 8 7 © Keren Kalif

35 פונקציה המחזירה את כמות תתי-העצים מגובה מסויים (1)
void main() { int i; Tree tr = buildTree(); printf("Count sub trees of height:\n"); for (i=0 ; i < 6 ; i++) printf("sub trees of height %d: %d\n", i, countSubTreesOfHeight(tr, i)); freeTree(tr); } 1 3 2 4 9 6 8 7 שימו לב: לפעמים פונקציית העזר תזדקק לפרמטר נוסף כדי לבצע את העבודה.. int countSubTreesOfHeight (Tree tr, int h) { int treeHeight; if (tr.root == NULL) return -700; // error! return countSubTreesOfHeightRec(tr.root, h, &treeHeight); } פונקציית העזר תצטרך בכל שלב לדעת מה גובה תת-העץ, על מנת לדעת האם הוא מתאים לתנאי © Keren Kalif

36 פונקציה המחזירה את כמות תתי-העצים מגובה מסויים (2)
int countSubTreesOfHeightRec(TNode* root, int h, int* treeHeight) { int leftHeight, rightHeight, countLeft, countRight; if (root->left == NULL && root->right == NULL) *treeHeight = 0; return h == 0 ? 1 : 0; } else if (root->right == NULL) // only left child countLeft = countSubTreesOfHeightRec(root->left, h, &leftHeight); rightHeight = 0; countRight = 0; אם אין בנים, אזי גובה העץ הוא 0. במידה והגובה המבוקש הוא 0, נחזיר שיש עץ 1 כזה, אחרת נחזיר 0 אם יש רק בן שמאלי, נבדוק מה גובהו וכמה בנים שלו עונים לתנאי, ונאתחל את נתוני צד ימין ב- 0 © Keren Kalif

37 המשך... int countSubTreesOfHeightRec(TNode* root, int h, int* treeHeight) { int leftHeight, rightHeight, countLeft, countRight; else if (root->left == NULL) // only right child countRight = countSubTreesOfHeightRec(root->right, h, &rightHeight); leftHeight = 0; countLeft = 0; } else // has both sons countLeft = countSubTreesOfHeightRec(root->left, h, &leftHeight); *treeHeight = max(leftHeight, rightHeight) + 1; if (*treeHeight == h) return 1; else return countLeft + countRight; אם יש רק בן ימני, נבדוק מה גובהו וכמה בנים שלו עונים לתנאי, ונאתחל את נתוני צד שמאל ב- 0 אם יש את 2 הבנים, נבקש את הנתונים עבור שניהם נעדכן את גובה העץ ונחזיר את הירך שחישבנו © Keren Kalif

38 האם הערך בכל צומת גדול מכל הערכים שבתתי-העצים שלו
sum=20√ 21 sum=6√ sum=3√ 7 4 2 3 1 2 1 פונקציית העזר תצטרך בכל שלב לדעת מה סכום הצמתים בעץ, על מנת שהאב יוכל לבדוק התאמה לתנאי, וכן האם תת-העץ בפני עצמו עומד בקריטריון הבדיקה © Keren Kalif

39 הקוד... int isNodeValueBiggerThanSubTrees(Tree tr) { int sum;
return isNodeValueBiggerThanSubTreesRec(tr.root, &sum); } int isNodeValueBiggerThanSubTreesRec(TNode* root, int* subTreesSum) int leftSum, rightSum, leftRes, rightRes; if (root == NULL) *subTreesSum = 0; return 1; if (root->left == NULL && root->right == NULL) *subTreesSum = root->data; …. הקוד... © Keren Kalif

40 int isNodeValueBiggerThanSubTreesRec(TNode* root, int* subTreesSum)
{ int leftSum, rightSum, leftRes, rightRes; // here means has at least one son if (root->right) // has right son rightRes = isNodeValueBiggerThanSubTreesRec(root->right, &rightSum); else rightSum = 0; rightRes = 1; } if (root->left) // has left son leftRes = isNodeValueBiggerThanSubTreesRec(root->left, &leftSum); leftSum = 0; leftRes = 1; *subTreesSum = root->data + leftSum + rightSum; return rightRes && leftRes && (root->data > leftSum + rightSum); המשך... © Keren Kalif

41 נשים לב.. 2 הדוגמאות האחרונות הן מאותו סגנון:
הפונקציה הרקורסיבית מקבלת עוד נתון שעוזר לה לבצע את העבודה הראשונה עדכנה כפרמטר את גובה העץ השניה עדכנה כפרמטר את סכום הצמתים בעץ יחד עם זאת, שתיהן כתובות בסגנונות כתיבה שונים: בראשונה טיפלנו בנפרד בכל אחד מן המקרים בהם יש בנים אם יש רק תת-עץ שמאלי אם יש רק תת-עץ ימני אם יש שני תתי-עצים בשניה טיפלנו ביחד ב- 3 המקרים הנ"ל: חישבנו את התוצאה כאשר יש תת-עץ שמאלי חישבנו את התוצאה כאשר יש תת-עץ ימני איחדנו בין התוצאות 2 הצורות תקינות, לבחירתכם... © Keren Kalif

42 ביחידה זו למדנו: הגדרות יצירת עץ מעברים על עצים חיפוש בעץ ממערכים לעץ
מעץ לרשימה © Keren Kalif


Download ppt "הרצאה 07 עצים קרן כליף."

Similar presentations


Ads by Google