אלגוריתמים הסתברותיים

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

Pasted image 20230207151136.png|350

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

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

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

באלגוריתמים רנדומיים ישנם שני סוגים של משתנים מקריים

  1. זמן הריצה הוא משתנה מקרי (תלוי ב r)
  2. נכונות האלגוריתם היא גם כן משתנה מקרי (היא תלויה ב r)

הדוגמה הכי קלאסית לאלגוריתם עם זמן ריצה מקרי הוא Quick Sort שבו ה pivot נקבל בצורה אקראית. אם הבחירה של הפיבוט היא רעה אנחנו יכולים להגיע לזמן ריצה של O(n2) אבל בתוחלת נקבל שזמן הריצה הוא O(nlogn) .

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

סוגים של אלגוריתמיים רנדומים

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

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

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

וידוא כפל מטריצות בינאריות

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

קלט: 3 מטריצות בינאריות A,B,C מגודל n×n
פלט: האם C=AB

הנאיבי הוא כמובן להכפיל עם כפל מטריצות מהיר בזמן ריצה O(nω) ולהשוות. אבל זמן ריצה זה לא פרקטי ונראה משהו מהיר יותר.

אם כן, נראה אלגוריתם שזמן הריצה שלו הוא O(n2) אבל הוא טועה בסבירות יחסית גבוהה של לכל היותר 12 .
לאחר מכן נשפר את סיכויי ההצלה לאלגוריתם שזמן הריצה שלו הוא O(n2logn) אבל הוא צודק בסיכוי גבוה, כלומר הסיכוי לטעות הוא 1nα כפי שרצינו.

אלגוריתם בסיסי

verify_binary_MM_basic(A,B,C)
	choose random vector V = (v1,....,vn) of bits
	Vb = B * V
	Vab = A * Vb

	if Vab = C * V
		return true
		
	return false

נשים לב שאם C=AB אזי לכל וקטור v מגודל n מתקיים Cv=ABv באופן די הגיוני וזה מה שהאלגוריתם בודק.

זמן ריצה
בחירת הוקטור היא ב O(n) זמן.
כל כפל עם וקטור לוקחת O(n2)
השוואה לוקחת O(n) .
סה״כ O(n2)

הבחנה

אם C=AB אז האלגוריתם תמיד צודק. אבל אם CAB אז בחירה של וקטור לא טוב יכולה לגרום לאלגוריתם לטעות (למשל וקטור ה0).

ניתוח הסיכוי לטעות
יהי D=CAB ונניח CAB . לכן D0 . בפרט זה אומר שקיים dij=1 (נזכיר שהמטריצות בינאריות).

לשם הסמנטיקה נאמר שוקטור v הוא רע אם מתקיים במצב שבו CAB אבל

Dv=(CAB)v=0

אם הוא לא רע נאמר שהוא טוב. נשים לב v is good Dv0

למה
מספר הווקטורים הטובים הוא לפחות כמספר הווקטורים הרעים .

הוכחה
נתאר פונקצייה חח״ע שממפה וקטורים רעים לווקטורים טובים. יהי v וקטור רע. כלומר Dv=0 אזי

1in:k=1ndikvk=0

בגלל ש v רע אז מתקיים בלי הגבלת הכלליות dij=1 . נגדיר

wj=(0010)

כאשר 1 נמצא בשורה ה j.

יתקיים ש

v=v+wj

נראה ש v וקטור טוב

k=1ndikvk=k=1ndik(vk+wk)=k=1ndikvk+k=1ndikwk=dij=1

k=1ndikvk הוא 0 כי v וקטור רע. וגם wj הוא האיבר היחיד שיהיה 1 בוקטור wj ולכן כל האחרים יהיו 0.

כלומר הצלחנו לבנות פונקצייה שממפה וקטור רע לטוב.

f(v)=v+wj

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

מסקנה - הסיכוי לבחור וקטור רע הוא לכל היותר 12 .

אמפליפיקציה

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

verify_binary_MM(A,B,C)
	k = alog(n)
	for i = 1 to k
		if verify_binary_MM_basic(A,B,C) = false
			return false
	return true

חישוב הסיכוי לטעות:
על מנת שהאלגוריתם הנ״ל יטעה מספיק שנבחר פעם אחת וקטור רע כלומר שהפלט של האלגוריתם הבסיסי אמור להחזיר שהם ״לא שווים״ אבל הוא מחזיר ״שווים״ נחשב את ההסתברות לכך

P(each iteration a bad vector was chosen)=P(make one iteration wrong)k(12)k=12k

המעבר הראשון נובע מאי תלות בין הבחירה של הוקטור.
כפי שאמרנו מטרתנו היא לטעות בסבירות של לכל היותר 1nα אם כן

12k=1nαk=αlog(n)

שזה הסיבה שבחרנו את k הזה

זמן ריצה k איטרציות שכל אחת O(n2) סה״כ O(n2logn) .

Quick sort פרנואידי

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

המקרה הרע הוא מצב בו r=1 או r=n . בשני אלו נקבל סדרה חשבונית של מספר ההזזות שנעשה שמתחילה ב1 ומסתיימת ב n כלומר מסתכמת ב O(n2)

המקרה טוב ar הוא חציון של A במצב זה נקבל נוסחת נסיגה לפי משפט המאסטר שכל פעם עובדים עם חצי מערך ולכן נקבל O(nlogn) .

הבחנה

לא חייב שהחלוקה תהיה בהכרח כמו במקרה הטוב כדי לקבל זמן ריצה כזה, גם אם החלוקה היא כזו שגודל כל צד הוא לפחות n4 זמן הריצה יישאר O(nlogn)

