470 likes | 629 Views
תכנות – שיעור 7. חזרה -מערכים. נגדיר בעזרתו קבוצת משתנים כאשר יהיה לנו מספר רב של משתנים זהים Type name[SIZE]; Char a[10]; int b[50]; הגודל חייב להיות קבוע. פנייה לאיברי המערך מתבצעת ע"י a[k] כאשר K בין 0 ל- SIZE-1 אתחול ע"י לולאה או רשימת אתחול. החלפה בין משתנים.
E N D
חזרה -מערכים • נגדיר בעזרתו קבוצת משתנים כאשר יהיה לנו מספר רב של משתנים זהים • Type name[SIZE]; • Char a[10]; int b[50]; • הגודל חייב להיות קבוע. • פנייה לאיברי המערך מתבצעת ע"י a[k] כאשר K בין 0 ל-SIZE-1 • אתחול ע"י לולאה או רשימת אתחול
החלפה בין משתנים • נסתכל על התוכנית הבאה: int main() { int a,b,x,y,temp; temp = x; x = y; y = temp; temp = a; a = b; b = temp; } החלפת הערכים החלפת הערכים
החלפה בין משתנים • כפי שלמדנו במצב כזה הינו רוצים שתהיה פונקציה שתבצע את החלפה הזו בין המשתנים.
החלפה בין משתנים • נכתוב פונקציה כזו בשם swap void swap(int p, int q) { int tmp = p; p = q; q = tmp; }
החלפה בין משתנים int main() { int a=10,b=2,x=3,y=5,temp; swap(x,y); swap(a,b); }
החלפה בין משתנים • נעקוב אחרי הקריאה לפונקציה swap(x,y); void swap (int q,int p) { int temp = p; p = q; q = temp; }
מצביעים • ראינו שכל משתנה שהגדרנו בתוכנית שלנו מוצקה עבורו תא באזור כלשהו בזיכרון. int main() { Int x,y; } • לכל תא כזה יש כתובת בזיכרון של המחשב. • כדי להגיע לכתובת של משתנה משתמשים באופרטור &. x y
מצביעים • מה יחזירו &x ו&y . x5000 y5004
מצביעים • ניתן להגדיר משתנים שסוגם הוא מצביע. • משתנים כאלה מחזיקים בתוכם את הכתובות. int main() { int x,y; int* p; p = &x; } x5000 y5004 p9008
מצביעים • כדי לסמן שמצביע לא מצביע לשום מקום נהוג להשים לו NULL • p = NULL; • אפשר לחשוב על משתנה מסוג מצביע כאל חץ: x y p
מצביעים פורמט הדפסה של משתנה מסוג מצביע int main() { int x=7,*p = &x; printf(“Value %d Pointer %p\n”, x, p); } • כדי להדפיס את ערך שאילו מצביע p. printf(“Value %d\n”,*p)
מצביעים • יש לשים לב שאין הצבעה לביטויים ולקבועים &3 &(k+99)
Call by value and call by reference • בכל הפונקציות של c מנגנון העברת של הפונקציות הוא לפי ערך. • נחזור לפונקצית ה swap. • הבעיה הייתה שהחלפת הערכים התבצעה בין הפרמטרים של הפונקציה ולא בין המשתנים עצמם.
Call by reference int main() { int a=10,b=2,x=3,y=5; swap(&x,&y); swap(&a,&b); } void swap (int* q,int* p) { int temp = *p; *p = *q; *q = *temp; }
Call by reference • בכדי לממש call by reference יש לבצע את הדברים הבאים: 1 להגדיר את הפרמטרים של הפונקציה כמצביעים void swap(int *p, int *q) 2 להשתמש בגוף הפונקציה בערכים שליהם שמצביעים מצביעים *p = *q; 3 בקריאה לפונקציה להעביר כתובות כארגומנטים swap(&x, &y);
Call by value and call by reference • בכל הפונקציות של c מנגנון העברת של הפונקציות הוא לפי ערך. • נחזור לפונקצית ה swap. • הבעיה הייתה שהחלפת הערכים התבצעה בין הפרמטרים של הפונקציה ולא בין המשתנים עצמם.
Call by reference int main() { int a=10,b=2,x=3,y=5; swap(&x,&y); swap(&a,&b); } void swap (int* q,int* p) { int temp = *p; *p = *q; *q = temp; }
Call by reference • בכדי לממש call by reference יש לבצע את הדברים הבאים: 1 להגדיר את הפרמטרים של הפונקציה כמצביעים void swap(int *p, int *q) 2 להשתמש בגוף הפונקציה בערכים שלהם שמצביעים מצביעים *p = *q; 3 בקריאה לפונקציה להעביר כתובות כארגומנטים swap(&x, &y);
הקשר בין מערכים ומצביעים • כאשר אנחנו מגדירים מערך בגדול 20 של שלמים אזי יש לנו 20 משתנים מסוג int בזיכרון. מה הכתובת של משתנים אלו ? • int a[20]; a[0] כמו לכל משתנה גם לאיברי המערך ישנה כתובת. כיצד נגיע לכתובת זו? a[19]
הקשר בין מערכים ומצביעים • ניתן להגיע לכתובות בדרך הרגילה • int *p; • p = &a[0]; • נזכר שהמערך ממוקם בצורה רציפה בזיכרון לכן כתובות איברי המערך הן רציפות למשל: 5000 5004 5008
משמעות שם המערך • פניה לשם המערך נותנת לנו את כתובת המערך שהיא למעשה מצביע לאיבר הראשון במערך. • בשם המערך ניתן להשתמש כדי לקבל את הכתובת הזו לא ניתן לשנות אותו (כלומר הוא מצביע קבוע) • int *p; • p = a; • p מצביע לראש המערך.(את p כמובן שניתן לשנות)
חשבון של מצביעים • ניתן להשתמש בפעולות חיבור וחיסור על כתובות. זה שימושי בעיקר בשילוב עם מערכים. • *(a+i) שקולa[i]; • *(p+i) שקולp[i];
חשבון של מצביעים • מספר דרכים לכתוב לולאה שרצה על מערך: for (i=0; i<N; i++) sum + = a[i]; for (p = a, i=0; i<N; i++) sum + = *(p+i); for (p=a; p<=&a[N-1]; p++) sum + = *p;
חשבון של מצביעים • כיצד ה++ יודע לקדם את המצביע לאיבר הבא ? • הבעיה: המערך יכול להיות של תווים של שלמים או של כל דבר והמרחק בין התאים הוא שונה. • מה המרחק עבור int ? • מה המרחק עבורchar ?
חשבון של מצביעים • כיוון שהגדרנו את סוג האיבר שאילו המצביע מצביע ולכן יש לנו את הגודל שצריך לקפוץ בכל פעם. זו החשיבות והסיבה שמגדירים את סוג האיבר שאילו מצביעים. • המקום השני שבו אנו משתמשים בסוג שאליו מצביעים הוא בשימוש ב-*
העברת מערך לפונקציה • כיצד נכתוב פונקציה שמקבל כערך מערך ומבצעת חישוב על איבריו? • נניח שנרצה לכתוב פונקציה שמקבלת מערך של מספרים ומחזירה את סכום איברי המערך. • העברת מערך לפונקציה מתבצעת למעשה ע"י העברת מצביע לראש המערך. • int sum(int a[], int size); prototype
העברת מערך לפונקציה • a[ ] זהו פשוט מצביע למערך של int. • הפרמטר השני הוא גודל המערך. • מאחר ואנחנו עובדים עם מצביעים אין לנו מידע מהו גודל המערך ולכן נעביר את המידע הזה כפרמטר לפונקציה.
העברת מערך לפונקציה • ההגדרות הבאות שקולות: • int f(float arr[]); • int f(float arr[5]); • int f(float arr[100]); • int f(float *arr);
העברת מערך לפונקציה int sum(int a[], int size) { int i, res = 0; for(i = 0; i < size; i++) res += a[i]; return res; }
העברת מערך לפונקציה Int sum(int *p, int size) { int i, res = 0; for(i = 0; i < size; i++) res += p[i]; return res; }
העברת מערך לפונקציה • כאשר נרצה למנוע א אפשרות של שינוי איברי המערך בפונקציה נעביר את המערך כconst- int sum(const int a[], int size) { int i, res = 0; for(i = 0; i < size; i++) res += a[i]; return res; }
מיון מערכים • המטרה נרצה למיין מערך של מספרים כאשר האיבר הקטן יהיה בתחילת המערך והגדול בסופו. • בעיה חשובה במדעי המחשב • המטרה היא לפתור באופן יעיל ככל הניתן (אנו נציג פיתרון לא יעיל)
מיון מערכים • מציאת אלגוריתם למיון • פסאודו קוד • קוד תיקני ב-C
מיון מערכים-אלגוריתם • האלגוריתם: • נגדיר אלגוריתם בעל N שלבים כאשר בסוף השלב ה-K, K האיברים הקטנים ביותר יהיו ממוינים. • אלגוריתם משני לשלב ה-K • נמצא את האיבר המינימל מבין N-K+1 האיברים הגדולים(אשר נמצאים במערך בין N-(K ונחליף בינו לבין האיבר K.
מיון מערכים – פסאודו קוד min הערך המינימלי min_index האיבר בעל הערך המינמלי SORT(A) N = size(A) For k =1 to N min = A[k] min_index = k for j = k + 1 to N if (A[j] < min) min_index = j; min = A[j] end Swap(A,k,min_index); end מציאת האיבר המינימלי מ- k עד n
מיון מערכים –קוד Void swap(int *, int *); Void Sort(int A[], int size) { int min, min_index, k, j; for(k = 0; k < size; k++) { min = A[k]; min_index = k; for(j = k + 1; j< size;j++) { ….. } swap(A + k, A + min_index); } }
מיון מערכים –קוד for(j= k + 1; j < size; j++) { if (a[j] < min) { min = a[j]; min_index = j; } }
מיון מערכים • מציאת איבר יכולה להיעשות מהר log2(N)) כאשר במערך לא ממוין ייקח למצוא איבר N צעדים
מציאת איבר במערך ממוין – חיפוש בינארי int BinarySearch(const int arr[], int size, int key) { int low, middle, high; low = 0; high = size - 1; while(low <= high) { middle = (low + high) / 2; if (key == arr[middle]) return middle; if (key > arr[middle]) low = middle + 1; else high = middle -1; } return -1; }
מיון מערכים-יעילות • הזמן שייקח לתוכנית זאת למיין הוא סדר גודל של n^2 • האם ניתן לשפר זאת?
מיון בזמן ליניארי- אם הערכים בטווח קטן • בכדי לבצע מיון זה נצטרך באופן כללי שני מערכי עזר אחד למערך הממוין ואחד למערך עזר (כאשר איברי המערך הם מספרים ניתן להסתפק רק במערך העזר) • הרעיון יהיה לספור בעזרת מערך העזר כמה איברים קטנים מאיבר עם ערך k וכך ניתן יהי להכניסו למקום ללא בעיות
מיון בזמן ליניארי- אם הערכים בטווח קטן Counting_Sort(A,B) Set C[N] to 0 For i=1 to SIZE C[A[i]] = C[A[i]] + 1; For i=2 to N C[i] = C[i-1] + C[i] For j=1 to SIZE B[C[A[j]]] = A[j] C[A[j]] = C[A[j]] - 1 כמה איברים מכל סוג כמה איברים קטנים מאיבר מסוג k הכנסה למערך הפלט
void CountSort(const int a[], int b[], int size) { int i; int c[RANGE] = {0}; for(i = 0; i < size; i++) c[a[i]]++; for(i=1; i < RANGE; i++) c[i] += c[i-1]; for(i=0; i < size; i++) { b[c[a[i]]] = a[i]; c[a[i]]--; } return; }
#include <stdio.h> #define SIZE 10 #define RANGE 6 void CountSort(const int a[], int b[], int size); void Print(const int arr[], int size); int main() { int a[SIZE] = {5,3,2,5,5,4,1,1,0,0}, b[SIZE] ={0}; Print(a, SIZE); CountSort(a,b,SIZE); Print(b, SIZE); return 0; }
יעילות • תוכנית זאת תרוץ בזמן ליניארי ב-גודל המערך ובגודל הטווח אך תשתמש במקום עזר