מבוא לשפת C תרגול 8: מערכים רב-ממדיים תרגילים בנושא מערכים ורקורסיה מבוסס על השקפים שחוברו ע"י שי ארצי, גיתית רוקנשטיין, איתן אביאור וסאהר אסמיר עבור הקורס "מבוא למדעי המחשב" נכתב ע"י טל כהן, עודכן ע"י יורי פקלני. כל הזכויות שמורות לטכניון מכון טכנולוגי לישראל
תוכנייה מערכים רב-ממדיים תרגילים בנושא מערכים דו-ממדיים תרגילים בנושא מערכים ורקורסיה 2
מערכים רב-ממדיים
מערכים, וקטורים ומטריצות המערכים שהכרנו עד-כה הם חד ממדיים. בהינתן אינדקס (מספר שלם בין 0 לגודל המערך), יש במערך תא מתאים לאינדקס זה. אפשר לייצג בעזרת המערך וקטורים\שורות. ואכן, בשפות תכנות שונות אומרים "וקטור" במקום "מערך". אבל... איך אפשר לייצג בעזרת מערך מטריצה\טבלאות? 3 12 19 8 1 5 A 78 36 65 0 4 9 7 2 6 4
מערכים דו-ממדיים התשובה: אם "מערך" הוא לייצג מטריצה! למשל: מספר עמודות = רוחב של מטריצה "וקטור", אז נשתמש במערך דו-ממדי כדי int a[5][3] = {{ 3, 12, 19, { 8, 1, 5, {78, 36, 65, { 0, 4, 9, { 7, 2, 6; מספר שורות = גובה של מטריצה מגדיר ומאתחל מערך דו מימדי בעל 5 שורות ו- 3 עמודות. כדי לגשת לאיבר במערך דו-ממדי משתמשים בשני אינדקסים. למשל, בדוגמא לעיל מתקיים: == 5 [2][1]a אינדקס עמודה אינדקס שורה 5
תמונת זיכרון כזכור זיכרון מחשב הוא לינארי, את איברי המטריצה בזיכרון. ב- C לכן צריך לקבוע את הסדר בו נשמור פורשים את המטריצה בזיכרון שורה-אחר-שורה. int a[5][3] = {{ 3, 12, 19, { 8, 1, 5, {78, 36, 65, { 0, 4, 9, { 7, 2, 6; כלומר המטריצה הבאה: תיראה בזיכרון כך בהנחה שמתקיים = 2 :sizeof(int) 100 102 104 106 108 110 112 114 116 118 120 122 124 126 128 3 12 19 8 1 5 78 36 65 0 4 9 7 2 6? איך יראה הזיכרון עבור = 4 sizeof(int) 6
גישה למערך דו-ממדי בעזרת מצביעים בדיוק כמו עבור מערכים חד-ממדיים, מצביע לאיבר הראשון: כעת ניתן לגשת לנתונים של מטריצה a גם עבור מטריצות אפשר להגדיר int a[5][3] = {{ 3, 12, 19, { 8, 1, 5, {78, 36, 65, { 0, 4, 9, { 7, 2, 6; int *p = &a[0][0]; גם בעזרת המצביע p: שקולים int x = a[i][j]; int x = *(p + 3*i + j); כי כדי להגיע לתחילת שורה i יש לקדם את p ב- i*width : p p+3 p+6 p+9 p+12 i*3 100 3 102 12 104 19 106 8 108 1 110 5 112 78 114 36 116 65 118 0 120 4 122 9 124 7 126 2 128 6
מערך דו-ממדי כפרמטר לפונקציה אם רוצים לכתוב פונקציה שמניחה אורך שורה קבוע, המטריצה בצורה הבאה: חייבים לציין את מספר העמודות (אורך השורה) ניתן להעביר את void f(int a[][4], int height) מותר גם לציין את מספר השורות, אך הקומפיילר יתעלם ממנו. פרמטר height (מספר השורות) מועבר כדי שבתוך הפונקציה לא נחרוג מגבולות המטריצה. פונקציה זו יודעת ש- a היא מטריצה, לאיבר i,j בעזרת: 0 i height-1, 0 j 3 הקריאה לפונקציה זו תראה כך: לכן מותר בתוך הפונקציה לגשת int m[9][4]; f(m, 9); a[i][j]
מערך דו-ממדי כפרמטר לפונקציה אבל אם רוצים לכתוב פונקציה שעובדת עבור מטריצה בגודל כלשהו חייבים להעביר לפונקציה מצביע לאיבר הראשון, גובה ורוחב של המטריצה: void f(int *p, int height, int width) כעת, מבחינת הפונקציה, p הוא לא מטריצה, אלא מצביע. לכן בתוך הפונקציה לא ניתן להשתמש ב-[ p[i][j, אלא יש לגשת לאיבר i,j בעזרת: *(p + i*width + j) 0 i height-1, 0 j width-1 int m[9][4]; f(&m[0][0], 9, 4); הקריאה לפונקציה זו תראה כך:
מערכים רב-ממדיים למעשה, ניתן להגדיר בשפת C מערכים מכל טיפוס בכל מימד שהוא. למשל, מערך תלת-ממדי של :double double array[5][3][6]; אנו נסתפק במערכים דו-ממדיים לכל היותר בקורס זה. 7
תרגיל 1: מערכים ומטריצות כתבו פונקציה len) void num2(int arr[ ], int המקבלת מערך arr ואת אורכו len ומדפיסה את צמד המספרים המופיע מספר פעמים הרב ביותר. אם יש יותר מצמד אחד אזי יודפסו כל הצמדים. צמד מספרים הינם 2 מספרים הנמצאים במקומות עוקבים. הנח כי כל המספרים קטנים ממש מ- 500 וגדולים או שווים ל- 0. דוגמא: עבור מערך 4 5 7 88 2 3 4 5 2 3 הצמדים הם:( 2,3 ) (5,2) (4,5) (3,4) (2,3) (88,2) (7,88) (5,7) (4,5) לכן יודפס : 5 4 2 3 הערה: הסדר בתוך הצמד חשוב, כלומר: הצמד 5 4 שונה מהצמד 4. 5 8
הדרך לפתרון בשביל למצוא מהו צמד שמופיע הכי הרבה פעמים, צריכים לספור כמה פעמים מופיע כל צמד. כיוון שיש 500 אפשרויות לכל מספר במערך, יש 500*500 אפשרויות שונות של צמדים: 500 צמדים שהמספר הראשון שלהם 0 500 צמדים שהמספר הראשון שלהם 1... 500 צמדים שהמספר הראשון שלהם 499 סה"כ.500*500 כדי לספור את מספר הצמדים, נגדיר מטריצה [500][500]m שכל איבר.arr מופיע במערך (i,j) סופר כמה פעמים הצמד m[i][j] 9
פתרון תרגיל 1 void num2( int arr[], int len ) { int m[500][500] = {{0; int i, j, max = 0; if ( len < 2 ) return; for ( i = 0 ; i < len-1 ; i++ ) m[arr[i]][arr[i+1]]++; for ( i = 0 ; i < 500 ; i++ ) for ( j = 0 ; j < 500 ; j++ ) if ( m[i][j] > max ) max = m[i][j]; נגדיר מטריצה 500*500 ונאתחל אותה באפסים. נספור כמה פעמים מופיע כל צמד arr[i+1]) (arr[i], נמצא את מקסימום של מספר ההופעות. נדפיס את כל הצמדים שמספר ההופעות שלהם שווה למקסימום שמצאנו. for ( i = 0 ; i < 500 ; i++ ) for ( j = 0 ; j < 500 ; j++ ) if ( m[i][j] == max ) printf( \n max appearance: %d, %d, i, j );;.1.2.3.4 10
תרגיל 2: מטריצות ריבוע חצי-קסם הוא מערך דו-מימדי שבו סכומי השורות הם שווים. (אין שום דרישה לגבי העמודות או האלכסונים) לדוגמה: מערך הבא: 5 6 3 3 8 3 7-1 8 הוא ריבוע חצי-קסם, כיוון שסכום המספרים בכל שורה הוא 14. כתבו פונקציה A[N][N]) int is_magic(int המקבלת מערך דו מימדי ומחזירה 1 במידה וזהו ריבוע חצי-קסם, ואחרת מחזירה 0. יש להניח ש N מוגדר ב.#define 11
פתרון תרגיל 2 int is_magic(int A[][N]) { int curr_row_sum, i, j; int first_row_sum = 0; נחשב את הסכום של השורה הראשונה for (j=0; j<n; j++) { first_row_sum += A[0][j]; for (i=1; i<n; i++) { curr_row_sum=0; for (j=0; j<n; j++) { curr_row_sum += A[i][j]; if (curr_row_sum!= first_row_sum) { return 0; return 1; נבדוק שסכום של כל השורות שווה לסכום של השורה הראשונה נחשב את הסכום של השורה i אם הסכום שונה, זה לא ריבוע חצי-קסם. אין טעם לבדוק את שאר השורות. אם הגענו עד לכאן, סכום כל השורות שווה 12
תרגיל 3 נייצג תמונה בשחור לבן ע"י מערך דו ממדי, שכל תא בו מכיל 1 עבור נקודה שחורה ו 0 עבור נקודה לבנה נגדיר את מטריצת השליטה של תמונה כמערך דו מימדי של שלמים, בו במקום ה-[ i][j ] מוצב הערך n אם ורק אם התא ה [i][j] של התמונה הוא נקודה שחורה וגם 1-n התאים שתחתיו הם נקודות שחורות כתבו פונקציה אשר מקבלת תמונה ובונה את מטריצת השליטה שלה לדוגמה:
תרגיל 3- פיתרון void compute_dominance (int p[][w], int d[][w], int h){ int i,j; for (j=0; j<w; j++) { /* base case: bottom row */ d[h-1][j] = p[h-1][j]; /* d is 1 if p is black, 0 otherwise */ for (i=h-2; i>=0; i--) { /* compute upper rows */ for (j=0; j<w; j++) { /* for each column */ d[i][j] = (p[i][j]? d[i+1][j] + 1 : 0);
תרגיל 4 כתבו פונקציה max_rect_at_point המקבלת תמונה, את מטריצת השליטה שלה וקורדינאטות של תא בתמונה, ומחזירה את שטחו של המלבן השחור הגדול ביותר שהתא הנתון הוא הקודקוד השמאלי העליון שלו. למשל בהינתן התמונה P ומטריצת השליטה D מהתרגיל הקודם.
תרגיל 4- פיתרון int max_rect_at_point (int p [][M], int d[][m], int i, int j) { int k, length, width, area; if (!p[i][j]) { return 0; area = length = d[i][j]; /* for a rectangle of width 1, area = length */ k = j+1; width = 2; /* try rectangles of increasing width */ while (k < M && p[i][k]) { /* as long as the line is black */ length = MIN (length, d[i][k]); area = MAX (area, width*length); k++; width++; return area;
תרגילים רקורסיה ומערכים
שאלה 1 ממשו פונקציה רקורסיבית המקבלת מחרוזת A ושני מערכים ריקים B ו.C void SeparateLetters(char A [], char B [], char C []) הפונקציה מעתיקה את האותיות הגדולות מ- A ל- B ואת האותיות הקטנות מ- A ל- C. בסוף הריצה B ו C צריכות להיות מחרוזות כלומר להסתיים ב-' 0 \' יש להניח שגודל המערכים Bו C הוא לפחות כמו הגודל של A. לדוגמא עבור: נקבל: A K i 1 @ B j 7 Y 8 \0 B K B Y \0 C i j \0 18
שאלה 1 האלגוריתם הרקורסיבי הבעיה:.1.2.3.4 יש להעביר אותיות ממחרוזת A ו- C. ל- B הדרך להקטין פרמטר-מחרוזת היא להוריד את התו הראשון. נניח שאנחנו יודעים להעביר אותיות כמבוקש עבור מחרוזת בלי התו הראשון. אם תו הראשון הוא אות גדולה אם תו הראשון הוא אות קטנה אחרת נתעלם מהתו הראשון. נעביר אותו ל- B. נעביר אותו ל- C. אחרי שטיפלנו בתו הראשון, נשאר לטפל במחרוזת בלי התו הראשון, והנחנו שאת זה הקריאה הרקורסיבית יודעת לעשות. תנאי עצירה: כאשר מחרוזת A מסתיימת (כלומר מגעים ל- 0 \ ) סיימנו להעביר את כל התווים. נשאר רק לשים 0\ בסוף של B ושל C. 19
- פתרון שאלה 2 void SeparateLetters(char *A, char *B, char *C) { if (*A == '\0') { *B = '\0'; *C = '\0'; return; if (*A >= 'a' && *A <= 'z') { /* copy small letter to B */ *B = *A; SeparateLetters(A+1, B+1, C); return; if (*A >= 'A' && *A <= 'Z') { /* copy big letter to C */ *C = *A; SeparateLetters(A+1, B, C+1); return; SeparateLetters(A+1, B, C); /* skip non letters */ 1
שאלה 2 n. הוא מערך שלמים באורך a ידוע ש- n אי-זוגי. כתבו פונקציה רקורסיבית void extreme_to_middle(int a[ ], int i, int j) שתדפיס את אברי a בסדר הבא משמאל לימין: a[0] a[n-1] a[1] a[n-2] a[(n-1)/2] 21
- פתרון שאלה 2 void extreme_to_middle(int a[], int i, int j) { if (j==i) { printf("%d ", a[i]); return; printf("%d ", a[i]); printf("%d", a[j]); extreme_to_middle(a,i+1 j-1); return; 22
שאלה 3 כתבו פונק' שחתימתה: int path_exists(int mat[][n], int i, int j) הפונק' תקבל מערך דו-מימדי שמכיל אפסים ואחדים בלבד, ותבדוק אם קיים מסלול שכולו אחדים מהפינה השמאלית העליונה (התא 0,0) לפינה הימנית התחתונה (התא,(N-1,N-1 ותחזיר 1 אם כן, ו- 0 אחרת. בסוף הפונק' המערך הדו-מימדי צריך להיות זהה להתחלה. 1 0 0 0 0 1 1 0 יוחזר 1 יוחזר 0 למשל: 1 0 0 0 0 1 1 0 0 1 0 0 0 0 1 1 0 1 0 0 0 0 0 1
int path_exists(int mat[][n], int i, int j){ int i2, j2; if( i==n-1 && j==n-1 ) if( mat[i][j]==1 ) return 1; else return 0; if(!mat[i][j] ) return 0; mat[i][j] = 2; /* A sign that we visited the cell */ for(i2=i-1; i2<=i+1; i2++) /*The cells around the current cell*/ for(j2=j-1; j2<=j+1; j2++){ if( i2>=0 && i2<n && j2>=0 && j2<n && mat[i2][j2]==1 ) if( path_exists(mat,i2,j2) ){ mat[i][j]=1; return 1; 8 mat[i][j]=1; return 0; פיתרון איך אפשר להדפיס את המסלול? הזכויות שמורות כל. לביולוגים תכנות 2