לדלג לתוכן

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

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי

המבנה הרקורסיבי של הבעיה

[עריכה]

ראשית מספר סימונים:

  1. כפי שצוין בשאלה, נגדיר את אורך X כm, ואת אורך Y כn.
  2. נסמן את הרישה הi של מחרוזת X כXi. במילים אחרות, Xi=x1,...,xi.‏
  3. נגדיר כl(i,j) את אורך הLCS של Xi וYj. במילים אחרות, l(i,j) היא אורך תת-הסדרה הארוכה ביותר המשותפת לXi וYj.


עכשיו תורכם:

וודא שהמך מבין מדוע לפי הגדרה זו, l(m,n) הוא אורך הLCS של X וY.



משפט:

l(i,j) מקיים את הנוסחה הבאה:

  1. אם i=0 או j=0,‏ אז l(i,j)=0.
  2. אם לא,‏ אז:
    1. אם xi=yj, אז l(i,j)=1+l(i−1,j−1).
    2. אם xi≠yj, אז l(i,j)=max⁡{l(i−1,j),l(i,j−1)}.



הוכחה: אם i=0 או j=0, אז (לפחות) אחת המחרוזות היא המחרוזת הריקה, ורק המחרוזת הריקה יכולה להיות תת-מחרוזת של המחרוזת הריקה. למחרוזת הריקה יש אורך 0, ומכאן החלק הראשון של המשפט.


מכאן נניח ששתי המחרוזות אינן ריקות.

נניח שxi=yj. ראשית, קל לראות שבהכרח תווה האחרון של Z הוא xi - אם זה לא היה המצב, אז Zxi גם היא מחרוזת משותפת לXi וYj, דבר שנוגד עפ"י ההגדרה את היותה של Z תת-המחרוזת המשותפת הארוכה ביותר. נחשוב כעת על כל תתי המחרוזות המשותפות מהצורה Z′xi: עבור כל אחת מהן, קל לראות שZ′ היא תת-מחרוזת משותפת לXi−1 וYj−1, ולכן בהכרח הארוכה שבהן היא זו שהרישה שלה היא הLCS של Xi−1 וYj−1.

לחלופין, נניח שxi≠yj; יש שלוש אפשרויות:

  1. Zמסתיימת בxi - אם כן, הLCS בהכרח מהצורה Z′xi, כאשר Z′ היא הLCS של Xi וYj−1.
  2. Z מסתיימת בyj - אם כן, הLCS בהכרח מהצורה Z′yj, כאשר Z′ היא הLCS של Xi−1 וYj.
  3. Zאינה מסתיימת בxi, וכן אינה מסתיימת בyj - אם זה המצב, אז הLCS של Xi−1 וYj−1 הוא הLCS של Xi−1 וYj (וכן גם הLCS של Xi וYj−1), כך ששתי הבדיקות הקודמות מכסות כבר (פעמיים אפילו) מקרה זה.


מציאת אורך הLCS

[עריכה]

להלן פסוודו-קוד למציאת אורך הLCS.

1	L = Make-Matrix(m, n)

2	for i in [1, ..., m]
3		for j in [1, ..., n]
4			L[i][j] = Nil

LCS-Length(i, j)
1	if L[i][j] == Nil
2		if i == 0 or j == 0
3			return 0
4		else if X[i] == Y[j]
5			L[i][j] = 1 + LCS-Length(i - 1, j - 1)
6		else
7			guess-x = LCS-Length(i - 1, j)
8			guess-y = LCS-Length(i, j - 1)
		
9			L[i][j] = Max(guess-x, guess-y)

10	return L[i][j]

ארבע השורות הראשונות מאתחלות מטריצה המשמשת לmemoization, והפונקציה LCS-Length מוצאת את את האורך המבוקש. 1-4 מייצרות מטריצה (גלובלית) שכל אחד מאיבריה מאותחל לNil.‏ 2-9 של LCS-Length, פועלות בדיוק לפי נוסחת הנסיגה הקודמת (שאת נכונותה כבר הראינו). המטריצה L, בפרט ב1 של LCS-Length, מטפלות במצב בו הפונקציה נקראת עם פרמטרים שאתם היא כבר נקראה בעבר.

ניתוח סיבוכיות

[עריכה]

נגדיר את T′(i,j) כזמן שאורכת LCS-Length בהנחה שכל אחת מקריאותיה הרקורסיביות היא O(1). קל לראות שT′(i,j)=O(1).

זמן הריצה של LCS-Length(m, n) חסום מלמעלה על ידי ∑i=0m∑j=0n[T′(i,j)]=Θ(m⋅n). חוץ מכך ישנו האתחול 1-4 שאורך גם כן Θ(m⋅n). הזמן הכולל, לכן, הוא O(m⋅n).

הדפסת איברי הLCS

[עריכה]

ראשית נשנה מעט את הפסוודו-קוד הקודם.

1	L = Make-Matrix(m, n)
2	D = Make-Matrix(m, n)

3	for i in [1, ..., m]
4		for j in [1, ..., n]
5			L[i][j] = Nil


LCS-Length(i, j)
1	if L[i][j] == Nil
2		if i == 0 or j == 0
3			return 0
4		else if X[i] == Y[j]
5			L[i][j] = 1 + LCS-Length(i - 1, j - 1)
6			D[i][j] = 'b'
7		else
8			guess-x = LCS-Length(i - 1, j)
9			guess-y = LCS-Length(i, j - 1)
		
10			L[i][j] = Max(guess-x, guess-y)
	
11			if L[i][j] == guess-x
12				D[i][j] = 'x'
13			else
14				D[i][j] = 'y'	

15	return L[i][j]

המטריצה הגלובלית D מתארת בכניסה ה(i,j) את ההחלטה לגבי הLCS של Xi וYj:

  1. 'x' מסמלת את ההחלטה לוותר על התו האחרון בXi.
  2. 'y' מסמלת את ההחלטה לוותר על התו האחרון בYj.
  3. 'b' מסמלת את ההחלטה לוותר על התו האחרון בשניהם. זה בדיוק המצב בו Xi=Yj נמצא כחלק מהLCS.הפונקציה הבאה מדפיסה את הLCS כאשר קוראים לPrint-LCS(m, n):
Print-LCS(i, j)
1	if i > 1 and j > 1
2		if D[i][j] == 'x':
3			Print-LCS(i - 1, j)
4		else if D[i][j] == 'y':
5			Print-LCS(i, j - 1)
6		else
7			Print-LCS(i - 1, j - 1)

1	if D[i][j] == 'b':
2		Print(X[i])

קל לראות שהסיבוכיות עדיין Θ(m⋅n): השינויים בLCS-Length אינם משנים את סיבוכיותה; כל קריאה לPrint-LCS(i, j) מורידה (לפחות) את אחד הארגומנטים שלה ב1, ולכן היא יכולה להיקרא לכל היותר m+n פעמים. הסיבוכיות, לכן, היא Θ(m⋅n) + O(m+n) = Θ(m⋅n).