fbpx

01. מבוא וחישוב זמן ריצה

מבוא לניתוח אלגוריתמים

אז למדנו לתכנת. האם זה הופך אותנו למתכנתים טובים? או בכללי, למהנדסים טובים?

לשם כך אנו נרצה להבין איך לשפר את יעילות הקוד והמערכות שאנו יוצרים או עובדים איתם.

מה ההבדל בין מי שלמד תכנות בקורס או סדרת קורסים מרוכזת, לעומת מי שלמד תואר במדעי המחשב? האם רק הצגת היכולות האקדמיות כמו המתמטיקה זה מה שחשוב? אז האמת היא שלא. מי שלמד מדעי המחשב, מכיר את השפה והכלים בהם מודדים איכויות של מערכות.

בתואר להנדסת מחשבים או מדעי המחשב, החלק שמוקדש ללמידת שפת תכנות, הוא יחסית מועט. לעומת זאת הלמידה מתאפיינת הרבה בשיטות עבודה ויכולת פתרון בעיות. 

בקורס הזה אנו נכיר את אבני הבסיס של הדברים הללו. 

אחד הנושאים המרכזיים של הקורס, הוא מדידת יעילות של תוכניות מחשב. כמו כן, בקורס נכיר שיטות נפוצות לשמור נתונים בזיכרון. למשל, מערך, מחסנית וכדומה. כשהמטרה היא אחת: לייעל את העבודה שלנו.

מטרות הקורס:

  1. ניתוח זמן ריצה של אלגוריתם.
  2. הכרת סוגים שונים של מבני נתונים.
  3. מימוש מבני נתונים (מימוש רעיונות למבנים).
  4. הכרת כלים לבחינת יעילות (השוואה בין חלופות).
  5. בחירת מבנה נתונים לפתרון בעיה אלגוריתמית וניתוח יעילות האלגוריתם.
  6. היכרות מעמיקה עם בעיות המיון ומימושים שונים לפתרונן.


ונתחיל עם מעט הגדרות בסיסיות

הגדרות:

  1. אלגוריתם: סדרת צעדים ההופכת קלט לפלט.
    דוגמאות: מיון רשימת מספרים, חיפוש מחרוזות, דחיסת טקסטים ועוד.
  2. מבנה נתונים: אחסון קבוצת נתונים ואוסף של פעולות המותרות עליהם.
    דוגמאות: מערך, מחסנית, תור, עץ בינארי וכו’.
  3. מופע של בעיה: דוגמה של קלט ספציפי.
  4. נכונות אלגוריתם: אלגוריתם הוא נכון אם עבור כל מופע של קלט, האלגוריתם עוצר (נמנע מלהגיע ללולאה אינסופית) עם הפלט הנכון.

 

“פסאודו-קוד” Pseudo Code 

במהלך הקורס, אנו בדרך כלל נכתוב את הקוד בפסאודו קוד, שזוהי כתיבה לא רשמית של קוד. “פסאודו קוד” זוהי דרך להציג מה על שורות הקוד לעשות, בלי לרדת לרמת דיוק של התחביר. אולם בדרך כלל זה יהיה דומה לפייתון.

הגדרה רשמית: פסאודו קוד הוא תיאור של “שפה עילית בלתי רשמית” של יישום עקרונות של תוכניות מחשב או אלגוריתמים. 

ויקיפדיה: פסאודו קוד: הוא תיאור מצומצם ולא רשמי לאלגוריתם של תוכנית מחשב. פסאודו קוד משתמש בקונבנציות של שפות תכנות, אך מיועד לקריאה של בני אדם ולא לקריאה על ידי מחשב. הביטויים שנכתבים פסאודו קוד אינם ניתנים להידור (עיבוד הטקסט על ידי מחשב) בפני עצמם, אך הם ניתנים לתכנות כקוד אמיתי שכן ניתן להידור בכל שפת תכנות שהיא.

נוהגים בסיסיים בשימוש בשפת פסאודו קוד:

  1. מערכים בדרך כלל מאותחלים ב-1 בשפת פסודו קוד. (ולא ב-0, כפי שאנו רגילים ב ++C ופייתון)
  2. לעיתים לצורך “הצבה” של ערך למשתנה, משתמשים בסימן חץ במקום ב “=”:
    אם נסמן  a 1 המשמעות היא הצבת הערך 1 לתוך משתנה בשם a.
  3. במקום להשתמש בסוגריים לסימון {“בלוק”} בפסודו קוד נהוג להשתמש כמו בפייתון בהזחה דהיינו הזזת שורות הקוד השייכות לאותו בלוק, מספר רווחים ימינה.

 

