در علوم کامپیوتر، یک الگوریتم انتخاب، یک الگوریتم برای پیدا کردن *k*امین کوچک‌ترین عدد در یک لیست است (به چنین عددی *k*امین *مرتبه آماری* گفته می‌شود). این الگوریتم‌ها شامل پیدا کردن کمینه، بیشینه و میانه‌ی عناصر است. الگوریتم‌های انتخاب از O(n)، که در بدترین حالت خطی اند، وجود دارند. انتخاب یکی از زیرمسئله‌های مسائل پیچیده‌تر مانند مسئله نزدیک‌ترین همسایه و مسئله یافتن کوتاهترین مسیر است.

انتخاب با مرتب‌سازی
-------------------

انتخاب ممکن است با مرتب کردن لیست و سپس استخراج عنصر دلخواه، به مرتب‌سازی تبدیل شود. این روش زمانی کارآمد است که به تعداد زیادی انتخاب از یک لیست نیاز باشد، در موردی که تنها یک بار مقداردهی می‌شود، یک مرتب‌سازی پرهزینه، همراه با چندین عمل استخراج کم‌هزینه انجام می‌شود. در حالت کلی، این روش نیازمند زمان {\displaystyle O(nlogn)} است، که در آن *n* طول لیست است.

الگوریتم‌های کمینه/بیشینه خطی
-----------------------------

الگوریتم‌های خطی، از لحاظ زمانی، برای پیدا کردن کمینه‌ها یا بیشینه‌ها این گونه کار می‌کنند که روی لیست تکرار می‌کنند و رد کمینه یا بیشینه تا هر بار نگه می‌دارند.

الگوریتم کلی انتخاب غیر خطی
---------------------------

با کمک ایده‌های مورد استفاده در الگوریتم‌های کمینه/بیشینه، ما می‌توانیم یک الگوریتم کلی ساده، ولی ناکارامد برای پیدا کردن کوچک‌ترین *k*امین یا بزرگ‌ترین *k* عنصر در یک لیست بدهیم، که نیاز به زمان O(kn) دارد، که وقتی *k* کوچک باشد مؤثر است. برای انجام دادن آن، ما به سادگی کوچک‌ترین/بزرگ‌ترین مقدار را می‌یابیم و آن را به ابتدای بازه حرکت می‌دهیم تا به اندیس دلخواه برسیم. این کار را می‌توانیم به عنوان یک مرتب‌سازی انتخابی ناتمام ببینیم. در اینجا الگوریتم بر اساس کمینه را می‌بینیم:

```
 **function** select(list[1..n], k)
 **for** i **from** 1 **to** k
 minIndex = i
 minValue = list[i]
 **for** j **from** i+1 **to** n
 **if** list[j] <minValue
 minIndex = j
 minValue = list[j]
 swap list[i] and list[minIndex]
 **return** list[k]
```

دیگر مزیت‌های این روش عبارتند از:

* بعد از پیدا کردن کوچک‌ترین *j*امین عنصر، برای پیدا کردن کوچک‌ترین *k*امین عنصر، تنها زمان (O(*j* + (*k*-*j*)2 یا برای *k* ≤ *j* تنها زمان {\displaystyle O(k)} مورد نیاز است.
* در رویه‌ای که بر پایهٔ افراز است و به دسترسی تصادفی نیاز دارد، می‌توان از داده‌ساختار لیست پیوندی برای پیاده‌سازی استفاده کرد.

الگوریتم کلی انتخاب بر پایهٔ افراز
----------------------------------

یک الگوریتم فراگیر انتخاب که در عمل کارآمد است، اما در بدترین حالت کارایی ضعیفی دارد، توسط ابداع‌کنندهٔ مرتب‌سازی سریع، *سی. اِی. آر هوار*، ارائه شده‌است. این الگوریتم به نام **الگوریتم انتخاب هوار** یا انتخاب سریع شناخته می‌شود.

در مرتب‌سازی سریع، یک زیررَویه به نام افراز وجود دارد که می‌تواند در زمان خطی، یک لیست را به دو بخش `left` و `right` تقسیم‌بندی می‌کند که یک گروه کوچک‌تر از یک عنصر خاص و گروه دیگر بزرگ‌تر یا مساوی آن عنصر خاص هستند. اینجا سودوکدی را می‌بینیم که یک افراز را برای عنصر `list[pivotIndex]` اجرا می‌کند:

```
 **function** partition(list, left, right, pivotIndex)
 pivotValue := list[pivotIndex]
 swap list[pivotIndex] and list[right] *// Move pivot to end*
 storeIndex := left
 **for** i **from** left **to** right
 **if** list[i] <pivotValue
 swap list[storeIndex] and list[i]
 increment storeIndex
 swap list[right] and list[storeIndex] *// Move pivot to its final place*
 **return** storeIndex
```

در مرتب‌سازی سریع، ما به صورت بازگشتی هر دو شاخه را در بهترین حالت در زمان {\displaystyle O(nlogn)} مرتب می‌کنیم. اما، وقتی که انتخاب انجام می‌دهیم، از می‌دانیم که عنصر مورد نظر ما در کدام افراز قرار دارد، چون محور (انگلیسی: pivot) در محل نهایی مرتب‌شدهٔ خود قرار دارد و از همهٔ عناصر قبل از خود بزرگ‌تر و از همهٔ عناصر بعد از خود کوچک‌تر یا مساوی است. بنابراین یک فراخوانی بازگشتی می‌تواند مکان یک عنصر دلخواه را در افراز درست بیابد:

```
 **function** select(list, left, right, k)
 **if** left = right // If the list contains only one element
 **return** list[left]  // Return that element
 select pivotIndex between left and right
 pivotNewIndex := partition(list, left, right, pivotIndex)
 pivotDist := pivotNewIndex - left + 1
     // The pivot is in its final sorted position,
     // so pivotDist reflects its 1-based position if list were sorted
 **if** pivotDist = k
 **return** list[pivotNewIndex]
 **else if** k <pivotDist
 **return** select(list, left, pivotNewIndex - 1, k)
 **else**
 **return** select(list, pivotNewIndex + 1, right, k - pivotDist)
```

  

مانند مرتب‌سازی سریع، کارایی الگوریتم به محوری که انتخاب می‌شود بستگی دارد. اگر محورهای نامناسب به‌طور متوالی انتخاب شوند، این رویه به انتخاب بر پایهٔ کمینه، که قبلاً گفته شد، تنزل پیدا می‌کند و در نتیجه به زمان (O(*n*2 نیاز پیدا خواهد کرد.

الگوریتم کلی انتخاب به صورت خطی - الگوریتم میانهٔ میانه‌ها
----------------------------------------------------------

یک الگوریتم با بدترین زمان اجرای خطی برای حالت کلی انتخاب *k*امین بزرگ‌ترین عنصر توسط *بلوم*، *فلوید*، *پرت*، *ریوست* و *ترجان* در مقاله سال ۱۹۳۷ با نام «حدود زمانی برای انتخاب» منتشر شد. گاهی از این الگوریتم با نام **BFPRT**، که حروف اول نام خانوادگی نویسندگان آن است، یاد می‌شود. این الگوریتم بر اساس الگوریتم انتخاب سریع کار می‌کند و هم‌چنین به نام الگوریتم میانه میانه‌ها شناخته می‌شود.

هرچند انتخاب سریع به‌طور میانگین دارای زمان خطی است، زمانی که محورهای ضعیفی استفاده شوند می‌تواند به زمان از درجه دوم نیاز پیدا کند (حالتی را در نظر بگیرید که در هر گام، محور در نزدیکی کوچک‌ترین عنصر انتخاب شود). راه چاره برای اینکه آن را به O(n) در *بدترین* حالت تبدیل کنیم این است که به‌طور پیوسته در هر گام محور *مناسب* را بیابیم. یک محور خوب باید به گونه‌باشد که بتوانیم اطمینان داشته باشیم نسبت ثابتی از عناصر قبل از آن و بعد از آن قرار بگیرند.

الگوریتم *انتخاب* لیست را به گروه‌هایی شامل پنج عنصر تقسیم می‌کند. (فعلاً با عناصر باقی‌مانده کاری نداریم). سپس، برای هر گروه پنج‌تایی، میانه محاسبه می‌شود (اگر آن پنج مقدار داخل ثبّات‌ها بارگذاری شوند و مقایسه شوند، عملیات به‌طور بالقوه بسیار سریع انجام می‌شود). (اگر مرتب‌سازی به صورت درجا صورت گیرد، این میانه‌ها به یک بلوک پیوسته در لیست منتقل می‌شوند) *انتخاب* به صورت بازگشتی در این زیرلیست‌های *n*/5 عنصری فراخوانده می‌شود تا مقدار واقعی میانه یافت شود. سرانجام، *میانهٔ میانه‌ها* به عنوان محور انتخاب می‌شود.

ویژگی‌های محور
--------------

محور انتخاب شده، از نیمی از عناصر لیست میانه‌ها بزرگ‌تر و از نیمهٔ دیگر کوچک‌تر است، به‌طوری‌که در هر نیمه {\displaystyle n/10} عنصر {\displaystyle (1/2\*(n/5))} قرار دارند. هر کدام از این عناصر، میانهٔ ۵ عنصر است و از ۲ عنصر کوچک‌تر و از ۲ عنصر در خارج از بلوک بزرگ‌تر است. پس، محور کوچک‌تر از {\displaystyle 3(n/10)} عناصر خارج از بلوک است، و از {\displaystyle 3(n/10)} عنصر دیگر خارج از بلوک بزرگ‌تر است. بنا بر این، میانهٔ انتخاب شده، عناصر را به مکانی بین ۳۰٪/۷۰٪ و ۷۰٪/۳۰٪ تقسیم می‌کند. این کار به ما اطمینان می‌دهد که رفتار الگوریتم در بدترین حالت خطی است. برای به دست آوردن شهود کافی به جدول زیر دقت کنید:

یک تکرار روی لیست {۹۹... ,۰٬۱,۲٬۳}|  | 12 |  | 15 |  | 11 |  | 2 |  | 9 |  | 5 |  | 0 |  | 7 |  | 3 |  | 21 |  | 44 |  | 40 |  | 1 |  | 18 |  | 20 |  | 32 |  | 19 |  | 35 |  | 37 |  | ۳۹ |
|  | 13 |  | 16 |  | 14 |  | 8 |  | 10 |  | 26 |  | 6 |  | 33 |  | 4 |  | 27 |  | 49 |  | 46 |  | 52 |  | 25 |  | 51 |  | 34 |  | 43 |  | 56 |  | 72 |  | ۷۹ |
| میانه‌ها | 17 |  | 23 |  | 24 |  | 28 |  | 29 |  | 30 |  | 31 |  | 36 |  | 42 |  | 47 |  | 50 |  | 55 |  | 58 |  | 60 |  | 63 |  | 65 |  | 66 |  | 67 |  | 81 |  | ۸۳ |
|  | 22 |  | 45 |  | 38 |  | 53 |  | 61 |  | 41 |  | 62 |  | 82 |  | 54 |  | 48 |  | 59 |  | 57 |  | 71 |  | 78 |  | 64 |  | 80 |  | 70 |  | 76 |  | 85 |  | ۸۷ |
|  | 96 |  | 95 |  | 94 |  | 86 |  | 89 |  | 69 |  | 68 |  | 97 |  | 73 |  | 92 |  | 74 |  | 88 |  | 99 |  | 84 |  | 75 |  | 90 |  | 77 |  | 93 |  | 98 |  | ۹۱ |
|

(راهنمای رنگ‌های جدول: قرمز = «(یکی از دو حالت ممکن) میانهٔ میانه‌ها»، خاکستری = «شماره <قرمز»، سفید = «شماره> قرمز»)

در جدول بالا، برای وضوح بیشتر ۵-تایی‌ها بر اساس میانه مرتب شده‌اند. مرتبسازی چندتایی‌ها لزومی ندارد، زیرا ما تنها به میانه برای استفاده به عنوان عنصر محور نیاز داریم.

توجه داشته باشید که تمام عناصر بالا/چپ خانهٔ قرمز (۳۰٪ از ۱۰۰ عنصر) کوچک‌تر از قرمز، و تمام عناصر پایین/راست خانهٔ قرمز (۳۰٪ دیگر از ۱۰۰ عنصر) بزرگتر از قرمز هستند.

### اثبات زمان اجرای (O(n

محاسبهٔ میانه به‌طور بازگشتی، در بدترین حالت از درجه خطی بیشتر نخواهد شد، زیرا لیست میانه‌ها ۲۰٪ از اندازهٔ لیست است، در حالی که فراخوانی بازگشتی دیگر حداکثر روی ۷۰٪ لیست لیست اجرا می‌شود. بنا بر این زمان اجرا به صورت زیر می‌شود:

```
 T(*n*) ≤ T(*n*/5) + T(7*n*/10) + O(*n*)
```

زمان (O(*n* ناشی از عمل افراز کردن است (ما هر عنصر را به تعداد دفعات ثابتی ملاقات می‌کنیم، تا آن‌ها را به گروه‌های (O(n دسته‌بندی کنیم و هر میانه را در زمان (O(n به دست آوریم. به این ترتیب می‌توان نشان داد که

 T(*n*) ≤ c\*n\*(1 + (9/10) + (9/10)2 + ...) = O(*n*).

منابع
-----

* M. Blum, R.W. Floyd, V. Pratt, R. Rivest and R. Tarjan, "Time bounds for selection," *J. Comput. System Sci*. 7 (1973) 448-461.
* K. C. Kiwiel. On Floyd and Rivest’s SELECT Algorithm, *Theoretical Computer Sci.* 347 (2005) 214-238.
* دانلد کنوت. *هنر برنامه‌نویسی رایانه*, Volume 3: *Sorting and Searching*, Third Edition. Addison-Wesley, 1997. ISBN 0-201-89685-0. Section 5.3.3: Minimum-Comparison Selection, pp.207–219.
* توماس اچ کورمن، Charles E. Leiserson, رونالد ریوست، and کلیفورد استین. *مقدمه‌ای بر الگوریتم‌ها*, Second Edition. MIT Press and McGraw-Hill, 2001. ISBN 0-262-03293-7. Chapter 9: Medians and Order Statistics, pp.183–196. Section 14.1: Dynamic order statistics, pp.302–308.
* مشارکت‌کنندگان ویکی‌پدیا. «Selection Algorithm». در *دانشنامهٔ ویکی‌پدیای انگلیسی*، بازبینی‌شده در ۲۵ نوامبر ۲۰۱۱.

پیوند به بیرون
--------------