زمان اجرای الگوریتم

در نظریه پیچیدگی محاسباتی و علوم کامپیوتر، **پیچیدگی زمانی** یا **زمان اجرایِ** یک الگوریتم مقدار زمانی را توصیف می‌کند تا الگوریتم اجرا و متوقف شود. به عبارتی دیگر پیچیدگی محاسباتی منابع زمانی الگوریتم است. پیچیدگی زمانی معمولاً با شمارش تعداد عملیات‌های پایه‌ای که الگوریتم انجام می‌دهد توصیف می‌شود.

زمان اجرای الگوریتم را معمولاً با نماد T به عنوان تابعی از مقدار ورودی نمایش می‌دهیم. به عبارتی دیگر T_A (n) یعنی مقدار زمانی که طول می‌کشد تا الگوریتم A اجرا شود اگر به آن n ورودی دلخواه بدهیم. به عدد طبیعی n **اندازهٔ ورودی** می‌گوییم.

در بسیاری از موارد ورودی‌های متفاوت با اندازهٔ یکسان زمان اجرای متفاوتی دارند. در این موارد بدترین حالت یا میانگین این حالات را معیارمان قرار می‌دهیم (بیشینه یا متوسط میان تمام ورودی‌های ممکن به طول n). دلیل این کار این است که در تحلیل یک الگوریتم، رفتار کلی یک الگوریتم برای ما مهم است (و نه یک حالت خاص) در حقیقت تنها مسأله‌ای که برایمان مهم است نرخ رشد زمان اجرا (نسبت به n) است.

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

## تعریف
به شکل دقیق، مقدار n (اندازهٔ ورودی) برابر با تعداد هایی (مثل صفر و یک) است که به الگوریتم A ورودی داده می‌شود. همان طور که گفته شد، این که این بیت‌ها صفر باشند یا یک اهمیتی ندارد و معمولاً بدترین حالت را در نظر می‌گیریم.

فرض کنید M یک مدل محاسباتی مثل یک ماشین تورینگ یا ماشین با دسترسی تصادفی  باشد که همیشه پایان ‌‌پذیر است. پیچیدگی زمانی M تابعی به صورت T_M \colon \N \to \N است که در آن T_M (n) تعداد مراحلی است (تعداد اعمال پایه مثل جمع و ضرب و ...) که M طی می‌کند (در صورت یک ورودی با طول n).

معمولاً مدل محاسباتی ذکر نمی‌شود و به طور پیشفرض RAM فرض می‌شود.

هر عمل پایه (مثل جمع و ضرب و ...) در یک مدل محاسباتی مقدار یکسانی زمان می‌برد و این مقدار (هزینهٔ هر عمل) را **واحد زمانی** می‌نامیم. پس زمان اجرای الگوریتم برابر با تعداد اعمال پایه‌ای است که این الگوریتم انجام می‌دهد. در نتیجه زمان اجرای هر خط کد (که تابع خاصی را صدا نمی‌زند) \mathcal O (1) است. از این موضوع برای تحلیل راحت‌تر الگوریتم‌ها استفاده می‌شود.

در پردازنده‌‌های واقعی هر عمل پایه ممکن است به اندازهٔ متفاوتی طول بکشند (مثلاً عمل ضرب کمی کندتر از عمل جمع است) اما در این مدل‌ها از این تفاوت‌های ناچیز صرف نظر می‌کنیم. در کامپیوترهای امروزی هر واحد زمانی حدوداً معادل یک نانوثانیه است ولی این تقریب با پیشرفت تکنولوژی (در سخت‌افزار) تغییر می‌کند (قانون مور).

گاهی در مدل‌های قدیمی هزینهٔ اعمال پایه واحد در نظر گرفته نمی‌شد:

* **معیار هزینه واحد** (پیچیدگی اعمال حسابی) : هر عملیات به اندازهٔ یک واحد زمانی (ثابت) هزینه دارد.
* **معیار هزینه لگاریتمی** (پیچیدگی بیتی) : هزینهٔ انجام هر عملیات با لگاریتم آن متناسب است. مثلاً جمع دو عدد n-بیتی \mathcal O (n) است.

