بستن پنجره
فرادرس - مجموعه آموزش‌های ویدئویی  مهندسی کامپیوتر - طراحی الگوریتم - ساختمان داده
بستن پنجره
حمایت از وب‌گاه:
حمایت از این صفحه:
دنباله‌ی اعداد فیبوناچی - الگوریتمستان
الگوریتمستان
1214.675.00
  »  

       

بررسی الگوریتم‌های محاسبه‌ی دنباله‌ی اعداد فیبوناچی و کارایی آنها

بسیاری از فرآیندهای طبیعی از جمله ترکیب ساختار بدن موجودات زنده نظم مشخصی دارند و از دنباله‌ی اعدادی تبعیت می‌کنند که امروزه با نام دنباله‌ی اعداد فیبوناچی (فیبوناتچی - Fibonacci) شناخته می‌شود. مشهورترین خاصیت این اعداد نسبت دو جمله‌ی متوالی آنها به ازای جملات بزرگ دنباله است که به عدد طلایی مشهور است.

    این دنباله از جمله دنباله‌های عددی است که در طراحی سوالات مسابقات برنامه‌نویسی نیز استفاده می‌شود و گاهی در حل سوالات کاربرد دارد. از این رو آشنایی با روش‌های مختلف تولید جملات آن حائز اهمیت است.

    تعریف: دنباله‌ی اعداد فیبوناچی روی اعداد حسابی به صورت زیر تعریف می‌شود:

\[ F_n= \left \{\begin{matrix} F_{n-1} + F_{n-2} & & & \; n > 1\\ 1 & & & \; n = 1 \\ 0 & & & n = 0 \end{matrix}\right. \]

    همانگونه که از تعریف مشخص است، جملات این دنباله از جمع دو جمله‌ی قبلی آن با شروع از دو مقدار صفر و یک به دست می‌آید:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...

  

محاسبه‌ی بازگشتی بر اساس تعریف

  [بازگشت به فهرست]

ساده‌ترین راهکار برای محاسبه‌ی اعداد دنباله‌ی فیبوناچی استفاده از تابع بازگشتی زیر است:

      

long long fibo_1(int n){
    if(n < 2)
        return n;
    return fibo_1(n - 1) + fibo_1(n - 2);
}

  

    این تابع فراخوانی تابع با مقدار $n $ را به فراخوانی بازگشتی با دو مقدار بسیار نزدیک به $n$ تبدیل می‌کند. بنابراین می‌توان پیش‌بینی کرد که زمان تولید خروجی نسبت به اندازه‌ی ورودی از مرتبه‌ی نمایی باشد. برای مثال فراخوانی‌های بازگشتی تابع برای محاسبه‌ی $F_7$ در شکل زیر آمده است:

      

فراخوانی بازگشتی تولید اعداد فیبوناجی

      

    به این ترتیب برای محاسبه‌ی $F_7$ تابع مذکور $41$ بار فراخوانی می‌شود که در مقایسه با مقدار $n $ مقرون به صرفه نیست. دلیل این فراخوانی‌های زیاد تکرار در محاسبه‌ی جملات میانی است. به عنوان نمونه بر اساس شکل فوق مقدار $F_2$ هشت بار به صورت تکراری محاسبه شده است.

      

محاسبه با استفاده از روش برنامه‌نویسی پویا

  [بازگشت به فهرست]

برای رفع مشکل فراخوانی‌های تکراری در محاسبات می‌توان از روش برنامه‌نویسی پویا و حرکت از جزء به کل استفاده کرد:

      

long long fibo_2(int n){
    if(n < 2)
        return n;
    int f1 = 0, f2 = 1, f3;
    for(int i = 2 ; i <= n ; i++){
        f3 = f1 + f2;
        f1 = f2;
        f2 = f3;
    }
10     return f3;
11 }

  

    مرتبه‌ی اجرایی محاسبه‌ی جمله‌ی $n$-ام دنباله‌ی فیبوناچی با این راهکار $ \Theta(n) $ است که در مقایسه با روش قبل (مرتبه‌ی نمایی) عملکرد بسیار بهتری دارد. همچنین با توجه به کنار گذاشتن فراخوانی‌های بازگشتی حافظه‌ی کمتری مصرف می‌شود.

    نکته: در صورتی که نیاز به در اختیار داشتن تمام جملات دنباله در یک بازه‌ی مشخص باشد، این روش بهترین راهکار ممکن است و کافی‌ست مقدار f3 در هر تکرار کد فوق در یک مخزن جدا مانند آرایه ذخیره شود.

      

محاسبه با استفاده از روش تقسیم و غلبه

  [بازگشت به فهرست]

تعریف رابطه‌ی فیبوناچی خود به وضوح یک راهکار تقسیم و غلبه برای محاسبه‌ی جملات آن است. اما همانگونه که بحث شد، استفاده از آن رابطه و فراخوانی‌های بازگشتی مقرون به صرفه نیست.

    یک تعریف بازگشتی دیگر دنباله‌ی فیبوناچی به صورت زیر است:

\[ \begin{matrix} F_{2n-1} = F^2_n + F^2_{n-1} \\ F_{2n} = (2 F_{n-1} + F_n) F_n \end{matrix} \]

    این رابطه محاسبه‌ی مقدار جمله‌ی $n$-ام دنباله را به محاسبه‌ی دو جمله در حدود $ \frac{n}{2}$ تقسیم می‌کند. چنین رابطه‌ای مرتبه‌ی $ O(log\;n) $ را تداعی می‌کند. اما پیاده‌سازی بازگشتی این رابطه نیز فراخوانی‌های تکراری دارد. به شکل زیر توجه کنید:

      

دنباله فیبوناچی

      

    این راهکار تعداد فراخوانی‌ها برای محاسبه‌ی $F_7$ را از 41 فراخوانی حالت بازگشتی عادی به 11 فراخوانی کاهش می‌دهد. اما همچنان برخی فراخوانی‌های تکراری وجود دارد که مرتبه‌ی آن را بزرگتر از $ \Theta(n)$ (مرتبه‌ی محاسبه به روش برنامه‌نویسی پویا) می‌کند. این فراخوانی‌های تکراری زمانی که عدد جمله بزرگتر می‌شود، تاثیر چشم‌گیری در زمان محاسبه دارند.

    برای رفع این مشکل و بالا بردن کارایی الگوریتم می‌توان جملات تولید شده را در حافظه نگه داشت تا از محاسبه‌ی مجدد آن جلوگیری کرد. در چنین حالتی اگرچه فضای مصرفی الگوریتم بالا می‌رود، اما زمان اجرای آن بهبود قابل توجهی پیدا می‌کند. به عنوان مثال برای محاسبه‌ی جمله‌ی $ F_{13941207} $ بیش از بیست میلیون فراخوانی تابع بدون ذخیره کردن مقادیر جملات کوچکتر صورت می‌گیرد (بسیار بیشتر از عدد $13941207$) که با ذخیره کردن این مقادیر به کمتر از $130$ فراخوانی کاهش می‌یابد!

    نکته: ممکن است این سوال پیش بیاید که در روش فراخوانی بازگشتی با تعریف اصلی دنباله‌ی فیبوناچی نیز امکان ذخیره کردن جملات دنباله برای پیشگیری از محاسبه‌ی مجدد آن وجود دارد. ویژگی مهم روش اخیر این است که تنها بخشی از اعداد دنباله تولید و ذخیره می‌شوند. در حالی که برای محاسبه با تعریف یا روش برنامه‌نویسی پویا باید تمامی جملات قبلی دنباله تولید و ذخیره شوند. به عنوان مثال، برای محاسبه‌ی $F_{13941207}$ با راهکار اخیر تنها نیاز به ذخیره کردن $64 $ جمله است که در مقایسه با تمامی جملات بسیار کمتر است.

      

محاسبه با استفاده از روش ماتریسی

  [بازگشت به فهرست]

دنباله‌ی فیبوناچی را می‌توان به فرم ماتریسی زیر نشان داد:

\[ \begin{pmatrix} F_n \\ F_{n-1} \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} F_{n-1} \\ F_{n-2} \end{pmatrix} \]

    با بسط دادن سمت راست رابطه، برابری زیر حاصل می‌شود:

\[ \begin{pmatrix} F_n \\ F_{n-1} \end{pmatrix} = M^{n-1} \begin{pmatrix} F_1 \\ F_0 \end{pmatrix} = M^{n-1} \begin{pmatrix} 1 \\ 0 \end{pmatrix}\; \; , \; \; M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \]

    به عبارت دیگر، درایه‌ی سطر اول حاصل‌ضرب توان $(n-1)$-ام ماتریس $ M $ در ماتریس ستونی یک و صفر همان $ F_n $ است. بنابراین کافی است عنصر  $ {(M^{n-1})}_{11} $ (عنصر سطر اول و ستون اول) محاسبه شود (چرا؟).

    این رابطه محاسبه‌ی جملات دنباله‌ی فیبوناچی را به محاسبه‌ی توان یک ماتریس بدل می‌کند. توان یک عدد یا یک ماتریس مربعی را می‌توان با استفاده از رابطه‌ی زیر حساب کرد که همواره از مرتبه‌ی $ O(log \; n)$ است (چرا؟):

\[ A^n= \left \{ \begin{matrix} {(A^{\frac{n}{2}})}^2 & & & \; n \% 2 = 0 \\ {(A^{\frac{n - 1}{2}})}^2 \times A & & & \; n \% 2 = 1 \end{matrix} \right. \]

    مزیت بزرگ این روش نسبت به روش قبلی عدم نیاز به ذخیره‌سازی مقادیر جملات کوچکتر است و در مقابل هزینه‌ی سنگین‌تر محاسبه‌ی ضرب اعداد را دارد که با توجه به کم بودن آنها (مرتبه‌ی لگاریتمی) قابل چشم‌پوشی است. همینطور تعداد فراخوانی‌های بازگشتی کمتری نسبت به روش قبلی دارد. به عنوان مثال، با این روش تنها $24$ فراخوانی بازگشتی برای محاسبه‌ی توان $13941206$-ام ماتریس $ M $ و در نتیجه محاسبه‌ی $F_{13941207} $ نیاز است.


این نوشته آخرین بار در تاریخ یکشنبه، ۳ مرداد ماه ۱۳۹۵ مورد بازنویسی نگارشی قرار گرفته است.
پیوند کوتاه صفحه دسته‌بندی
امتیاز نوشته
  • 1
  • 2
  • 3
  • 4
  • 5
ارسال پیام

نام: *  

پست الکترونیک:

وبگاه:

متن پیام: *

right 01 02 03 04 05 06 07 08 09 10 11 12 13 14 left

 


» شیما

سه‌شنبه، ۱ تیر ماه ۱۳۹۵، ساعت ۱۷:۴۸
بله منظورم همین بود  تشکر می کنم از شما12

» شیما

سه‌شنبه، ۱ تیر ماه ۱۳۹۵، ساعت ۱۲:۲۴
سلام آقا مسعود... میدونم ربطی به این بحث نداره ولی میشه یه توضیحی در مورد اعداد فوق اول بدید.. ومعادل ریاضیش رو در انگلیسی بفرمائید چی هست prime  عدد اول ...فوق اول چی میشه


سه‌شنبه، ۱ تیر ماه ۱۳۹۵، ساعت ۱۷:۰۷
مسعود:
فکر کنم منظورتون اعداد superprime یا higher-order prime هستن.
اگه دنباله‌ی اعداد اول رو بنویسید و عناصری از اونها رو انتخاب کنید که شماره‌ی محلشون عدد اول باشه، بهشون superprime گفته می‌شه. مثلا اولین عدد superprime عدد ۳ هست که در موقعیت ۲ (اولین عدد اول) از دنباله‌ی کل اعداد اول قرار داره.