למעשה גם תיאור מילולי מדויק של אלגוריתם, ייחשב לפסאודו קוד, זה לא חייב להיות קוד.
המטרה היא שקל יהיה להמיר את האלגוריתם הכתוב בפסאדו, לשפת תכנות כלשהי. כך כל מתכנת יכול לממש/לתרגל בשפה שהוא רגיל אליה.

כמובן ההנחה היא שאתם כבר יודעים לתכנת, לכתוב לולאות וליצור פונקציות.

אז חבל על הזמן, בואו נתחיל…

 

יעילות של אלגוריתמים

ניתוח אלגוריתם:

הערכת יעילות של אלגוריתם היא הערכת כמות המשאבים שהאלגוריתם דורש. 

מהם המשאבים?

  1. זמן ריצה: דהיינו מספר צעדי החישוב שהאלגוריתם מבצע. שזה אוסף הפעולות הבסיסיות – כמו חיבור, חיסור, הצבה וכדומה. (אין משמעות לכמה זמן הריצה של פקודה בודדת תיקח, שזה משהו שתלוי גם על איזה מחשב מבוצע החישוב, וכמובן שלכל מחשב ייקח זמן שונה. אלא המדידה היא במספר סך הפעולות הבסיסיות הנדרשות)
  2. מקום: כמות הזיכרון שצורך האלגוריתם מזיכרון המחשב.

אנו רוצים לדעת לנתח אלגוריתמים, כי אם נדע לעשות זאת, נוכל להשוות בין חלופות. בין אלגוריתמים שונים לאותה הבעיה.

זמן הריצה של אלגוריתם בדרך כלל תלוי בקלט. ככל שהקלט יהיה גדול יותר, כך מספר הפעולות שיאלץ האלגוריתם לבצע יהיה גדול יותר. 

לשם הנוחות, נגדיר פונקציה המסומנת:

T(n)

למשל, אם יש אלגוריתם כלשהו למיון מערך עם n איברים, וסימנו:

T(n) = 𝑛²

זה אומר שמספר הפעולות הבסיסיות שהאלגוריתם הזה מבצע עבור קלט n, הוא n בריבוע.

נצלול לדוגמה פשוטה של לולאת for:

				
					f(A)    // הגדרת פונקציה המקבלת מערך
     for i=0 to n :        
         print i	
				
			

בדוגמה זו, ישנו מימוש “אלגוריתם” להדפסת מערך.

שורה מספר 1 תבוצע ע”י ריצת האלגוריתם פעם אחת בלבד – בעת כניסה לפונקציה.
שורה מספר 2 תבוצע n + 1 פעמים כנגד כל פעם שבודקים את תנאי הלולאה.
(מאחר שלפני סיום הלולאה יש את הבדיקה האחרונה של תנאי הלולאה – כשהוא כבר לא מתקיים – אז זה פלוס 1)
שורה מספר 3, תיקרא ע”י המחשב n פעמים בדיוק.

ואם כך, זמן הריצה של תוכנית זו יסומן כחיבור של כל זמני הריצה של כל שורה:

T(n) = 1 + (n+1) + n

דוגמא נוספת:

נתונה סדרת מספרים בגודל n בתוך מערך בשם A. האלגוריתם הבא מדפיס את סכומם.

				
					s(A)  
     sum = 0
     for  i=1  to  size(A)  
	     sum = sum + A[i]
     return  sum

				
			

גודל הקלט של דוגמה זו, הוא מספר האיברים שיהיו במערך A. 

בדוגמא הזו: את השורה הראשונה והשנייה, המחשב יקרא פעם אחת. אך את השורה השלישית, שמכילה תנאי for, בדומה לדוגמה האחרונה, המחשב יקרא n +1 פעמים. n פעמים כנגד כל כניסה ללולאה, ועוד פעם אחת כנגד הבדיקה האחרונה להתקיימות תנאי הלולאה.

את השורה הרביעית, המחשב יקרא/יבצע n פעמים בדיוק. ואת השורה החמישית, הוא יקרא ויבצע פעם אחת בדיוק. (כי היא מחוץ ללולאת ה-for). סך חישוב זמן הריצה יהיה חיבור בין כל השורות כאשר כל שורה מוכפלת במספר הפעמים שהמחשב “עובר” עליה:

T(n) = 1 + 1 + (n+1) + n + 1

למעשה, מספר הפעולות שהאלגוריתם מבצע, יכול להיות תלוי לא רק בגודל של הקלט, אלא גם בצורה שבה הוא מסודר. למשל, כמו בדוגמה הבאה.

דוגמא נוספת: מיון הכנסה

מהו מיון הכנסה?

הנה סרטון, צפו בו, לאחר מכן ננסה לתאר את האלגוריתם גם מילולית.

 

מיון הכנסה, Insertion Sort

הבעיה: נתונים אוסף מספרים במערך, ונדרש למיינם.

תיאור האלגוריתם: אנו נעבור על כל איברי המערך, החל מהשני, בכל פעם נסמן איבר אחד, ו”נדמיין” שהאיברים משמאל ממויינים כבר, וכל שנותר הוא רק נכניס את האיבר באיטרציה הנוכחית למקום המתאים לו.

נתחיל עם סימון האיבר השני, לדוגמה:

4
2
7
8
1
6
1
3
5

אכן בשלב הזה, האיברים משמאל, שזה רק איבר אחד – 5, הם ממויינים. (מספר אחד הוא נחשב לסדרה ממויינת, כי אין חשיבות לסדר כשמדובר במספר אחד).

אז נכניס את 3 למיקום המתאים לו, שזה קודם ל-5, במילים אחרות, נחליף ביניהם:

4
2
7
8
1
6
1
5
3

ונמשיך בסימון האיבר הבא, השלישי משמאל:

4
2
7
8
1
6
1
5
3

נשים לב שאכן המערך משמאל (הערכים באינדקסים הקטנים מהאינדקס של האיבר המסומן) ממויינים, ורק נשאל איפה לשים את 1, והתשובה היא לפני כולם, אז נקדם את הערך אינדקס 2 למיקום האינדקס 3 (נקדם את הערך 5 אחד ימינה), ואת הערך באינדקס 1 לאינדקס 2 (דהיינו: נקדם את 3 למיקום הישן של 5), ונשים את האיבר החדש 1 שהוא המסומן באינדקס 1 שהתפנה.

מה שעשינו, זה שכל איבר משמאל לאיבר המסומן שגם גדול ממנו, עשה “צעד ימינה” במערך.

ולמעשה, זוהי סדרת “החלפות” של האיבר החדש, מול האיברים משמאלו, עד שנגיע למספר הקטן ממנו, ואז לא נדרשת המשך החלפה.

בתום הפעולה, לאחר שנבצע זאת על האיבר האחרון, המערך כולו כבר יהיה ממויין.

אם עדיין הסתבכתם, זה הזמן ללכת לגוגל/יוטיוב, ולחפש הסבר ממחיש נוסף ל-Insertion Sort

בשביל לבדוק את היעילות של האלגוריתם הקצת מסובך הזה, בואו נבחן קוד שמממש אותו:

הקוד:

				
					sort(A, size)	
     for  j=2  : size   // נרוץ על כל האיברים החל מהשני
        key = A [ j ];  // נסמן איבר
        i = j-1;        // אינדקס של האיבר הצמוד משמאל
        
        // כעת אם צריך, נקדם את כל המספרים הגדולים ממנו
        while (A[i] > key  AND  i >=0) // כל עוד האיבר הנמוך יותר גדול מהנוכחי
            arr[i+1] = arr[i]; // הזזה ימינה של האיבר משמאל הגדול מהמסומן		
            arr[i+1] = key;   // נציב את המסומן, במיקום המתאים, זוהי ההחלפה
            i--;   // נמשיך הלאה שמאלה, לביצוע החלפות נוספות לפי הצורך
				
			

אכן זהו אלגוריתם מעט מורכב, אז תנסו להריץ אותו על מערך, ולהבין תוך כדי מה הוא עושה.

 

ניתוח יעילות אלגוריתם המיון:

למעשה מספר הפעולות שיבוצעו באלגוריתם, תלוי באיזה “בלגן” הגיע הקלט (מראש).

אם המערך שהגיע, הוא במקרה כבר ממויין, אז סך הריצות של האלגוריתם יהיה יחסית קטן, אך כאשר הוא יהיה ממויין בסדר הפוך, זה יהיה המקרה ה”גרוע” שהאלגוריתם כנגד כל איטרציה של הלולאה החיצונית, יבצע מספר איטרציות מקסימלי בלולאה הפנימית.

