Download presentation
Presentation is loading. Please wait.
1
הרצאה 07 עצים קרן כליף
2
ביחידה זו נלמד: הגדרות יצירת עץ מעברים על עצים חיפוש בעץ ממערכים לעץ
מעץ לרשימה © Keren Kalif
3
הגדרות עץ הוא מבנה נתונים המכיל נתונים היררכיים
למשל: אילן יוחסין, מבנה אירגוני מבנה-נתונים זה מורכב מצמתים (העיגולים הירוקים) ומקשתות (החצים) "שורש" הוא הצומת העליון, אף צומת אחר אינו מכיל קשת אליו "אבא": צומת שיש ממנו קשת לצומת אחר "בן" (או: ילד, או: צומת פנימי): צומת שיש לו אבא כל הצמתים הם בנים פרט לשורש "עלה": צומת שאין לו בנים 1 שורש 3 8 2 בן אבא 4 9 6 1 בן 5 5 7 עלה © Keren Kalif
4
הגדרות (2) רמה: המרחק של צומת מהשורש מסלול: רצף של צמתים שמתחיל בשורש
השורש נמצא ברמה 0 מסלול: רצף של צמתים שמתחיל בשורש למשל: 1 39 אורך מסלול: מס' הקשתות במסלול שקול לרמה של הצומת שבסוף המסלול אורך המסלול 1 39 הוא 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
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.