برزدن فیشر یتس
بر زدن فیشر یتس
الگوریتمی برای ایجاد جایگشت تصادفی از یک دنباله است.
فرض کنید به ما آرایهای داده شدهاست و میخواهیم جایگشتی تصادفی از اعضای آن آرایه به دست آوریم. این کار مانند بر زدن دستهای ورق است و بر زدن آرایه به این معناست که هر کدام از جایگشتهای ممکن، با احتمال مساوی بیایند و جایگشتی نااریب ایجاد شود.
نسخهٔ اولیه پیچیدگی زمانی
دارد اما نسخهٔ جدید این الگوریتم بهینه است و زمان اجرای آن ضریبی از تعداد عناصر و خطی میباشد.[1]
همچنین درجا اجرا میشود و نیاز به حافظهٔ اضافی ندارد.
روش اصلی بر زدن فیشر یتس
نسخهٔ اولیه بر زدن فیشر-یتس در سال ۱۹۳۸ توسط رونالد فیشر و فرنک یتس در کتاب جداول آماری برای تحقیقات زیستی، زراعتی، پزشکی[2] منتشر شد.
این الگوریتم بسیار ساده است و برای استفادهٔ مستقیم انسانها مناسب میباشد زیرا تنها به کاغذ و خودکار نیازمند است! اما به دلیل پیچیدگی زمانی بالا توسط کامپیوترها که با دنبالههای بزرگ سر و کار دارند به کار گرفته نمیشود.
در این نسخه خاصیت تصادفی توسط جدولی از اعداد تصادفی ایجاد میشود. روش ایجاد جایگشت تصادفی اعداد ۱ تا
n
به شرح زیر است:
۱-
اعداد یک تا
n
را بنویسید.
۲-
عدد تصادفی
k
را بین یک و تعداد اعداد خط نخورده انتخاب کنید.
(k
میتواند یک و تعداد اعداد باقیمانده نیز باشد)
۳-
k
امین عدد خط نخورده را پیدا کنید و انتهای لیستی دیگر بنویسید. سپس آن را خط بزنید.
۴-
مراحل ۲ و ۳ را تکرار کنید تا تمامی اعداد خط بخورند.
۵-
دنبالهٔ اعداد ایجاد شده در لیست، جایگشتی از دنبالهٔ اولیه است.
با فرض اینکه عدد تصادفی انتخاب شده در مرحلهٔ ۲ واقعاً تصادفی و نااریب باشد جایگشت ایجاد شده نیز دارای این خواص است. همچنین فیشر و یتس دربارهٔ چگونگی تولید عدد تصادفی در هر بازهای توسط جداول از پیش مهیا شده توضیح دادند. این روش نااریب است.
مثال
فرض کنید میخواهیم جایگشتی از اعداد ۱ تا ۵ را به دست آوریم. آنها را روی تکه کاغذی مینویسیم:
دامنه | عدد تصادفی | کاغذ | لیست نهایی |
---|---|---|---|
۵ ۴ ۳ ۲ ۱ |
فرض کنید عدد تصادفی به دست آمده در مرحلهٔ دو ۳ باشد. با خط زدن ۳ و افزودن به لیست نهایی به نتیجهٔ زیر میرسیم:
دامنه | عدد تصادفی | کاغذ | لیست نهایی |
---|---|---|---|
[۵ ،۱] | ۳ |
۵ ۴ ۳ ۲ ۱ |
۳ |
به همین ترتیب ادامه میدهیم تا تمامی اعداد خط بخورند.
دامنه | عدد تصادفی | کاغذ | لیست نهایی |
---|---|---|---|
[۴ ،۱] | ۱ |
۵ ۴ ۳ ۲ ۱ |
۱ ۳ |
دامنه | عدد تصادفی | کاغذ | لیست نهایی |
---|---|---|---|
[۳ ،۱] | ۳ |
۵ ۴ ۳ ۲ ۱ |
۵ ۱ ۳ |
دامنه | عدد تصادفی | کاغذ | لیست نهایی |
---|---|---|---|
[۲ ،۱] | ۱ |
۵ ۴ ۳ ۲ ۱ |
۲ ۵ ۱ ۳ |
دامنه | عدد تصادفی | کاغذ | لیست نهایی |
---|---|---|---|
[۱ ،۱] | ۱ |
۵ ۴ ۳ ۲ ۱ |
۴ ۲ ۵ ۱ ۳ |
نسخهٔ جدید الگوریتم فیشر یتس
نسخهٔ جدید بر زدن فیشر یتس برای استفادهٔ کامپیوترها طراحی شده. این نسخه در سال ۱۹۶۴ توسط ریچارد داستنفلد معرفی شد[3]
و توسط دونالد کنوت در کتاب هنر برنامهنویسی رایانه با عنوان «الگوریتم پی»[4]
به شهرت رسید. به نظر میرسد آنها از الگوریتم فیشر و یتس مطلع نبودهاند.
ایدهٔ نسخهٔ جدید مشابه نسخهٔ اصلی است و بر مبنای انتخاب تصادفی میباشد. اما تفاوت جزئی آن موجب کاهش چشمگیر زمان اجرا میشود. این الگوریتم با وجود سادگی نااریب است و از حافظهٔ اضافه استفاده نمیکند.
الگوریتم فشر یتس زمان غیرضروریای را صرف پیدا کردن k امین عدد خط نخورده میکند (خط ۳). داستنفلد برای آن راه حلی ارائه کرد. در هر بار اجرای حلقهٔ دستورات عدد خط خورده را با جابهجا کردن آن با آخرین عدد خط نخورده به انتهای آرایه منتقل کنیم. این راه حل پیچیدگی زمانی بر زدن را از
به
کاهش داد.
۱-
دنباله ایی به طول
n
از اعداد مورد نظر را در نظر بگیرید.
۲-
عددی تصادفی مانند
در بازهٔ
انتخاب کنید.
k
تعداد اعدادی است که روی آنها عملیات انجام شده؛ به عبارت دیگر تعداد حلقههای اجرا شده میباشد.
۳-
عنصر
ام را با عنصر
ام جابهجا کنید.
۴-
مراحل ۲ و ۳ را
دفعه تکرار کنید.
۵-
دنبالهٔ شما به جایگشتی تصادفی از دنبالهٔ اولیه تبدیل شدهاست.
مثال
فرض کنید دنبالهٔ مورد نظر ۵عضوی باشد؛ مثلاً
دامنه | عدد تصادفی | دنباله پیش از تغییر | دنباله پس از تغییر |
---|---|---|---|
[۵ ،۱] | ۲ |
دامنه | عدد تصادفی | دنباله پیش از تغییر | دنباله پس از تغییر |
---|---|---|---|
[۴ ،۱] | ۳ |
دامنه | عدد تصادفی | دنباله پیش از تغییر | دنباله پس از تغییر |
---|---|---|---|
[۳ ،۱] | ۳ |
دامنه | عدد تصادفی | دنباله پیش از تغییر | دنباله پس از تغییر |
---|---|---|---|
[۲ ،۱] | ۱ |
نمونه کد
کد زیر به زبان پایتون نوشته شده:[5]
# Python Program to shuffle a given array import random
# A function to generate a random permutation of arr[] def randomize (arr, n): # Start from the last element and swap one by one. We don't # need to run for the first element that's why i > 0 for i in range(n-1,0,-1): # Pick a random index from 0 to i j = random.randint(0,i+1)
# Swap arr[i] with the element at random index arr[i],arr[j] = arr[j],arr[i] return arr
# Driver program to test above function. arr = [1, 2, 3, 4, 5, 6, 7, 8] n = len(arr) print(randomize(arr, n))
# This code is contributed by Pratik Chhajer
خروجی کد:
7 8 4 6 3 1 2 5
برخی کاربردها
مولدهای اعداد تصادفی سختافزاری
معمولاً بین بیتهای اعداد تولیدشده توسط مولدهای اعداد تصادفی که بهطور سختافزاری پیادهسازی شدهاند، همبستگی وجود دارد.[6]
مهاجمها با یافتن این بیتها میتوانند به ما آسیب بزنند. برزدن بیتهای عدد تولید شده میتواند این مشکل را بر طرف کند زیرا هربار بیتهای همبسته به مکانهای متفاوتی میروند و مهاجم نمیتواند آنها را شناسایی کند. البته بر زدن یکی از مشکلات عمدهٔ مولدهای اعداد تصادفی سختافزاری که اریب بودن آنهاست را حل نمیکند. این مولدها بیت صفر را به احتمال ۰٫۵ تولید نمیکنند و با برزدن تعداد صفر و یکها تغییر نمیکند و مولد همچنان اریب باقی میماند.
مرتبسازی ساختگی
کلیت الگوریتم به هم ریختن تصادفی آرایه و تولید جایگشتهای تصادفی تا رسیدن به آرایهٔ مرتبشدهاست. این الگوریتم بهینه نیست و در بدترین حالت میتواند به اتمام نرسد و زمان بینهایت داشتهباشد.
تولید عدد تصادفی در بازهٔ دلخواه
علاوه بر توجه به الگوریتم فیشر یتس باید به الگوریتم به کارگرفتهشده برای تولید عدد تصادفی در مرحلهٔ دو نیز توجه کنیم. اریب بودن الگوریتم مذکور موجب اریب شدن جایگشت نهایی میشود.
معمولاً مولدهای اعداد تصادفی، اعدادی در بازهٔ
[
,
۰]
تولید میکنند. یکی از روشهای معمول برای پیدا کردن عدد تصادفی در بازهٔ
[
,
۱]
استفاده از روش باقیمانده میباشد. (
میتواند تعداد اعداد خط نخورده در روش اصلی فیشر یتس یا تعداد حلقههای باقیمانده در نسخه جدید باشد)
این روش در حالت کلی مناسب نمیباشد و نتیجهٔ نهایی ما را اریب میکند.
فرض کنید مولد، عدد تصادفی صحیح
r
را در بازهٔ
[
,
۰]
تولید کند. روش باقیمانده
را به عنوان عدد تصادفی در بازهٔ
[
,
۱]
خروجی میدهد. (
باقیماندهٔ تقسیم بر میباشد)
بهطور مثال فرض کنید مولد، عددی در بازهٔ
[۹۹
,
۰]
تولید کند و ما عددی در بازهٔ
[۱۵
,
۱]
بخواهیم. اگر اعداد صحیح بازهٔ
[۹۹
,
۰]
را بر ۱۵ تقسیم و باقیمانده را با یک جمع کنیم، اعداد یک تا ده، ۷ مرتبه و سایر اعداد ۶ مرتبه ظاهر میشوند؛ زیرا ۱۰۰ بر ۱۵ بخشپذیر نیست. پس توزیع اعداد تصادفی تولید شده در بازهٔ
[۱۵
,
۱]
یکنواخت نیست و اریب میباشد.
لینکهای مرتبط
منابع
- Black, Paul E. (2005-12-19). "Fisher–Yates shuffle". Dictionary of Algorithms and Data Structures. National Institute of Standards and Technology. Retrieved 2007-08-09.
- Fisher, Ronald A.; Yates, Frank (1948) [1938]. Statistical tables for biological, agricultural and medical research (3rd ed.). London: Oliver & Boyd. pp. 26–27. OCLC 14222135.
- Durstenfeld, R. (July 1964). "Algorithm 235: Random permutation". Communications of the ACM. 7 (7): 420. doi:10.1145/364520.364540.
- Knuth, Donald E. (1969). Seminumerical algorithms. The Art of Computer Programming. 2. Reading, MA: Addison–Wesley. pp. 139–140. OCLC 85975465.
- "Shuffle a given array using Fisher–Yates shuffle Algorithm".
- Markus Dichtl (2007), Eli Biham and Helena Handschuh and Stefan Lucks and Vincent Rijmen, ed., Cryptographic Shuffling of Random and Pseudorandom Sequences, Dagstuhl Seminar Proceedings (07021), Internationales Begegnungs- und Forschungszentrum f{\"u}r Informatik (IBFI), Schloss Dagstuhl, Germany, ISSN 1862-4405
- Chakrabarty D (2018), Random Numbers Tables Due to Tippet, Fisher & Yates, Kendall & Smith and Rand Corporation: Comparison of Degree of Randomness by Run Test. J Biostat Biometric App 3 (1): 103 (PDF), 3 (1), Journal of Biostatistics and Biometric Applications, ISSN 2348-9820