מההבחנה הזאת אנחנו יכולים לבנות גרסה אחרת של האלגוריתם שנקראת מיון מהיר פרנואידי. מה שהוא עושה באופן די פשוט זה , אם לאחר חלוקה התנאי הנ״ל לא מתקיים כלומר, אם נסמן L כצד הקטן יותר של ar ו G כצד הגדול יותר, אם מתקיים |G|<n4 or |L|<n4 נבטל את בחירת הפיבוט שלנו ונחפש פיבוט חדש.

נוסחת הנסיגה של האלגוריתם הרגיל ייראה ככה

T(n)=T(|L|)+T(|G|)+O(n)

נרצה להבין מהי תוחלת זמן הריצה כלומר ה״ממוצע״ של זמן הריצה שלנו.

E[T(n)]=E[T(|L|)+T(|G|)+time to find a good pivotO(n)]=E[T(L)]+E[T(G)]+E[number of times to find a pivot]O(n)

O(n) מייצג את מספר ההשוואות.

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

על מנת שהפיבוט יהיה רע הוא צריך להיות או ברבע האיברים הכי קטנים או ברבע האיברים הכי גדולים. כדי שפיבוט יהיה טוב הוא צריך להיות בטווח שבין [n4+1,3n41] כלומר חצי מהאיברים הם טובים וחצי הם לא טובים בהסתברות שווה לבחירה פיבוט מכל מקום ולכן

p=121p=2

Pasted image 20230207183537.png|450

סך הכל אם נציב ונחשב את התוחלת נקבל

E[T(n)]=O(nlogn)

Quick sort - ניתוח תוחלת זמן הריצה

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

עבור

A=(a1,a2,,an)

נבנה

(y1,y2yn)  |  i>jyi>yj

מתי האלגוריתם משווה בין yi ל yj ? זה קורה כאשר אחד מהם נבחר להיות הפיבוט (בלי הגבלת הכלליות yj>yi) ועד הרגע שבו yj נבחר מתקיים

k[i,j]:yk was not a pivot

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

נגדיר משתנה אינדיקטור

Xij={1yj is compared with yi0else

נחשב את התוחלת

E[T(n)]=i=1n1j=i+1nE[Xij]

ומתקיים מתוחלת של משתנה אינדיקטור

E[Xij]=P(Xij=1)

בטווח בינהם יש ji+1 איברים וצריך לבחור 2 מבינהם ולכן

P(Xij=1)=2ji+1

סך הכל נציב בתוחלת ונחשב נקבל

i=1n1j=i+1n2j1+1=2i=1n1(12+13++1ni+1)<2i=1n1l=1n1+11l=2i=1n1O(ln(ni+1))=O(nlogn)

מסקנה: מיון מהיר מבצע בתוחלת O(nlogn) השוואות. זהו גם זמן הריצה בתוחלת.

Bucket sort

האלגוריתם הוא דטרממינסטי כאשר הקלט מיוצר בצורה אקראית.
נרצה לפתר את בעיית המיון כאשר כל מספר בקלט נבחר בהתפלגות אחידה מטווח המספרים [0,1] סה״כ נרצה לבחור n מספרים.

בעזרת מיון מבוסס השוואות ניתן למיין בזמן Ω(nlogn) בתוחלת. נרד מזמן זה בעזרת מיון לא מבוסס השוואות. במקרה הגרוע האלגוריתם עדיין יכול לרוץ בזמן גרוע יותר.

הרעיון נקח את טווח המספרים ונחלק ל n דליים

Pasted image 20230207194330.png|250

לכל איבר יש סיכוי של 1n להיות בדלי ה i כלומר תוחלת מספר האיברים בדלי הוא n1n=1 נובע מליניאריות התוחלת שכן נוכל להגדיר סדרת ניסויים שבתוחלת 1n יהיו בדלי ה i ואם נסכום אותם נקבל שהתוחלת של n איברים להיות בדלי הi הוא הנ״ל.

אם כן אלגוריתם מיון דלי יעבוד באופן הבא:

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

function bucketSort(array, k) 
    buckets ← new array of k empty lists
    for i = 0 to length(array) 
        insert arr[i] into the correct bucket
    for i = 0 to k 
        nextSort(buckets[i])
    return the concatenation of buckets[0], ...., buckets[k]

Pasted image 20230207195603.png|250

נסמן את הדלי ה i כ

Bi=[i1n,in)

נגדיר ni משתנה מקרי המסמל את מספר האיברים ב Bi ויתקיים

i[n]:E[ni]=1

זמן ריצה
הכנסה לדלי המתאים ליניארית O(n)
נמיין כל דלי בעזרת מיון הכנסה O(ni2)
שרשור הדליים לפי סדרם O(n)

סך הכל

O(n+i=1n(ni)2)

תוחלת זמן ריצה

E[n+(ni)2]=E[n]+E[(ni)2]=n+E[(ni)2]

נשים לב שהאלגוריתם לוקח n איברים וכל אחד מהם ״מצליח״ להכניס ל Bi בהסתברות 1n ו ni הוא משתנה מקרי שסופר את מספר ההצלחות בהסתברות 1n להצלחה. זהו למעשה התפלגות בינומית כלומר

niB(n,1n)

עם תוחלת E[ni]=1 ושונות

Var[ni]=np(1p)

מהגדרת השונות אנחנו יודעים ש

11n=E(ni2)(E[ni])2=E[(ni)2]1E[(ni)2]=21n

סה״כ נקבל

n+E[(ni)2]=n+(21n)=3n1=O(n)

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

מדוע בחרנו במיון הכנסה?

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