معیار هزینه لگاریتمی درک و تحلیل الگوریتم را سخت‌تر و سنگین‌تر می‌کرد. همچنین در کامپیوترهای امروزی هر عدد در  یا  ذخیره می‌شود و معمولاً ۳۲ یا ۶۴ بیت دارد و اعمال ساده در آنها هزینهٔ ثابتی دارد \mathcal O (1). به همین دلیل معیار هزینه واحد ساده‌تر و به واقعیت نزدیکتر است. به همین دلیل اکثراً از این معیار استفاده می‌شود.

در مقابل اعداد قابل ذخیره در  (هر چند بزرگ) محدود اند n \leqslant 2^{64} \in \mathcal O (1). این تناقض از تحلیل کردن الگوریتم‌ها به درستی جلوگیری می‌کند. در معیار هزینه ثابت امکان سوء استفاده وجود دارد. اگر بتوان جمع و ضرب و ... برای اعداد 2^n-بیتی را با هزینهٔ ثابت انجام داد، می‌توان از این قابلیت برای حل سریعتر و راحت‌تر مسائل سوء استفاده کرد؛ در حالی که در واقعیت چنین امری ممکن نیست (دلیل استفاده از معیار هزینه لگاریتمی نیز همین بود).

در نتیجه باید تعریفمان از  را در مدل محاسباتی دقیق کنیم. یک  دنباله‌ای از صفرها و یک‌ها است. اگر طول این دنباله‌ها را ۳۲ فرض کنیم به تعریف  می‌رسیم. اگر طولشان را واحد فرض کنیم (بیت) هزینهٔ هر عمل مثل معیار لگاریتمی می‌شود (مثلاً برای جمع دو عدد n-بیتی باید بیت به بیت جمع کرد و برای هر بیت یک واحد هزینه می‌شود)؛ همچنین اگر طولشان را دلخواه (هر اندازه بزرگ) فرض کنیم به تعریف  می‌رسیم و هزینهٔ هر عمل مثل معیار هزینه واحد می‌شود.

هر عمل پایه (مثل ۴ عمل اصلی و اعمال بیتی منطقی و ...) بر روی هر  به اندازهٔ یک واحد زمانی هزینه دارد. طول هر  و اعمالی که می‌توان روی آنها انجام داد بستگی به تعریف خودمان دارد و در هر موقعیت و زمینه‌ای باید انتخاب مناسب انجام داد تا واقعیت را به شکل دقیق‌تری مدل‌سازی کند.

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

### پیچیدگی بیتی
در تحلیل پیچیدگی محاسباتی اعمال ریاضی هدف ما پیدا کردن پیچیدگی محاسبهٔ یک عمل مثل ضرب است و نمی‌توانیم صورت مسئله (عمل ضرب n-بیتی) را یک عمل پایه فرض کنیم. در موارد مشابه از پیچیدگی بیتی استفاده می‌کنیم. در این مدل هر  یک‌بیتی است و اعمال پایه تنها روی یک بیت اعمال می‌شوند.

### پیچیدگی اعمال حسابی
در این مدل هر  تعداد بیت‌های دلخواهی دارد. در الگوریتم‌های محاسبات ماتریسی معمول (مثل روش گاوس) پیچیدگی اعمال حسابی \mathcal O (n^3) است ولی پیچیدگی محاسباتی یا بیتی آن ممکن است بسیار فراتر رود (نمایی)، زیرا در طول محاسبه (مثلاً در هر عمل کاهش یا حذف) ممکن است درایه‌های ماتریس به صورت نمایی رشد کنند.

## پیچیدگی‌های زمانی متداول

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

## جستارهای وابسته
* نظریه پیچیدگی محاسباتی
* پیچیدگی زمانی
* طراحی الگوریتم
* مهندسی الگوریتم
* کلاس پیچیدگی

## منابع

* بابا محمودی، رمضان. *کتاب طلایی پویندگان دانشگاه تحلیل و طراحی الگوریتم‌ها*. تهران:نشر ۱۳۹۰.
* احمدی، احمد. *فایل در فایل*. چاپ دوم. تهران: نشر۲، ۱۳۷۵

رده:تحلیل الگوریتم‌ها
رده:منابع محاسباتی
رده:نظریه پیچیدگی محاسباتی
رده:زمان