אז איך מבצעים מדידה יעילה ומדויקת? אנו נצטרך להתייחס גם למקרה הגרוע וגם למקרה הטוב, והכי חשוב, נרצה לדעת מהו הזמן הממוצע. 

לשם כך נכיר את המושג “סיבוכיות”.

סיבוכיות זמן ריצה:

ויקיפדיה: בתורת החישוביות,סיבוכיות זמן של אלגוריתם היא הערכה באמצעות חסמים על מספר הפעולות שמבצע האלגוריתם במהלך פעולתו, כפונקציה של מורכבות הקלט.

  • זמן הריצה הגרוע ביותר – Worst Case, הינו זמן הריצה הארוך ביותר על איזשהו קלט בגודל n.
  • זמן הריצה הטוב ביותר – Best Case, הינו זמן הריצה הקצר ביותר על איזשהו קלט בגודל n.
  • זמן הריצה הממוצע – Average Case, הינו זמן הריצה הצפוי. כלומר הממוצע בין זמני הריצה של כל הקלטים האפשריים בגודל n. 

ישנן שיטות לחישוב ממוצע זמן, ואנו עוד נלמד עליהם.

ובינתיים, כבר התחלנו, אז נמשיך וננתח את אלגוריתם החיפוש של מיון ההכנסה הנ”ל.

להמשך הניתוח, נמספר את שורות הקוד. 

כעת נבדוק כמה פעמים המחשב יקרא כל שורה (הקוד הבא הינו בעל משמעות זהה, אך כתוב בפסאודו קוד שונה).

				
					for j ← 2 to n 
     key ← A[j] 
     // insert A[j] into the sorted sequence A[1…j-1]
     i ←  j-1
     while i>0 and A[i] > key
        A[i+1] ← A[i]
        i ← i-1
     end while
     A[i+1] ← key
				
			

בואו נבצע ניתוח מלא. נסמן כל שורה כ-C. כך למשל שורה 1 היא C1, ושורה 5 היא C5.

השורה הראשונה C1 מבוצעת n פעמים. (כיוון שאתחול ה-for הוא ב-2 והוא רץ עד n+1. ובסוף פעם נוספת לשם בדיקת התנאי, כפי שהסברנו בדוגמאות הראשונות).

שורה C2 מבוצעת n-1 פעמים. כמספר הפעמים שהתוכנית נכנסת בפועל ללולאת ה-for.

שורה C3 מחושבת מבחינת זמן ריצה כ-0. כי זו רק הערה.

שורה C4 מבוצעת גם כן n-1 פעמים כמו שורה C2.

שורה C5 מבוצעת n-1 פעמים (בהתאם ללולאת ה-for), אך כיוון שהיא כשלעצמה מהווה לולאה פנימית, מספר הפעמים שהיא תרוץ בכל איטרציה של ה-for, זה מספר שתלוי בעוצמת ה”בלגן” של הקלט של סדרת המספרים.
משתנה זה נסמן ב-T𝒊. כאשר המשתנה T הוא אחד מאוסף משתנים שכל אחד הוא כנגד מספר הריצות הפנימי בכניסה בודדת אחת של לולאת ה-for החיצונית.
כך למשל T16 מסמן את מספר הפעמים שלולאת ה-while רצה כנגד האיבר ה-16 מלולאת ה-for.

עכשיו בואו ננסה להחזיק ראש:
סך הפעמים ששורת התנאי של while תבוצע היא:    T1 + T2 + T3 ….. Tn (סדרה מ-T2 ועד Tn)
בלי להיבהל, נבין שזה סימון מתמטי מקוצר הנראה כך:

\[ \sum_{j=2}^n T_i \]

(אם ממש מפחיד אתכם הסימון הזה, ואתם סקרנים להבין אותו יותר, הנה סרטון שעושה זאת)

שורה C6 מתבצעת דומה לשורה 5. אלא פחות אחד. (הבדיקה האחרונה של ה-while שנספרה בשורה 5, לא נספרת כאן). ואם כך, זו כמות הפעמים שלה:

\[C_6 = \sum_{j=2}^n T_i -1\]

שורה C7 כמו שורה 6:

\[ \sum_{j=2}^n T_i -1\]

ושורה C8 מבוצעת n-1 פעמים (כמו שאר השורות תחת for, שאינן תחת ה-while).

 

נסכם כעת את הזמנים:

סה”כ זמן ריצה של מיון הכנסה יהיה תלוי במספר הפעמים שכל שורה “תיקרא”, ולכן סך הזמן יהיה:

ננסה כעת לפשט את הנוסחה לפי זמן המקרה הטוב ביותר או הגרוע ביותר.

בזמן הריצה במקרה הטוב ביותר, המערך יהיה כבר ממוין, ולכן לולאת ה-while לא תיכנס כלל (תנאי הכניסה ללולאה לא יתקיים). רק שורת התנאי שלה תבוצע n-1 פעמים. שזה אומר שכל T𝒊 שווה ל-0, ולכן כל הביטוי עם הסימן המורכב, יהיה 0. 

ואם כך, הנוסחה תיראה כך:

אם הבנו נכון, במקרה הטוב, השורה C5 תבדוק את תנאי הלולאה בכל איטרציה של ה-for, ולכן היא תרוץ n-1 פעמים, אבל השורות תחת לולאה ה-while לא תתבצענה כלל.

 

נפתח סוגריים בביטוי המתמטי הקודם, נוציא גורם משותף n, ונקבל:

= (c1+c2+c4 + c5+c8) n – (c2+c4+c5+c8)

זוהי פונקציה ליניארית של n שהמבנה שלה בצורה הבאה:

a ∙ n + b

הפרמטרים a וגם b הם מספרים קבועים כלשהם, והנוסחה בעצם מתארת לנו שזמן הריצה הוא “ליניארי”, דהיינו כפל של n בקבוע, כשה-n מופיע ליניארית, ללא “חזקה” כלשהי (בהמשך נתאר יותר את המשמעות).

במקרה הגרוע:

זמן הריצה במקרה הגרוע יהיה במצב בו הקלט יהיה ב”בלגן” מוחלט.

במקרה כזה, עבור כל כניסה ללולאת ה-for החיצונית, המחשב יבצע אף מספר כניסות (מקסימאלי) בלולאת ה-while הפנימית.

המספר המקסימאלי כמובן לא יכול להיות יותר מכמות האיברים שיש משמאל לאיבר המסומן בכל איטרציה. ולכן מספר הכניסות המקסימלי ללולאה הפנימית יהיה בכניסת ה-for הראשונה, 1, בשנייה 2 בשלישית 3 וכן הלאה, כך שסך הכניסות ללולאה יהיה:

1+2+3+4 ….. + n

אפשר לזהות פה סדרה חשבונית, שכפי שאנו זוכרים מהבגרות, הסכום שלה שווה ל:
האיבר הראשון פלוס האיבר האחרון, כפול מספר האיברים, חלקי 2.

\[S_n = \bigl(a_1+a_n \bigr) ∙ {n \over 2}\]

הנוסחה המלאה תיראה כך:

נפשט את הפונקציה ונקבל:

זוהי פונקציה ריבועית של n שצורתה:

a𝑛² + b𝑛 + c

המשמעות של התוצאה הינה שעלות זמן הריצה במקרה הגרוע, מגיע לסדרי גודל של n בריבוע. 

 

במקרה הממוצע:

במקרה זה, אנו נגדיר כל משתנה שתלוי ב”בלגן” שהמערך היה נתון בו, להיות שווה לחצי מהמקסימום. דהיינו:

\[T_j = {j \over 2}\]

אם נפשט את הפונקציה הכללית ע”י נתון זה, הצורה של התוצאה גם תהיה ריבועית.

בניתוח זמן ריצה, מתייחסים רק ל“קצב הגידול” של המשוואה. קצב הגידול הוא שווה לחזקה הגבוהה הקיימת בנוסחה. (תוך כדי התעלמות מהמקדם שלה). בדוגמה שלנו, במקרה הגרוע, ה”חזקה” הגבוהה ביותר זה n בריבוע, ולכן זהו קצב הגידול, וזהו זמן הריצה.

את קצב הגידול נהוג לסמן באות היוונית הקרויה תטא. Theta Θ.

הסימון באמצעות “תטא” (Θ) בדוגמא שלנו:

במקרה הגרוע:

T(𝑛) = a𝑛² + b𝑛 + c = Θ(𝑛²)

במקרה הטוב:

T(𝑛) = a𝑛 + b = Θ(𝑛)

במקרה הממוצע:

T(𝑛) = a𝑛² + b𝑛 + c = Θ(𝑛²)

