נכתב ע"י כרמי גרושקו. כל הזכויות שמורות 2010 הטכניון, מכון טכנולוגי לישראל הקצאה דינמית )malloc( מערכים דו-מימדיים סיבוכיות: ניתוח
כזכור, כדי לאחסן מידע עלינו לבקש זכרון ממערכת ההפעלה. 2 עד עכשיו: הגדרנו משתנים, היום: פרמטרים, מערכים. היינו חייבים לדעת מראש כמה זכרון נרצה. "הקצאה דינמית" מאפשרת לנו לבקש זכרון בזמן ריצת התכנית. הזכרון שנקבל יהיה רציף, כמו מערך. לעתים קרובות זה גם השימוש בזכרון זה: בתור מערך.
כתבו תכנית הקולטת מהמשתמש מספר חיובי n, ולאחריו n מספרים ממשיים. על התכנית לזכור את כל המספרים. לא ניתן להגדיר מערך, כי אנחנו לא יודעים את n מראש! 3 כמה בתים של זכרון מספרים ממשיים? n*sizeof(double) צריך כדי לאחסן n double* numbers; scanf( "%d", &n ); numbers = malloc( n*sizeof(double) ); if( numbers == NULL ) printf( "Memory allocation failed!" ); return 1; for( i=0; i<n; i++ ) scanf( "%lf", &numbers[i] ); אם ההקצאה נכשלה, מוחזר NULL כאילו זה היה מערך רגיל
מיד עם סיום העבודה עם הזכרון המוקצה, חייבים לשחררו חזרה למערכת ההפעלה. אחרת הזכרון עלול להגמר. משחררים ע"י free(p) כאשר לזכרון שהוקצה ע"י malloc p מצביע ביצוע free שלא לפי הנחיה זו יגרום בד"כ לקריסת התכנית. 4 ברגע שהזכרון שוחרר, שוב אסור לגשת אליו. לנו free( numbers );
כתבו פונקציה המקבלת int ומחזירה מחרוזת המייצגת את המספר. 1234 "1234" 5 איך "מחזירים מחרוזת"? הרי מחרוזת היא מצביע לזכרון שבסופו יש '0\'. זכרון זה חייב להיות נגיש לנו. הצעה נפוצה: נגדיר מערך בתור הפונקציה, ונחזיר מצביע אליו. 1. מערך זה יהיה "אוטומטי לוקלי", שכזכור נעלם ברגע שיוצאים מהפונקציה. 2. אנחנו לא יודעים כמה גדול המערך צריך להיות!
אנחנו צריכים להחזיר מצביע לזכרון שאינו נעלם מעצמו, ושגודלו לא ידוע מראש. malloc נותן לנו פתרון מעולה:.1.2 הזכרון לא יעלם עד שלא נקרא מפורשות ל- free לא חייבים לדעת את גודל המערך מראש. ניגש לפתרון הבעיה: נגלה את מספר הספרות במספר לא נשכח לטפל בשליליים. נקצה זכרון נמלא את הזכרון נחזיר מצביע 6
char* int2str( int n ) פגשנו אותה בעבר char *str, *p; int negative = n < 0; unsigned digits; n = n < 0? -n : n; /* n = n */ digits = count_digits(n) + negative; str = malloc( (digits+1)*sizeof(char) ); p = str + digits; 7 *(p--) = '\0'; while( n >= 10 ) *(p--) = n%10 + '0'; n /= 10; *(p--) = n%10 + '0'; if( negative ) *p = '-'; return str;
דבר אחד אחרון - אמרנו שכל זכרון שמוקצה ע"י malloc חייב להיות מוחזר ע"י.free 8? מה עם הזכרון שהוקצה בתוך int2str פה חייבים לעשות הסכם מי שנותן לנו את הפונקציה, חייב להגדיר מי אחראי לניקיון. למשל: "אחריות המשתמש )של הפונקציה( לשחרר את הזכרון". המשמעות היא שבסיום העבודה שלנו עם המחרוזת שקיבלנו, אנחנו אחראיים לשחרר אותה.
מערכים דו-מימדיים
הגדרת מערך דו-מימדי )=מטריצה(: 10 int a[10][20]; int b[3][2] = 1, 2, 3, 4, 5, 6 ; גישה: a[row][col] = 5; הכרזה על פונקציה המקבלת מטריצה: מספר העמודות חייב להיות ידוע מראש הפונקציה תקבל רק מטריצות עם מספר כזה של עמודות. מספר השורות חייב להיות פרמטר בדומה להעברת גודל מערך 1D לפונקציה #define M 20 void blurpicture( int picture[][m], int rows );
העברת מטריצה לפונקציה, בדומה למקרה ה-.1D 11 מצביע לאיבר הראשון חייבים להעביר את מספר השורות... אלא אם אתם יודעים את האורך למשל, מטריצות ריבועיות int pig[150][m]; blurpicture( pig, 150 );
כתבו פונקציה המקבלת מטריצה, ומוצאת נקודות שווי-משקל. נק' שווי משקל: איבר במטריצה שהינו המקסימלי בשורה שלו, ומינימלי בעמודה שלו. ]במובן החלש[ הניחו שמספר העמודות מוגדר ב- define #. 12 פתרון נמצא בכל שורה את המקסימום. נבדוק עבור כל אחד כזה אם הוא מינימלי בעמודה 200 15-43 230 121 54 2 17 510 99 4 11 243 223 439 7
בשפת C, בהינתן מטריצה,a[N][M] הביטוי a[i] הינו השורה ה- i של המטריצה. אבל אין את הטריק הזה עבור עמודות... 13 void print_equilibrium( int matrix[][m], int rows ) unsigned r, temp_r; for( r=0; r<rows; r++ ) unsigned c = get_max_idx( matrix[r], M ); for( temp_r=0; temp_r<rows; temp_r++ ) if( matrix[r][c] > matrix[temp_r][c] ) break; if( temp_r == rows ) printf( "Equilibrium at (%d,%d): %d\n", r, c, matrix[r][c] );
כתבו פונקציה המקבלת מערך דו מימדי של מספרים שלמים בגודל NxN )הניחו כי N מוגדר באמצעות )#define 14 מחזירה את המספר הגדול ביותר של איברים סמוכים באותה עמודה שהם ממוינים בסדר לא יורד. למשל עבור המערך הבא )5=N( יוחזר 4. 1 12 8 4 7 9 3 11 7 9 24 11 19 6 12 17 4 2 3 16 18 12 7 5 9
int max_sorted( int a[n][n] ) int max_len = 0, r, c; for( c=0; c<n; c++ ) int len = 1; for( r=1; r<n; r++ ) if(!(a[r][c] >= a[r-1][c]) ) if( len > max_len ) max_len = len; len = 1; else len++; 15 if( len > max_len ) max_len = len; return max_len;
סיבוכיות: ניתוח
נתייחס לשני מדדים של סיבוכיות אסימפטוטית של פונקציות: זמן מקום )=זכרון( - כמה פעולות התכנית מבצעת? - כמה זכרון היא צריכה? 17 סיבוכיות תמיד נמדדת ביחס למשהו שהפונקציה מקבלת; למשל אורך מערך שהוגדר ב- define # N מספר הספרות ב- int בנוסף, יתכן שפרמטרים יוגדרו כ- "קבועים לצרכי חישוב סיבוכיות"; למשל מספר האותיות ב-א"ב )אנגלית= 26, סינית= 4000 (
מחליטים כפונקציה של מה מחשבים את הסיבוכיות. )בד"כ: אורך מערך(.1 18 2. תמיד מתייחסים למקרה הגרוע. 3. אם אפשר, מחשבים את הסיבוכיות במדויק. 4. אחרת, חוסמים מלמעלה ומלמטה. 5. כותבים את הסיבוכיות בכתיב Ω O, ו/או Θ. T(n) S(n) : סימון: 6. 1. סיבוכיות זמן: 2. סיבוכיות מקום
כמה פעולות הפונקציה הבאה תבצע? תלוי ב-[ ar[0... 19 void func1( int ar[], int len ) int i; if( ar[0] == -1 ) for( i=0; i<len; i++ ) ar[i]++; תמיד נתיחס למקרה הגרוע! במקרה זה, סדר גודל של len פעולות
בכתיב אסימפטוטי, 1. משאירים רק את הביטוי הדומיננטי 3x 2 + 4x = Θ 3x 2 20 2. זורקים את כל קבועי הכפל 3x 2 = Θ x 2
"איבר דומיננטי" - האיבר המשפיע כש- x שואף לאינסוף 21 לדוגמא x 2 משפיע יותר מ- x 4 x 2 משפיע יותר מכל פולינום. כלל אצבע משפיעה יותר מ-( g(x f(x) אם הביטוי הבא מתקיים : lim x f x g x =
רשמו בכתיב אסימפטוטי : 22 Tn ( ) 5 T( n) 3n 2 T n n n 3 ( ) 4 10 50 T( n) 2 3 n n 2n Tn ( ) (1) T( n) ( n) T n 3 ( ) ( n ) n Tn ( ) (3 ) רן רובינשטיין
רשמו בכתיב אסימפטוטי : 23 T( n) log n log n 2 5 T( n) n nlog n T( n) n log n n T n 2 3 3 ( ) log( n ) 2 Tn ( ) 3 n n T( n) 2 (log n) n T( n) (log n) (log n) T( n) ( nlog n) T n 3 ( ) ( n ) T( n) (log n) n Tn ( ) (9 ) n T( n) ((log n) ) 2 5 רן רובינשטיין
נתחו סיבוכיות זמן ומקום כפונקציה של n. 24 void f1 (int n) int j, k; for (j = 0; j < n; j++) printf("moo!"); מקום - מוקצים משתנה אחד ופרמטר אחד.Θ(1) = זמן - מנגנון הבקרה של for יבצע n) (n+1) + + (1 פעולות ישנן n הדפסות )זמן כ"א אינה תלויה ב- n ( סה"כ = 3n+2 פעולות =.Θ(n) בפרט, מספיק לחשב את מספר ההדפסות..1.2.3.4
נתחו סיבוכיות זמן ומקום כפונקציה של n. 25 void f1 (int n) int i, j, k; for (j = 0; j < n; j++) for (k = n; k > j; k--) printf("moo!");.θ(1) מקום - זמן מתחילים מבפנים החוצה. כמה פעמים הלולאה הפנימית תתבצע עבור n? j=0? j=1 n-1 1? j=n-1
נתחו סיבוכיות זמן ומקום כפונקציה של n. 26 void f1 (int n) int i, j, k; for (j = 0; j < n; j++) for (k = n; k > j; k--) printf("moo!"); סה"כ: n + n 1 + n 2 + + 1 = = 1 n n + 1 = Θ n2 2
ועכשיו? 27 void f1 (int n) int i, j, k; for (i = 1; i < n; i *= 3) for (j = 0; j < n; j++) for (k = n; k > j; k--) printf("moo!"); אנחנו יודעים את T(n) של שתי הפנימיות. כמה פעמים החיצונית תפעיל אותן? בכל שלב i מוכפל ב- 3 בשלב ה- k, ערכו של i יהיה 3. k הלולאה מופסקת כש-.i n כמה איטרציות זה ייקח?
נתחו סיבוכיות זמן ומקום כפונקציה של n. 28 void f2 (int n) int i; int* ar = (int*) malloc(n*n * sizeof(int)); for (i = n; i > 0; i -=2) i++; free(ar); מקום - מוקצים sizeof(int) n*n * בתים של זכרון, לכן ) 2.Θ(n זמן - הלולאה מתבצעת 2/n פעמים, לכן.Θ(n)
נתחו סיבוכיות זמן ומקום כפונקציה של n. 29 void f3 (int n) int i, j, s = 100; int* ar = (int*) malloc(s * sizeof(int)); for (i = 0; i < n; i++) s = 0; for (j = 0; j < n; j += 2) s += j; printf("%d\n", s); free(ar); מקום - מוקצים sizeof(int) s * בתים של זכרון, אבל s אינו תלוי ב- n. לכן (1)Θ. זמן - חיצונית -.n פנימית -.n/2 סה"כ ) 2.Θ(n
נתחו סיבוכיות זמן ומקום כפונקציה של n ו- m 30 void f1 (int n, int m) int k = n, i, j; for (i = 1; i <= m; i++) for (j = k; j > 0; j--) printf("moo"); k *= n; מקום -.Θ(1) זמן - שימו לב שהשורה האחרונה אינה מגוף הלולאה. חלק פנימית - k=n פעמים. חיצונית - m פעמים. סה"כ.Θ(nm)
נתחו סיבוכיות זמן ומקום כפונקציה של n 31 void f3 (int n) int **p; for (i = 1; i <= n; i++) p = (int**)malloc(i*n*sizeof(int*); for (j = 1; j <= i*n; j++) p[j] = (int*)malloc(sizeof(int)); for (j = 1; j <= i*n; j++) free(p[j]); free p; מקום - באיטרציה ה- i מוקצים ) sizeof(int*)+sizeof(int) i*n*( בתים בין האיטרציות, כל הזכרון משוחרר. בסיבוכיות מקום אנחנו מחפשים את המקסימום )ולא הסה"כ( לכן ) 2 Θ(n עבור.i=n
נתחו סיבוכיות זמן ומקום כפונקציה של n 32 void f3 (int n) int **p; for (i = 1; i <= n; i++) p = (int**)malloc(i*n*sizeof(int*); for (j = 1; j <= i*n; j++) p[j] = (int*)malloc(sizeof(int)); i*n for (j = 1; j <= i*n; j++) free(p[j]); i*n free( p ); זמן - )לרוב מניחים ש- malloc ו- free לוקחות )1(Θ זמן( 2 1 n + 2 2 n + + 2 n n = = 2n 1 n n + 1 = Θ n3 2
נתחו סיבוכיות זמן ומקום כפונקציה של ו- k n 33 void f4 (int n, int k) int i, j, *ar; for (i = 0; i < n; i++) for (j = k; j > 1; j /= 7) ar = (int*)malloc(i*j*sizeof(int)); printf("moo"); free(ar) for (i = 0; i < k; i++) for (j = k; j > 1; j--) printf("yuu"); מקום - באיטרציה ה- i, j מוקצים ) sizeof(int) i*j*( בתים בין האיטרציות, כל הזכרון משוחרר. בסיבוכיות מקום אנחנו מחפשים את המקסימום )ולא הסה"כ( לכן Θ(nk) עבור.j=k,i=n
נתחו סיבוכיות זמן ומקום כפונקציה של n ו- k 34 void f4 (int n, int k) int i, j, *ar; for (i = 0; i < n; i++) for (j = k; j > 1; j /= 7) ar = (int*)malloc(i*j*sizeof(int)); printf("moo"); free(ar) log 7 k for (i = 0; i < k; i++) for (j = k; j > 1; j--) printf("yuu"); k 2 n זמן: Θ n log k + k 2 שימו לב: איננו יודעים את הקשר בין לכן לא ידוע מי יותר דומיננטי. ו- k,
נתחו סיבוכיות זמן ומקום כפונקציה של n 35 void f(int n) for( ; n > 0; n /= 2 ) int i; for( i=0; i<n; i++ ) printf( "Booya!" ); זמן: Θ(n) מקום: (1)Θ
נתחו סיבוכיות זמן ומקום כפונקציה של n 36 void f3(int n) int i, m=1; for(i = 0; i < n; i++ ) m *= n; while( m > 6) m /= 3; זמן: n) Θ(n log מקום: (1)Θ
נתחו סיבוכיות זמן ומקום כפונקציה של n 37 int f4(int n) int i, j, k = 1, count = 0; for(i = 0; i < n; i++) k *= 3; for(j = k; j; j /= 2) count++; return count; זמן: ) 2 Θ(n מקום: (1)Θ