انتخاب با میانهی میانهها
k-امین عنصر کوچک را در بدترین حالت هم در زمان خطی پیدا میکند: محور، میانهی میانههای گروههای پنجتایی است.
Quickselect بهطور میانگین خطی است، ولی اگر محورها بد باشند به O(n²) میرسد. میانهی میانهها (بلوم، فلوید، پرت، ریوست و تارجان، ۱۹۷۳) محوری انتخاب میکند که همیشه خوب است:
- زمان:
O(n) - حافظه:
O(n) - CLRS، بخش ۹.۳
- هوروویتز–ساهنی، فصل ۳
هفده عدد؛ چهارمین عدد کوچک را میخواهیم. محور اول ۱۲ است، جواب در بخش کوچکتر است و محور دوم، ۵، خود جواب است.
def select(a, k):# The k-th smallest item (k = 1 is the minimum), in worst-case linear time.if len(a) <= 5:a = sorted(a)return a[k - 1]groups = [sorted(a[i:i + 5]) for i in range(0, len(a), 5)]medians = [g[(len(g) - 1) // 2] for g in groups]pivot = select(medians, (len(medians) + 1) // 2)low = [x for x in a if x < pivot]high = [x for x in a if x > pivot]same = len(a) - len(low) - len(high)if k <= len(low):return select(low, k)if k <= len(low) + same:return pivotreturn select(high, k - len(low) - same)
شروع اجرا
درخت فراخوانیهای بازگشتی
نارنجی پر = فراخوانی در حال اجرا · حاشیهی نارنجی = هنوز روی پشته · خاکستری = تمام شده
متغیرهای محلی
نارنجی = در همین گام عوض شد
a list
Call stack
- select()خط 1
توضیح
ایده
Quickselect بهطور میانگین خطی است، ولی اگر محورها بد باشند به O(n²) میرسد. میانهی میانهها (بلوم، فلوید، پرت، ریوست و تارجان، ۱۹۷۳) محوری انتخاب میکند که همیشه خوب است:
- آرایه را به گروههای ۵تایی تقسیم کن و میانهی هر گروه را پیدا کن.
- با فراخوانی بازگشتی، میانهی همین میانهها را پیدا کن: این محور است.
- آرایه را به کوچکتر، برابر و بزرگتر از محور تقسیم کن و فقط در بخشی که جواب در آن است ادامه بده.
چرا خطی؟
دستکم نیمی از گروهها میانهای ≥ محور دارند و در هر کدام دستکم ۳ عنصر ≥ محورند؛ پس حدود 3n/10 عنصر بزرگتر و 3n/10 کوچکتر از محورند و هر بخش حداکثر 7n/10 است:
T(n) ≤ T(n/5) + T(7n/10) + O(n) ⇒ T(n) = O(n)
چون 1/5 + 7/10 < 1.
در trace به چه چیزی نگاه کنی
- میلهها آرایهی فراخوانی فعلیاند؛ ببین هر بار چقدر کوچکتر میشود.
- درخت فراخوانیها دو نوع فراخوانی دارد: یکی برای میانهها و یکی برای بخشی که جواب در آن است.