בדרך כלל אנו מעוניינים בזמן הריצה של המקרה הגרוע ביותר ממספר סיבות:

  • המקרה הגרוע מהווה חסם עליון (מקסימלי) של פעולות על זמן הריצה עבור כל קלט אפשרי.
  • במקרים רבים המקרה הגרוע הוא גם המקרה השכיח (המצוי).
  • בדרך כלל המקרה הממוצע, הוא “גרוע” בערך כמו המקרה הגרוע ביותר. (כמו בדוגמה שלנו).
  • קלות – יותר קל לחשב את המקרה הגרוע מאשר הממוצע. 

הערות: 

  1. אנו נתעלם מזמן הריצה על קלטים קטנים. (אם אלגוריתם מסוים טוב עבור קלט מאוד קטן, אך גרוע עבור קלטים גדולים, אנו נתעלם מהיתרון הקטן (תרתי משמע) שלו.
  2. אנו נתעלם ממכפלה בקבוע. אם זמן הריצה הוא 4n, אנו פשוט נציין n.
  3. אם מחשב מהיר יותר יבצע פעולה בזמן ריצה קצר יותר, זה לא יעניין אותנו. מבחינתנו זה כמו מכפלה בקבוע. המשמעות של “עוצמת המחשב” לא באה לידי ביטוי בניתוח האלגוריתם.
  4. אנו בדרך כלל נתעניין בסדר גודל של זמני ריצה, ולא נחפש זמני ריצה מדויקים ממש (החלשים במתמטיקה יכולים לנשום לרווחה), לרוב לא נבצע שוב את הדוגמא המלאה האחרונה הכוללת חישובים מתמטיים מעט קשים.

אם לאור כללים אלו היינו נגשים לאלגוריתם הנ”ל, היינו מחפשים את השורה שרצה הכי הרבה פעמים במקרה הגרוע, שאלו השורות תחת ה-while, עושים חישוב זריז ומבינים שיש פה סדרה חשבונית, וממילא הסדר גודל הוא n בריבוע. זה אמנם נשמע לא כל כך טריוויאלי, אבל לאחר שנעשה זאת כמה פעמים על דוגמאות דומות, הדוגמה שעשינו תיראה לנו פשוטה.

 

חשיבות אלגוריתם יעיל

אז למה בכלל להתאמץ לכתוב אלגוריתם יעיל כאשר ברשותנו מחשבים מהירים וחזקים, בעלי קיבולת זיכרון ענקית, ומעבדי על..?

התשובה היא שזה משנה מאוד. 

כדי לסבר את האוזן: על קלטים גדולים, התוספת של מחשב מהיר פי 100, היא שולית אם האלגוריתם הוא לא יעיל. 

הטבלה הבאה מתארת את מספר הפעולות הנדרשות לאלגוריתם שזמן הריצה שלו הוא מעריכי, לעומת אלגוריתם שזמן הריצה שלו הוא n*logn.

N
N • log₂N
10
100
33
100
10,000
665
1,000
מיליון
9,966
מיליון
אלף מיליארדים
20 מיליון
מיליארד
מיליארד מיליארדים
30 מיליארד

כאשר n שווה מיליארד, ההבדל עצום. הרבה יותר מסתם פי 100 שמחשב יעיל יוכל לעזור…

 

לסיכום: יעילות אלגוריתם זה דבר מאוד חשוב, משום שככל שמספר הפעולות גָדֵל ביחס לגודל הקלט, תוספת יעילות למהירות מעבד, תהיה שולית יותר.

אז איך מחפשים אלגוריתם טוב?

כשנגש לפתור בעיה, תחילה נחפש/נציג מספר אלגוריתמים לפתרון שלה, ולכל אלגוריתם שנמצא, נחשב את זמן הריצה שלו, ונשווה בין זמני הריצה השונים.


כל זאת ועוד… בשיעור הבא.

כניסה

שכחתי סיסמה

אין לך חשבון? להצטרפות

איפוס סיסמה:

שכח את הסיסמה? הזן את שם המשתמש או כתובת הדואר אלקטרוני. תקבל קישור לאיפוס סיסמה באמצעות דואר אלקטרוני.

ברכות! סיימתם את השיעור הראשון בקורס!

עשיתם את הצעד הראשון בדרך שלכם להיות מתכנתים מקצועיים
הצטרפו אלינו כדי להמשיך במסלול שיוביל אתכם לעתיד בהייטק

שינוי סיסמה

עריכת פרטים אישיים