درخت جستجوی دودویی بهینه
با برنامهریزی پویا درخت جستجویی میسازد که با دانستن بسامد جستوجوی هر کلید، کمترین هزینهی مورد انتظار را دارد؛ برای هر بازهی کلیدها، بهترین ریشه را امتحان میکند. O(n³).
کلیدهای k1 < … < kn را داریم و میدانیم هر کلید ki با بسامد pi جستوجو میشود، و جستوجوهای ناموفقی که بین ki و ki+1 میافتند بسامد qi دارند. هزینهی هر جستوجو تعداد گرههایی است که میبینیم. درخت جستجویی میخواهیم که هزینهی مورد انتظار کمینه باشد. درخت متوازن لزوماً بهینه نیست: کلید پرطرفدار باید نزدیک ریشه باشد.
- زمان:
Θ(n³) (O(n²) with Knuth's speed-up) - حافظه:
Θ(n²) - CLRS، بخش ۱۴.۵
- هوروویتز–ساهنی، فصل ۵
پنج کلید با p = ۱۵، ۱۰، ۵، ۱۰، ۲۰ و q = ۵، ۱۰، ۵، ۵، ۵، ۱۰ (صدم)؛ هزینهی بهینه ۲۷۵، یعنی ۲٫۷۵، با k2 در ریشه.
class Node:def __init__(self, key):self.key = keyself.left = Noneself.right = Nonedef optimal_bst(P, Q):# p[1..n]: how often each key k1..kn is searched; q[0..n]: how often a search falls between keys (in hundredths).p = [0] + P[0]q = Q[0]n = len(P[0])if len(q) != n + 1:return None# e[i][j]: the cheapest expected cost of a tree on keys ki..kj; rt[i][j]: its root. Row 0 is unused, as in CLRS.e = [["·"] * (n + 1) for _ in range(n + 2)]w = [[0] * (n + 1) for _ in range(n + 2)]rt = [["·"] * (n + 1) for _ in range(n + 2)]for i in range(1, n + 2):e[i][i - 1] = q[i - 1]w[i][i - 1] = q[i - 1]for length in range(1, n + 1):for i in range(1, n - length + 2):j = i + length - 1w[i][j] = w[i][j - 1] + p[j] + q[j]best = Nonefor r in range(i, j + 1):t = e[i][r - 1] + e[r + 1][j] + w[i][j]if best is None or t < best:best = te[i][j] = trt[i][j] = rcost = e[1][n]# Rebuild the tree from rt, with a stack of (node, i, j) ranges.tree = Node("k" + str(rt[1][n]))stack = [[tree, 1, n, rt[1][n]]]while stack:node, i, j, r = stack.pop()if i <= r - 1:node.left = Node("k" + str(rt[i][r - 1]))stack.append([node.left, i, r - 1, rt[i][r - 1]])if r + 1 <= j:node.right = Node("k" + str(rt[r + 1][j]))stack.append([node.right, r + 1, j, rt[r + 1][j]])return cost
شروع اجرا
∅
e
جدول هنوز خالی است.
rt
جدول هنوز خالی است.
متغیرهای محلی
نارنجی = در همین گام عوض شد
P list
Q list
Call stack
- optimal_bst()خط 8
توضیح
مسئله
کلیدهای k1 < … < kn را داریم و میدانیم هر کلید ki با بسامد pi جستوجو میشود، و جستوجوهای ناموفقی که بین ki و ki+1 میافتند بسامد qi دارند. هزینهی هر جستوجو تعداد گرههایی است که میبینیم. درخت جستجویی میخواهیم که هزینهی مورد انتظار کمینه باشد. درخت متوازن لزوماً بهینه نیست: کلید پرطرفدار باید نزدیک ریشه باشد.
زیرساختار بهینه
اگر kr ریشهی درخت بهینه برای ki..kj باشد، زیردرخت چپ و راست هم برای ki..kr−1 و kr+1..kj بهینهاند. وقتی زیردرختی زیر ریشه میرود، هر کلیدش یک سطح پایینتر میرود، یعنی w(i, j) (جمع بسامدها) به هزینه اضافه میشود:
e[i][j] = min over r of e[i][r−1] + e[r+1][j] + w[i][j]
هزینه
Θ(n²) بازه و برای هر کدام تا n ریشه: Θ(n³). Knuth نشان داد ریشهی بهینه بین rt[i][j−1] و rt[i+1][j] است و این زمان را به O(n²) میرساند.
ورودی
P بسامد کلیدها و Q بسامد جستوجوهای ناموفق (n + 1 عدد) است، به صدم. جدول e هزینهها و rt ریشهها را با اندیسهای CLRS نشان میدهد (سطر ۰ استفاده نمیشود).