0% found this document useful (0 votes)
22 views111 pages

Data Structure 15

Uploaded by

trip07mehrdad
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
22 views111 pages

Data Structure 15

Uploaded by

trip07mehrdad
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 111

‫به نام خالق هستی‬

‫ساختمان داده ها‬

‫نویسنده ‪ :‬سید سینا دالور‬


‫عضو رسمی انجمن علمی کامپیوتر دانشگاه آزاد شیراز (پردیس)‬

‫مدرس ‪ C#‬و ‪JAVA‬‬

‫ویرایش ‪ :‬حمید رضا بهبود‬


‫کارشناسی کامپیوتر‬

‫برنامه نویس ‪ C#‬و ‪JAVA‬‬

‫تهیه شده برای دانشجویان دانشگاه آزاد شیراز ( پردیس )‬


‫مقدمه‬
‫با نام خالق باورهای مثبت و پاک آغاز میکنم ‪ ,‬همان کسی که در چالشهای زندگی‬
‫حکمتی ارزنده به من آموخت ‪ .‬شاکر آن معبود هستم که در نشیب های زندگی در ازای‬
‫ایمانم به او مرا به سمت فراز برد ‪ .‬بعد از کسب تجربه از ویرایش سوم جزوه مبانی برنامه‬
‫نویسی جاوا و ساعت ها تدریس برنامه نویسی بنده بر آن شدم تا جزوه ساختمان داده ها‬
‫را با کمک دوست عزیزم مهندس حمیدرضا بهبود تالیف کنم ‪ .‬یادگیری شما از این جزوه‬
‫بستگی به آشنایی شما با زبان برنامه نویسی جاوا دارد زیرا که ساختمان داده ها را در‬
‫جاوا بررسی خواهیم کرد ‪ .‬گرچه تمرکز بنده روی مفاهیم ساختمان داده ها می باشد اما‬
‫آشنایی شما با زبان برنامه نویسی جاوا به شما کمک خواهد کرد تا بتوانید هر چه بهتر‬
‫مطالب این جزوه را بیاموزید ‪ .‬از آنجا که ساختمان داده ها درسی شیرین و مفهومی است‬
‫لذا از شما تقاضا دارم تا به جای حفظ کد ها و شبه کد ها آن ها را عمیقاً بیاموزید ‪.‬‬
‫امیدوارم بتوانم مجدداً با اهداء جزوه جدیدی از خودم سهم کوچکی در عرصه آموزشی و‬
‫علمی دانشگاهم داشته باشم ‪ .‬تمام تالشم را می کنم تا در آینده ی نه چندان دور‬
‫ویرایش های بهتر و ارزنده تر این جزوه را اختیارتان قرار دهم ‪ .‬در اینجا قصد دارم‬
‫تا از استاد صدیقه طاهری بابت مساعدت شان جهت نشر جزوه تشکر کنم ‪ .‬همچنین از‬
‫جناب دکتر فرشید شیرافکن بابت تدریس غیرحضوری اینجانب در فرادرس نیز کمال‬
‫تشکر را دارم ‪.‬‬

‫سید سینا دالور‬

‫عضو رسمی انجمن علمی دانشگاه آزاد شیراز پردیس‬

‫‪18‬بهمن‪1398‬‬
‫‪2‬‬
‫آنچه خواهید خواند ‪...‬‬
‫پیشنهادات و انتقادات و راه های ارتباطی با ما‪6.........................................................................‬‬

‫‪)۱‬ساختمان داده چیست؟‪7.............................................................................................................‬‬

‫‪)۲‬آرایه ها‪10......................................................................................................................................‬‬
‫‪ )۳‬توابع بازگشتی‪15........................................................................................................................‬‬
‫‪ )۳-۱‬فاکتوریل‪16..........................................................................................................‬‬

‫‪ )۳-۲‬جمع اعداد‪20........................................................................................................‬‬

‫‪)۳-۳‬فیبوناچی‪21............................................................................................................‬‬

‫‪ )۳-۴‬زادولد خرگوش ها‪23............................................................................................‬‬

‫‪ )۳-۵‬برج هانوی‪24..........................................................................................................‬‬

‫‪ )۳-۶‬جستجوی دودوئی‪27.........................................................................................‬‬

‫‪)۳-۷‬سایرتوابع‪30...........................................................................................................‬‬
‫‪)۴‬پیچیدگی زمانی‪33.......................................................................................................................‬‬
‫‪)۵‬لیست پیوندی‪43...........................................................................................................................‬‬
‫‪)۵-۱‬گره‪43.......................................................................................................................‬‬

‫‪)۵-۲‬لیست پیوندی یک طرفه‪45................................................................................‬‬

‫‪)۵-۳‬لیست پیوندی دوطرفه‪51...................................................................................‬‬

‫‪)۵-۴‬لیست پیوندی چرخشی‪51.................................................................................‬‬

‫‪)۶‬پشته ‪52..........................................................................................................................................‬‬
‫‪3‬‬
‫‪ )۶-۱‬باالنس پرانتز ها‪57....................................................................................................................‬‬

‫‪)۶-۲‬تبدیل نگارش های نوشتاری‪61...............................................................................................‬‬

‫‪)۷‬صف‪75...................................................................................................................................... ......‬‬
‫‪)۷-۱‬صف خطی‪75.................................................................................................................................‬‬

‫‪)۷-۲‬صف حلقوی‪80..............................................................................................................................‬‬

‫‪)۸‬درخت ‪84......................................................................................................................................‬‬
‫‪)۸-۱‬درخت مرتب‪86......................................................................................................................‬‬

‫‪ )۸-۲‬درخت دودویی‪86.................................................................................................................‬‬

‫‪ )۸-۳‬درخت دودویی پر‪87............................................................................................................‬‬

‫‪ )۸-۴‬پیمایش درخت دودویی‪89.................................................................................................‬‬

‫‪ )۸-۵‬درخت عبارت‪92...................................................................................................................‬‬

‫‪ )۸-۶‬درخت دودویی کامل‪93......................................................................................................‬‬

‫‪ )۸-۷‬درخت جستجوی دودویی‪94.............................................................................................‬‬

‫‪ )۸ -۸‬هرم‪95...................................................................................................................................‬‬

‫‪)۸-۹‬صف اولویت…………………………………‪97……………………………....………..‬‬

‫‪) ۷‬درهم سازی‪100...........................................................................................................................‬‬

‫‪)۷-۱‬توابع درهم سازی‪101....................................................................................‬‬

‫‪)۷-۲‬روش های برخورد با برخورد‪103.................................................................‬‬

‫‪)۷-۲-۱‬زنجیره ای‪103 ...............................‬‬


‫‪4‬‬
‫‪ )۷-۲-۲‬آدرس دهی باز‪104 ..................‬‬

‫منابع‪110...................................................................................................................................‬‬

‫‪5‬‬
‫پیشنهادات و انتقادات و راه های ارتباطی با ما‬
‫از آنجا که فقط خداوند عالمیان است که کامل و بی نقص است اگر دانشجویان عزیز با‬
‫مطالعه این جزوه متوجه نقاط ضعف شدند و حس کردند که جزوه نیازمند ویرایش می‬
‫باشد می توانند از طریق هر پیام رسانی بجز سروش با شناسه ‪ @ssd1377‬یا با‬
‫با اینجانب در میان بگذارند ‪ .‬شما می توانید‬ ‫شماره تلفن همراه ‪۰۹۳۶۲۱۷۵۹۴۷‬‬
‫با همکار بنده آقای بهبود نیز با شماره تلفن همراه ‪ ۰۹۱۷۷۸۷۱۰۹۴‬در ارتباط باشید‬
‫‪ .‬از راه های گفته شده نیز میتوانید برای درخواست کالس ساختمان داده ها یا جاوا یا‬
‫سی شارپ و ‪ ( . . .‬کالس های رفع اشکال یا کالس های جمع بندی و ‪ )....‬اقدام کنید ‪.‬‬

‫بنده در تالشم تا هر چه زودتر به عنوان مسئول برگزاری کارگاه ها و کالس های انجمن‬
‫دوره های خوب و پرباری را برایتان تدارک ببینم ‪.‬‬

‫با تشکر از همکاری شما مخاطب گرامی‬

‫سید سینا دالور‬

‫‪6‬‬
‫‪)۱‬ساختمان داده ها چیست؟‬
‫بیایید برای درک ساختمان داده ها یک کتابخانه را تصور کنیم ‪ .‬درون هر کتابخانه‬
‫تعدادی کتاب وجود دارد که آنها را به داده ها تشبیه می کنیم ‪ .‬نظمی که کتاب ها در‬
‫کنار هم دیگر دارند را ساختمان داده ها تصور کنید ‪ .‬به طور مثال میتوانیم کتابها را در‬
‫کنار همدیگر یا روی هم و غیره قرار دهیم ‪.‬‬

‫شکل تعدادی از ساختمان داده های معروف را در ادامه می بینید که هر کدام قوانین‬
‫متفاوتی دارند ‪.‬‬

‫‪7‬‬
‫به مجموع این داده ها (کل کتابخانه) که در کنار همدیگر قرار داده شدهاند پایگاه داده‬
‫ها گفته می شود ‪ .‬دقیقا بر خالف تصور بعضی که پایگاه داده ها را همان ساختمان‬
‫داده ها میدانند !‬

‫درس ساختمان داده ها به بررسی تعدادی از ساختمان داده های معروف میپردازد که‬
‫کاربرد زیادی در امور کامپیوتر دارند از جمله پشته ‪ ,‬لیست پیوندی ‪ ,‬صف و غیره ‪.‬‬

‫انواع ساختمان داده های را به دو قسمت دسته بندی می کنیم ‪.‬‬

‫ساختمان داده های خطی ‪ :‬آرایه ‪ ,‬لیست پیوندی ‪ ,‬صف ‪ ,‬پشته و ‪...‬‬

‫ساختمان داده های غیر خطی ‪ :‬گراف ‪ ,‬درخت‬

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

‫‪8‬‬
‫زیرا که هدف درس ساختمان داده ها آشنایی با کاربرد ساختمان داده ها است و نباید‬
‫تمرکز به سمت زبان برنامه نویسی خاصی برود ‪.‬‬

‫تصور کنید که می خواهید تعداد زیادی داده را مدیریت یا ذخیره کنید ‪ .‬در این حالت‬
‫استفاده از متغیرها اصال توصیه نمی شود زیرا مدیریت تعداد زیادی داده در متغیرها با‬
‫محدودیت هایی همراه است ‪ .‬از این رو برای رفع این مشکل از ساختمان داده هایی‬
‫همچون آرایه و امثال آن استفاده میکنیم تا بتوانیم بر این محدودیت ها غلبه کنیم ‪.‬‬

‫‪9‬‬
‫‪ )۲‬آرایه(‪)Array‬‬
‫آرایه از مهم ترین و کاربردی ترین ساختمان داده ها است ‪ .‬همانطور که کم و بیش درباره‬
‫آرایه شناختی دارید آرایه تعدادی خانه حافظه در برنامه است ‪ .‬با توجه به تجربه ای که‬
‫از تدریس این مبحث دارم پیشنهاد می کنم که برای یادگیری هر چه عمیق تر آرایه‬
‫فنجان ذهنتان را خالی کنید تا بنده بتوانم آرایه را به شکل درست در ضمیرتان قرار دهم‬
‫‪ .‬اگر شما برنامه نویس حرفه ای هستید و با آرایه نیز آشنایی عمیقی دارید نیازی به‬
‫مطالعه این قسمت نخواهید داشت ‪ .‬برای عزیزان عالقه مند به این مبحث جزوه مبانی‬
‫برنامه نویسی جاوای خودم را توصیه می کنم زیرا مفصال درباره تعریف و استفاده از آرایه‬
‫در جاوا شرح داده ام ‪.‬‬

‫آرایه در واقع مانند جدولی یا لیستی است که ما داده هایمان را در هر خانه اش وارد‬
‫میکنیم ‪ .‬لیستی که در پایین می بینید را به عنوان یک آرایه در نظر بگیرید تا بتوانید‬
‫خانه های آرایه که در کنار هم هستند را به درستی متصور شوید ‪ .‬دراین مثال داده‬
‫هایمان تعدادی نام هستند ‪.‬‬

‫سینا‬ ‫حمید‬ ‫حسام‬ ‫‪null‬‬ ‫نیلوفر‬


‫می توانیم آرایه ای از جنس اعداد داشته باشیم تا قادر به ذخیره سازی اعداد نیز باشیم‪.‬‬

‫‪۲۰‬‬ ‫‪۱۵‬‬ ‫‪۱۷‬‬ ‫‪۶‬‬ ‫‪۱۸‬‬ ‫‪۸‬‬ ‫‪۱۲‬‬ ‫‪۳‬‬ ‫‪۱۰‬‬ ‫‪۱۳‬‬
‫حتی می توانیم آرایه ای داشته باشیم که بدون محدودیت همه نوع داده ای را ذخیره‬
‫کند ‪.‬‬

‫در ابتدا برای استفاده از آرایه باید آن را تعریف کنیم و سپس قادر خواهیم بود تا مقادیر‬
‫درون آن را مقدار دهی کنیم ‪ .‬برای اینکه بتوانیم داده مان را درون خانه مورد نظرمان‬

‫‪10‬‬
‫قرار دهیم می بایست اندیس همان خانه را در زمان مقدار دهی ذکر کنیم ‪ .‬هر خانه یک‬
‫عدد اختصاصی دارد که اندیس ) ‪ ( index‬نامیده میشود ‪ .‬اندیس ها مانند کد پستی‬
‫هستند‪ .‬برای اینکه بتوانیم چیزی را به جای مشخصی هدایت کنیم باید عددی را داشته‬
‫باشیم که به ما کمک کند تا بتوانیم آدرس دقیق را پیدا کنیم ‪ .‬اندیس ها می توانند از‬
‫صفر یا از یک شروع شوند ‪ .‬زبان جاوا که زبانی ‪ zero-base‬است اندیس آرایه هایش از‬
‫صفر شروع می شوند ‪ .‬بنابراین می توانیم بگوییم که عدد ‪ ۲۰‬در آرایه باال در خانه ‪ ۰‬است‬
‫و عدد ‪ ۱۷‬در خانه با اندیس ‪ ۲‬است ‪ .‬طول این آرایه نیز برابر ‪ ۱۰‬است و اندیس خانه‬
‫آخر آن ‪ ۹‬است‪.‬‬

‫آرایه می تواند بیش از یک بعد داشته باشد ‪ .‬بطور مثال در ادامه یک آرایه دو بعدی را‬
‫میبینید که اندیس هر خانه به تفکیک در آن ذکر شده ‪.‬‬

‫‪۰,۰‬‬ ‫‪۱,۰‬‬ ‫‪۲,۰‬‬ ‫‪۳,۰‬‬


‫‪۰,۱‬‬ ‫‪۱,۱‬‬ ‫‪۲,۱‬‬ ‫‪۳,۱‬‬
‫‪۰,۲‬‬ ‫‪۱,۲‬‬ ‫‪۲,۲‬‬ ‫‪۳,۲‬‬
‫‪۰,۳‬‬ ‫‪۱,۳‬‬ ‫‪۲,۳‬‬ ‫‪۳,۳‬‬

‫در ادامه یک مکعب می بینید که در تصویر سازی آرایه سه بعدی درذهن شما موثر است‪.‬‬
‫تصور بیش از سه بعد غیر ممکن است ‪ .‬اما می توانید از آرایه با ابعاد بیشتر از ‪ ۳‬بعد‬
‫استفاده کنید ‪.‬‬

‫‪11‬‬
‫با اینکه شما در درس برنامه نویسی پیشرفته با تعریف و مقدار دهی آرایه آشنا شدید اما‬
‫در اینجا چند مثال برای شما ضمیمه می کنم تا اگر فراموش کردید بیاد بیاورید ‪.‬‬

‫در زیر آرایه ای به نام ‪ nums‬از جنس اعداد صحیح ‪ ۳۲‬بیتی تعریف کردیم که یک‬
‫بعدی است و طول آن ‪ ۱۰‬است ‪.‬‬

‫; ] ‪int [ ] nums = new int[ 10‬‬


‫در زیر آرایه ای به نام ‪ names‬از جنس رشته تعریف کردیم که دو بعدی است و طول‬
‫آن ‪ ۵۰‬در ‪ ۳۰‬است ‪.‬‬

‫;]‪String names [] []= new String [50][30‬‬


‫ایرادهای آرایه‬
‫‪-‬آرایه برای تعداد محدود استفاده می شود ‪ .‬اگر آرایه را ‪ 10‬تایی تعریف کنید دیگر نمی‬
‫توانید ‪ 11‬تا داده درون آن بریزید ‪.‬‬

‫‪-‬برای قرار دادن داده درون آرایه باید حتما اندیس را ذکر کنیم ‪ .‬این در صورتی ایراد به‬
‫شمار می رود که ندانیم کدام خانه خالی و کدام پر است ‪.‬‬

‫‪-‬برای حذف هر داده احتماال نیاز به عمل شیفت داریم ‪ .‬اگر بخواهیم داده ای حذف کنیم‬
‫در واقع بین انبوه داده ها خالیی ایجاد کرده ایم ‪ .‬برای پرکردن این خال باید تک تک‬
‫داده ها را جابجا کنیم تا این خال به درستی حذف شود ‪ .‬این مسئله در مقیاس بزرگ‬
‫بسیار اهمیت دارد ‪.‬‬

‫‪12‬‬
13
‫‪-‬برای اینکه ترتیب پشت سر هم قرار گرفتن داده ها را تغییر دهیم باید از الگوریتم هایی‬
‫با پیچیدگی اجرایی باال بهره ببریم ‪.‬‬

‫‪...-‬‬

‫در زبان جاوا ‪ ArrayList‬ساختمان داده ای است که تا حد زیادی محدودیت های آرایه‬
‫را کمرنگ کرده است ‪.‬‬

‫‪14‬‬
‫‪ )۳‬توابع بازگشتی‬
‫برای حل هر مسئله باید روشی ارائه کنیم ‪ .‬در درس طراحی الگوریتم ها با یکی از این‬
‫روش ها که به آن تقسیم و غلبه گفته میشود آشنا می شویم ‪ .‬در این روش حل به سراغ‬
‫مسائلی می رویم که قابل شکسته شدن به تکه های کوچک تر باشد ‪ .‬بنابراین می توانیم‬
‫مسائلی را که برایمان دشوار است با حل کردن تکه های کوچک حل کنیم ‪ .‬این روش‬
‫روش بسیار کار آمدی است که نخستین بار توسط ناپلئون بکار گرفته شد ‪ .‬حتی در‬
‫کتاب "قورباغه ات را قورت بده" مفصال درباره آن بحث شده است ‪ .‬شخصا توصیه میکنم‬
‫این کتاب را مطالعه کنید ‪.‬‬

‫در درس ساختمان داده ها با این روش مختصرا آشنا می شوید و یاد می گیرید که چگونه‬
‫توابعی طراحی کنید تا بتواند مسائل را با توجه به این روش حل کنید ‪.‬‬

‫توابع بازگشتی توابعی هستند که غالبا دستورات کمی دارند و برای حل مسئله خودشان‬
‫یا توابع دیگر را فراخوانی می کنند ‪ .‬مثال ‪:‬‬

‫} … )‪F(x){ … G(x‬‬ ‫} ‪F(x){ ... F(x) ...‬‬


‫} … )‪G(x){ … F(x‬‬

‫‪15‬‬
‫‪)۳-۱‬فاکتوریل(‪)Factorial‬‬
‫اولین تابع بازگشتی که به آن می پردازیم تابع فاکتوریل است که معموال اولین مثال برای‬
‫تدریس مبحث تابع بازگشتی است ‪ .‬همانطور که می دانید فاکتوریل عدد صحیحی مانند‬
‫‪ 3‬برابر ‪ 6‬است ‪ .‬در تعریف فاکتوریل به زبان ساده داریم ‪:‬‬

‫‪n! = n * (n-1) * (n-2) * …. * 1‬‬


‫تابع فاکتوریل به زبان جاوا را در ادامه می بینید ‪ .‬سعی کنید عملکرد این تابع را تحلیل‬
‫کنید ‪ .‬همچنین می بینید که برخالف تصورتان از حلقه های تکرار استفاده نکردیم ‪.‬‬

‫تابع زیر را بنویسید و نتیجه را آزمایش کنید ‪.‬‬

‫)‪public static int fact(int n‬‬


‫{‬
‫))‪if((n==1)||(n==0‬‬
‫;‪return 1‬‬
‫‪else‬‬
‫;)‪return n*fact(n-1‬‬
‫}‬
‫این تابع به ازای مقادیر ‪ 0‬و ‪ 1‬مقدار ‪ 1‬را برمیگرداند و در غیر این صورت‪:‬‬

‫)‪n*fact(n-1‬‬

‫‪16‬‬
‫چگونه توابع بازگشتی را طراحی کنیم؟‬
‫خب ‪. . .‬‬

‫اگر متوجه نشدید کامال حق دارید ‪ .‬شما نیز مانند بنده جز اکثریتی هستید که یک‬
‫مطلب را همان بار اول نمی فهمند ‪ .‬پس بیایید روشی که ابداع بنده است را بخوانیم تا‬
‫مطلب کامال جا بیفتد !‬

‫روش بنده از سه مرحله تشکیل شده و با پیمودن این سه مرحله بنده تضمین می دهم‬
‫تا ‪ 90‬درصد توابع بازگشتی را براحتی حل کنید ‪.‬‬

‫قدم اول ‪ :‬این مرحله ی به ظاهر احمقانه از چنان اهمیتی برخوردار است که اگر آن را‬
‫انجام ندهید صورت سوال را نمی فهمید و هیچگاه قادر نخواهید بود تا سوال را حل‬
‫کنید‪ .‬در این مرحله باید برای خود چندین مثال عینی بزنید تا به درستی سوال را‬
‫متوجه شوید مثال برای طراحی تابع بازگشتی فاکتوریل باید مثال های متنوعی بزنید تا‬
‫بفهمید این تابع چگونه رفتار میکند و به ازای مقادیر مشخص دقیقا چه جوابی از آن‬
‫انتظاردارید ‪.‬‬

‫‪5!=5 * 4 * 3 * 2 * 1‬‬
‫‪4!=4 * 3 * 2 * 1‬‬
‫‪2!=2 * 1‬‬
‫‪9!=9 * 8 * ….. * 1‬‬
‫‪1!=1‬‬
‫‪0!=1‬‬

‫‪17‬‬
‫قدم دوم ‪ :‬در این مرحله با کمک مرحله اول باید بتوانید صورت سوال را به قسمت های‬
‫کوچک دیگر بشکنید یا به اصطالح بنده مسئله را تکه تکه کنید ‪ .‬توصیه می کنم که از‬
‫فرمول خود سوال کمک بگیرید ‪ .‬در پیاده سازی عملگرهای ریاضی مانند عملگر فاکتوریل‬
‫یا عملگر ضرب و ‪ . . .‬باید به دنبال یک عملگر جایگزین باشید ‪ .‬مثال برای ارائه ی یک‬
‫فرمول از فاکتوریل از عملگر ضرب استفاده کردیم ‪ .‬همانطور که در مرحله اول دیدیم می‬
‫توانیم بگوییم ‪:‬‬

‫!‪5 ! = 5 * 4‬‬
‫در این مثال کامال مشاهده میشود که برای حل فاکتوریل از یک عملگر دیگر به نام ضرب‬
‫و خود فاکتوریل استفاده شده است ‪ .‬تبریک می گویم ! شما نصف سوال را تا همین جا‬
‫حل کرده اید ‪ .‬حال اگر بتوانید از روی مثال عینی یک فرمول ارائه کنید می توانید به‬
‫مرحله بعد بروید ‪ .‬دقت کنید که برای ایجاد حالت بازگشتی باید درون فرمولتان مجددا‬
‫از خود تعریف استفاده کنید مثل فاکتوریل که درون فرمولش از خود فاکتوریل نام برده‬
‫شده ‪.‬‬

‫!)‪n!=n*(n-1‬‬
‫گاهی پیدا کردن این فرمول سخت تر از اینهاست و خود طراح سوال ممکن است که‬
‫فرمول را در اختیارتان قرار دهد ‪.‬‬

‫قدم سوم ‪ :‬قدم سوم نصفه دیگر ماجراست ‪ .‬زیرا باید مشخص کنیم که فرمولی که در‬
‫مرحله دوم بدست اوردیم تا کجا ادامه دارد و فراخوانی میشود ‪ .‬نکته ای که باید بگویم‬
‫این است که سعی کنید بفهمید در سوالتان چه چیزی بصورت قرار دادی پیدا می شود‬
‫‪ .‬برای مثال در مورد فاکتوریل می دانیم که فاکتوریل ‪ 0‬و ‪ 1‬هر دو برابر ‪ 1‬است ‪ .‬پس‬
‫‪ n=1‬یا ‪n=0‬‬ ‫شرط توقف را می نویسیم ‪.‬‬
‫‪18‬‬
‫تبریک مجدد ! شما با طی کردن این سه قدم سوال را حل کردید ‪ .‬حال برای اینکه بیشتر‬
‫بیاموزید در ادامه تمرینات بیشتری قرار دادیم ‪ .‬توجه داشته باشید که ابتدا برای خود‬
‫مثال عینی بزنید و سپس مسئله را بشکنید و فرمول را بیابید و در انتها شرط‬
‫توقف را پیدا کنید ‪ .‬در غیر اینصورت به جواب نخواهید رسید ‪ .‬برای اینکه مسئله‬
‫فاکتوریل جمع بدی شود باید خالصه نتیجه را به نحوه ی زیر نشان دهیم تا از روی آن‬
‫تابع بازگشتی را طراحی کنیم ‪.‬‬
‫)‪n * Fact(n-1‬‬ ‫‪n>1‬‬
‫= )‪Fact(n‬‬
‫‪1‬‬ ‫‪ n = 0‬یا ‪n = 1‬‬

‫احتماال اکنون تابع بازگشتی فاکتوریل به زبان جاوا را که دیدید بهتر درک می کنید ‪.‬‬

‫اگر بخواهید بفهمید که چگونه سوال حل می شود خالصه ای از آن را برایتان بازگو می‬
‫کنم ‪ .‬ابتدا فرض کنید که تابع فاکتوریل را در تابع اصلی صدا زدید و به ورودی آن مقدار‬
‫‪ 3‬دادید ‪ .‬وقتی تابع صدا زده می شود وارد بخش ‪ else‬می شود و ‪ 3‬را در نتیجه فاکتوریل‬
‫‪ 2‬ضرب می کند ‪ .‬سپس همین اتفاقات برای ‪ 2‬نیز تکرار میشود ‪ .‬زمانی که تابع فاکتوریل‬
‫با مقدار ‪ 1‬یا ‪ 0‬صدا زده شد نتیجه ی آن ‪ 1‬می شود و توابعی که یکی یکی در پشته‬
‫ذخیره شده بودند تا نتیجه محاسبه شوند در هم ضرب شده و نتیجه نهایی برگردانده می‬
‫شود ‪ .‬برای درک بیشتر این مطلب می توانید در اینترنت نحوه فراخوانی توابع تو در تو‬
‫را بصورت ویدئو مشاهده کنید ‪ .‬همچنین برای اینکه بفهمید پشته چگونه کار میکند به‬
‫درس پشته مراجعه کنید ‪.‬‬

‫‪19‬‬
‫‪)۳-۲‬جمع اعداد از یک تا ‪N‬‬
‫حال به سراغ تابع جمع اعداد یک تا ‪ N‬می رویم ‪ .‬این تابع بسیار شبیه تابع فاکتوریل‬
‫می باشد ‪ .‬از همین جهت پیاده سازی این تابع را به عنوان هومورک به شما می سپارم ‪.‬‬

‫‪sum ( n ) = n + (n-1) + (n-2) + (n-3) + . . . + 1‬‬


‫جواب این سوال به شکل زیر است ‪.‬‬

‫)‪public static int sum(int n‬‬


‫{‬
‫)‪if(n==1‬‬
‫;‪return 1‬‬
‫)‪else if (n>1‬‬
‫;)‪return n+sum(n-1‬‬
‫;‪return -1‬‬
‫}‬

‫‪20‬‬
‫‪)۳-۳‬فیبوناچی(‪)Fibonacci‬‬
‫اکنون فیبوناچی را به عنوان تمرین حل می کنیم که مثال ساده ای از توابع بازگشتی‬
‫است ‪ .‬توجه کنید که حل سوال فیبوناچی به روش تقسیم و غلبه نتیجه مطلوبی ندارد و‬
‫این مسئله با روش برنامه نویسی پویا نتیجه بهینه تری از خود نشان میدهد ‪ .‬در اینجا‬
‫صرفا و استثنا برای یادگیری توابع بازگشتی مسئله فیبوناچی را با توابع بازگشتی حل می‬
‫کنیم ‪.‬‬

‫فیبوچی بنده خدا یک دنباله معروف به نام خودش داره ‪ .‬هر جمله این دنباله حاصل‬
‫جمع دو عدد پیش از خود است ‪ .‬حال شاید بپرسید که خب جمله اول و دوم که دو‬
‫جمله قبل از خود ندارند ‪ .‬پس آنها چگونه محاسبه می شوند ؟؟؟ باید بگویم که بصورت‬
‫قرار داد در بعضی منابع دو جمله اول هر دو یک هستند و در بعضی منابع جمله اول ‪0‬‬
‫و جمله دوم ‪ 1‬است ‪ .‬که ما همان قرار داد دوم را در نظر میگیریم ‪ .‬اوکی ؟!‬

‫پس دنباله فیبوناچی چیزی به این شکل است ‪.‬‬

‫‪0 1 1 2 3 5 8 13 21 34 . . .‬‬
‫حال سه قدم طالیی بنده را طی می کنیم ‪.‬‬

‫در قدم اول میخواهیم بدانیم که به ازای چه مقدار ورودی چه خروجی می خواهیم ‪.‬‬
‫پس مثال میزنیم ‪ .‬مثال می خواهیم بدانیم جمله چهارم دنباله فیبوناچی چیست ‪.‬‬

‫‪Fibo ( 4 ) = Fibo ( 3 ) + Fibo ( 2 ) = 2‬‬


‫‪Fibo ( 3 ) = Fibo ( 2 ) + Fibo ( 1 ) = 1‬‬
‫) ‪Fibo ( 100 ) = Fibo ( 99 ) + Fibo ( 98‬‬

‫‪21‬‬
‫در قدم دوم باید یک فرمول بیابیم تا بتوان رابطه بازگشتی را پیدا کرد ‪ .‬این فرمول‬
‫همانطور که متوجه شدید به شکل زیر است ‪.‬‬

‫) ‪Fibo ( n ) = Fibo ( n-1 ) + Fibo ( n-2‬‬


‫در قدم سوم باید شرط توقف را بیابیم ‪ .‬همانطور که قبال گفتم برای پیدا کردن شرط‬
‫توقف باید دنبال قرار داد ها باشیم ‪ .‬در اینجا هر وقت که بپرسند جمله اول و دوم چیست‬
‫ما جواب را از قبل در قالب یک قرار داد می دانیم ‪ .‬پس شرط توقف برابراست با ‪:‬‬

‫‪ n=1‬یا ‪n=2‬‬

‫از این پس می دانیم که اگر این تابع را با مقدار ‪ 1‬یا ‪ 2‬صدا زدیم نتیجه برابر‪ 0‬و ‪ 1‬است‪.‬‬

‫در انتها یادمان باشد تا نتیجه را خالصه کنیم و آن را بنویسیم ‪.‬‬

‫) ‪Fibo ( n-1 ) + Fibo ( n-2‬‬ ‫‪n>2‬‬


‫= )‪Fibo(n‬‬
‫‪n–1‬‬ ‫‪ n = 2‬یا ‪n = 1‬‬
‫تابع فیبوناچی به این شکل می تواند طراحی شود ‪.‬‬

‫)‪public static int fibo(int n‬‬


‫{‬
‫))‪if((n==1)||(n==2‬‬
‫;‪return n-1‬‬
‫‪else‬‬
‫} ;)‪return fibo(n-1)+fibo(n-2‬‬

‫‪22‬‬
‫‪)۳-۴‬زاد و ولد خرگوشها(‪)Rabbits Breeding‬‬

‫حال بیایید قصه ای بشنویم ‪...‬‬

‫روزی روزگاری دوتا بچه خرگوش با هم آشنا شدند ‪ .‬بعد از مدتی بالغ شدند و جفت‬
‫گیری کردند ‪( .‬منم فکر نمی کردم انقدر سریع داستان بره سر اصل مطلب ) اونا صاحب‬
‫دو فرزند شدند ‪ .‬حال تا اینجا اگر حساب کنیم دوتا خرگوش بالغ داریم و دوتا خرگوش‬
‫نابالغ داریم ‪ .‬اگر خرگوش های نابالغ هر کدام مدتی طول بکشد که بالغ شوند و سپس‬
‫جفت گیری کنند و همچنین خرگوش های پیر نیز همچنان جفتگیری کنند و دوتا‬
‫خرگوش بدنیا بیاوردند سوال اینجاست که در نسل ‪ 10‬یا ‪ 6‬یا ‪ ...‬جمعیت خرگوش ها‬
‫چندتاست ؟؟؟ مثال در نسل سوم ‪ 2‬جفت خرگوش در جمعیت موجودند ‪.‬‬

‫حل این سوال بسیار شباهت به حل مسئله فیبوناچی دارد ‪ .‬به عنوان هومورک این سوال‬
‫را حل کنید ‪.‬‬

‫‪23‬‬
‫‪)۳-۵‬برج های هانوی(‪)Hanoy towers‬‬
‫اکنون مسئله ی برج های هانوی را بررسی میکنیم ‪ .‬در این مسئله سه پایه را به شکل‬
‫زیر در نظر بگیرید ‪.‬‬

‫در این مسئله دیسک ها را که هر تعداد می توانند باشند را می خواهیم به میله آخر‬
‫(سمت راست)منتقل کنیم اما تعدادی محدودیت را باید در نظر بگیریم ‪ .‬محدودیت اول‬
‫اینکه حق نداریم در هر مرحله بیش از یک دیسک را جابجا کنیم ‪ .‬محدودیت دوم این‬
‫است که حق نداریم دیسک بزرگتر را روی کوچک تر قرار دهیم ‪.‬‬

‫کلید طراحی تابع بازگشتی این مسئله توجه کردن به فرآیند تکراری است که به وضوح‬
‫در طی حل سوال دیده میشود ‪ .‬برای اینکه بفهمید چه چیزی تکرار میشود همین سوال‬
‫را با سه و بعد چهار دیسک حل کنید ‪ .‬آن وقت میفهمید که با زیاد شدن تعداد دیسک‬
‫ها کار پیچیده نمی شود و صرفا مدت زمان حل آن افزایش می یابد ‪ .‬این نشان دهنده‬
‫این است که تعداد دیسک هر چه باشد شما دیگر می دانید که چگونه این سوال را حل‬
‫کنید ‪ .‬این دقیقا مانند همان روشی است که حل کنندگان مکعب های رنگی روبیک‬

‫‪24‬‬
‫استفاده می کنند تا هر مسئله ای را با یک تکنیک ساده و با تکرار آن حل کنند ‪.‬‬

‫مراحل حل این مسئله برای سه دیسک به ترتیب زیر میباشد ‪.‬‬

‫تابع هانوی را می توانید با طی کردن قدم های سه گانه ای که ذکر کردم طراحی کنید‪.‬‬

‫طراحی تابع بازگشتی برج های هانوی به صورت زیر است ‪ .‬در تابع زیر عدد ‪ n‬همان‬
‫تعداد دیسک هاست و کاراکتر های ‪ c , b , a‬به ترتیب نام پایه های اول ‪ ,‬دوم و سوم‬
‫است ‪.‬‬

‫‪25‬‬
public static void tower(int n, char a,char b,char c)
{
if(n==1)
System.out.println(a+" to "+c);
else
{
tower(n-1,a,c,b);
System.out.println(a+" to "+c);
tower(n-1,b,a,c);
}
}

26
‫‪ )۳-۶‬جستجوی دودوئی(‪)Binary search‬‬
‫حال تابع جستجوی دودوئی را بررسی می کنیم ‪ .‬فرض کنید که آرایه ای داریم و در این‬
‫آرایه به دنبال یک عدد هستیم ‪ .‬برای اینکه آن را بیابیم روش های مختلفی ارائه می‬
‫شود ‪ .‬حتما می توانید حدس بزنید که یکی از این راه ها این است که از اول تا آخر آرایه‬
‫را با عدد مورد نظرمان مقایسه می کنیم ‪ .‬خب این روش که به آن ‪Linear Search‬‬
‫یا جستجوی خطی می گویند خیلی ساده و قابل درک است و به راحتی قابل پیاده سازی‬
‫است ‪ .‬اما اگر تعداد داده ها زیاد باشد و برای پیدا کردن عددی که در خانه آخر است از‬
‫خانه اول به جستجو بپردازیم واقعا کار آمد به نظر نمی رسد ‪ .‬یکی از بهینه ترین روش‬
‫های جستجو ‪ ,‬جستجوی دودوئی است که با وجود ساده بودن و بهینگی اش ایراداتی نیز‬
‫دارد اما از نظر پیچیدگی زمانی سریع ترین الگوریتم جستجو به حساب می آید ‪.‬‬

‫بیایید برای درک الگوریتم جستجوی دودوئی یک بازی کنیم !‬

‫من یک عدد بین ‪ 0‬تا ‪ 100‬را در نظر گرفته ام ‪ .‬حال شما باید با پرسیدن کمترین سوال‬
‫بتوانید آن را پیدا کنید ‪ .‬حتما اولین سوالتان این است که آیا این عدد از ‪ 50‬کمتر است‬
‫یا بیشتر ؟ بعد که بنده جواب بدهم کمتر از ‪ 50‬شما دیگر در مورد اعداد بیش از ‪50‬‬
‫هیچ فکری نمی کنید و کامال آنها را فراموش می کنید ‪ .‬در ادامه می پرسید که آیا از‬
‫‪ 25‬کمتر است یا بیشتر ؟ و این فرآیند تکرار و تکرار و تکرار و تکرار می شود تا آن عدد‬
‫را بیابید و برنده بازی شوید ‪.‬‬

‫خب البته این بازی فقط ذهن شما را برای حل این مسئله آماده می کند و نکات ریزی‬
‫نیز وجود دارد که باید با حل تمرین بیشتر به آنها پی ببرید ‪.‬‬

‫‪27‬‬
‫برای اینکه بتوانیم در آرایه ای دنبال عددی به روش جستجوی دودوئی بگردیم باید ابتدا‬
‫آن را ‪ Sort‬یا همان مرتب کنیم ‪ .‬نیاز این الگوریتم به داده های مرتب شده از ایرادات‬
‫الگوریتم جستجوی دودوئی است ‪ .‬آیا می توانید ایرادات دیگری برای این الگوریتم ارائه‬
‫کنید ؟‬

‫ترجیحا داده ها را از کوچک به بزرگ مرتب می کنیم ‪ .‬مراحلی که در این الگوریتم تکرار‬
‫میشود به شرح زیر است ‪.‬‬

‫ابتدا داده ی وسطی را پیدا میکنیم ‪.‬‬

‫داده ی وسط را با عددی که به دنبالش هستیم مقایسه میکنیم تا اگر برابر بود نتیجه را‬
‫اعالم کنیم و بگوییم که داده ی مورد نظر یافت شده است ‪.‬‬

‫اگر داده مورد نظر پیدا نشد حال به دنبال این هستیم که آیا داده ی مورد نظرمان بزرگتر‬
‫از داده ی وسطی است یا خیر ‪.‬‬

‫اگر داده ی وسطی بزرگتر از داده مان بود در نیمه پایینی آرایه به دنبالش می گردیم و‬
‫نیمه باال را رها می کنیم و همچنین بر عکس ‪.‬‬

‫وقتی متوجه شدیم که عدد مورد نظر احتماال در نیمه پایینی است همین الگوریتم را‬
‫برای نیمه پایینی انجام می دهیم ‪ .‬اگر هم که در نیمه باالیی بود این الگوریتم را برای‬
‫نیمه باالیی آرایه صدا می زنیم ‪.‬‬

‫در نهایت با تکرار مراحل به جوابی می رسیم ‪ .‬جواب در نهایت این است که عنصر مورد‬
‫نظرمان پیدا شده است یا خیر ‪.‬‬

‫‪28‬‬
public static int binarySearch(int a[],int n,int low,int high)
{
if(low<=high)
{
int middle=(low+high)/2;
if(n<a[middle])
return binarySearch(a,n,low,middle-1);
else if(n>a[middle])
return binarySearch(a,n,middle+1,high);
else
return middle;
}
return -1;
}
‫تابعی که در باال میبینید در صورت پیدا کردن عدد آن را بازمی گرداند و در غیر این‬
. ‫ را باز می گرداند‬-1 ‫صورت عدد‬
29
‫‪)۳-۷‬سایر توابع بازگشتی(‪)Other methods‬‬
‫در انتهای درس توابع بازگشتی به عنوان تمرین توابعی مانند عملگر ضرب و عملگر توان‬
‫و عملگر ترکیب و عملگر باقی مانده و عملگر خارج قسمت صحیح را پیاده سازی کنید‬
‫که البته به دلیل عطوفتی که بنده دارم این توابع را برایتان ضمیمه میکنم که امیدوارم‬
‫باعث تنبلی شما نشود ! ! ! همچنان تاکید دارم که می توانید با روشی که گفتم این توابع‬
‫را پیاده سازی کنید اما این به شرطی است که مراحل را قدم به قدم بگذرانید ‪.‬‬

‫)‪public static int multiply(int operand1, int operand2‬‬


‫{‬
‫)‪if(operand2==0‬‬
‫{‬
‫;‪return 0‬‬
‫}‬
‫;)‪return operand1+multiply(operand1,operand2-1‬‬
‫}‬
‫‪/////////////////////////////////////‬‬
‫)‪public static int power(int n,int m‬‬
‫{‬
‫)‪if(m==1‬‬
‫;‪return n‬‬
‫‪30‬‬
else
return n*power(n,m-1);
}
///////////////////////
public static int combination(int n,int m)
{
if((m==0)||(m==n))
return 1;
else
return combination(n-1,m)+combination(n-1,m-1);
}
//////////////////
public static int quotient(int a,int b)
{
if(a<b)
return 0;
else
return quotient(a-b,b)+1;
}
////////////////////////////

31
public static int remainder(int a,int b)
{
if(a<b)
return a;
else
return remainder(a-b,b);
}
////////////////////////////

32
‫‪)۴‬پیچیدگی زمانی(‪)Time Complexity‬‬
‫درس پیچیدگی اجرایی یا همان پیچیدگی زمانی درسی فوق العاده مهم و کلیدی و‬
‫سخت است ‪ .‬با آنکه بنده اعتقاد دارم که هیچ چیز از توانایی شما خارج نیست اما‬
‫اگر این درس را مطالعه نکنید در این درس چیز آنچنانی را از دست نمی دهید ‪ .‬در درس‬
‫طراحی الگوریتم ها با این درس مفصال درگیر می شوید و مجبورید یاد بگیرید ‪ .‬اما بد‬
‫نیست در اینجا برای اینکه ذهنیتی از این درس داشته باشید مقداری با آن آشنا شوید ‪.‬‬
‫همان طور که گفتم این درس به شدت مهم است و در اکثر درس های پیش رویتان با‬
‫آن کار دارید ‪ .‬پس با وجود سختیش حد اقل کمی پیش زمینه داشته باشید ‪.‬‬

‫حکایت مسابقه خرگوش و الک پشت را حتما شنیده اید ‪ .‬هر دو یک هدف را دنبال می‬
‫کنند ‪ .‬یکی سریع تر و یکی آهسته تر به نتیجه میرسد ‪ .‬هر کدام قابلیت خود را دارند ‪.‬‬

‫این داستان را به درس خودمان تعمیم می دهیم ‪ .‬اگر الک پشت را یک الگوریتم و‬
‫خرگوش را یک الگوریتم دیگر تصور کنیم یک مهندس کامپیوتر باید بتواند یکی از این‬
‫ها را برگزیند ‪ .‬برای انتخاب الگوریتم ها دو معیار وجود دارد تا یک مهندس بتواند بدور‬
‫از احساسات و سلیقه شخصی الگوریتم بهتر را تشخیص دهد ‪ .‬این دو معیار پیچیدگی‬
‫زمانی و پیچیدگی فضا هستند ‪ .‬هر الگوریتم براساس زمان و فضایی که برای حل سوال‬
‫نیاز دارد قضاوت می شود ‪ .‬اگر بخواهیم بدانیم که الگوریتم جستجوی خطی بهتر است‬
‫یا جستجوی دودویی باید بدانیم که کدام یک زمان و فضای کمتری برای حل مسئله نیاز‬
‫دارند ‪ .‬گاهی برای ما معیار زمان پر رنگ تر است از معیار فضا و همچنین گاهی بلعکس‬
‫‪ .‬برای مثال حل مسئله فیبوناچی به روش تقسیم و غلبه بسیار فضای زیادی اشغال می‬
‫کند در حالی که روش برنامه نویسی پویا فقط دو یا سه خانه را برای محاسبه نیاز دارد ‪.‬‬
‫در درس ساختمان داده ها در مورد پیچیدگی فضا صحبت نمی کنیم اما این را به خاطر‬
‫‪33‬‬
‫داشته باشید که با وجود ارزان بودن فضا یا همان حافظه ‪ ,‬باز هم پیچیدگی فضا معیار‬
‫بسیار تاثیر گذاری در تصمیم گیری است ‪ .‬به رسم تمام کتاب ها که ابتدا تعاریف ارائه‬
‫می دهند ما نیز یک تعریف از پیچیدگی اجرایی یا همان پیچیدگی زمانی ارائه میدهیم‬
‫با آنکه پس از آن دیگر برایمان اهمیتی نخواهد داشت‪.‬‬

‫پیچیدگی یک الگوریتم تابعی است که مدت زمان اجرای استفاده شده توسط‬
‫الگوریتم را بر حسب تعداد داده های ورودی ‪ n‬اندازه می گیرد ‪.‬‬

‫همان طور که متوجه شدید قرار است تا یک تابع ارائه کنیم که بتواند سرعت اجرای‬
‫الگورتیم یک برنامه را توصیف کند ‪ .‬هر الگوریتم یک تابع پیچیدگی مخصوص به خودش‬
‫دارد ‪ .‬در میان الگوریتم هایی که همگی یک سوال را حل می کنند برای انتخاب الگوریتم‬
‫بهتر از لحاظ سرعت اجرا باید توابع پیچیدگیشان را با هم از نظر بزرگی مقایسه کنیم ‪.‬‬
‫الگوریتمی که تابع پیچیدگی کوچک تری دارد برنده ی ماجراست ‪.‬‬

‫قبل از اینکه پیچیدگی اجرایی را خودمان بدست بیاوریم باید پیچیدگی های معروف و‬
‫پرکاربرد را بشناسیم ‪ .‬در جدول زیر تعدادی پیچیدگی معروف را می بینید ‪.‬‬

‫‪Notation‬‬ ‫‪Name‬‬ ‫نام‬


‫)‪O(1‬‬ ‫‪Constant‬‬ ‫ثابت‬
‫)‪O(n‬‬ ‫‪Linear‬‬ ‫خطی‬
‫)‪O(logn‬‬ ‫‪Logarithmic‬‬ ‫لگاریتمی‬
‫)‪O(n^2‬‬ ‫‪Quadratic‬‬ ‫درجه ‪2‬‬
‫)‪O(n^c‬‬ ‫‪Polynomial‬‬ ‫چند جمله ای‬
‫)‪O(c^n‬‬ ‫‪Exponential‬‬ ‫نمایی‬
‫)!‪O(n‬‬ ‫‪Factorial‬‬ ‫فاکتوریل‬

‫‪34‬‬
‫در درس ساختمان داده ها لگاریتم در حالت پیش فرض در پایه ‪ 2‬است ‪.‬‬

‫در ادامه بطور مثال ترتیبی از این پیچیدگی ها را می بینید ‪.‬‬

‫)‪O ( 1 )<O(logn)<O(n)<O(nlogn)<O(2^n)<O(n!)<O(n^n‬‬
‫بطور مثال یک الگوریتم از مرتبه ثابت بهتر از یک الگوریتم از مرتبه لگاریتمی است یا‬
‫مثال یک الگوریتم از مرتبه خطی بسیار بهتر از یک الگوریتم از مرتبه فاکتوریل است ‪.‬‬

‫از آنجا که پیچیدگی ها می توانند با هم ترکیب شوند و ترتیب های زیادی خلق کنند‬
‫ترتیب خاصی را نمیتوان معیار سنجش قرار داد ‪ .‬تنها راه حلی که می توان ارائه داد‬
‫کمک گرفتن از قضایا و روابط مربوط به پیچیدگی اجرایی است ‪ .‬در این مرحله بهتر‬
‫است با اوی بزرگ و اومگای بزرگ و تتا آشنا شویم ‪.‬‬

‫‪Big O‬‬
‫برای اینکه بتوانیم به صورت ریاضی بگوییم که مقادیر تابع ‪ g‬در بینهایت از مقادیر تابع‬
‫‪ f‬در بینهایت بیشتر است باید به این شکل بگوییم ‪.‬‬

‫) )‪f(n) 𝜖 O ( g(n‬‬
‫این جمله به این معناست که به ازای یک مقدار ثابت مانند ‪ c‬مقادیر تابع ‪ g‬تا بینهایت‬
‫از مقادیر تابع ‪ f‬تا بینهایت بیشتر است ‪.‬‬

‫)‪f(n) ≤ c g(n‬‬

‫‪35‬‬
‫‪Big omega‬‬
‫برای اینکه بتوانیم به صورت ریاضی بگوییم که مقادیر تابع ‪ g‬در بینهایت از مقادیر تابع‬
‫‪ f‬در بینهایت کمتر است باید به این شکل بگوییم ‪.‬‬

‫) )‪f(n) 𝜖 Ω ( g(n‬‬
‫این جمله به این معناست که به ازای یک مقدار ثابت مانند ‪ c‬مقادیر تابع ‪ g‬تا بینهایت‬
‫از مقادیر تابع ‪ f‬تا بینهایت کمتر است ‪.‬‬

‫)‪f(n) ≥ c g(n‬‬

‫‪Theta‬‬
‫برای اینکه بتوانیم به صورت ریاضی بگوییم که مقادیر تابع ‪ g‬با ‪ f‬برابر است باید به این‬
‫شکل بگوییم ‪.‬‬

‫) )‪f(n) 𝜖 𝜃 ( g(n‬‬
‫این جمله به این معناست که‬

‫) )‪f(n) 𝜖 O ( g(n‬‬ ‫و‬ ‫) )‪f(n) 𝜖 Ω ( g(n‬‬

‫به عنوان تمرین نشان دهید که ) ‪𝑛2 + 10𝑛 ∈ 𝑂(𝑛2‬‬

‫جواب ‪:‬‬

‫برای این که نشان دهیم ) ‪ 𝑛2 + 10𝑛 ∈ 𝑂(𝑛2‬باید عبارت زیر را اثبات کنیم ‪.‬‬

‫‪𝑛2 + 10𝑛 ≤ 𝑐𝑛2‬‬


‫‪36‬‬
‫می دانیم که اگر به ازای ‪ c‬عدد ‪ 1‬قرار دهیم طرف دیگر ‪ 10n‬بیشتر دارد ‪ .‬پس عدد ‪2‬‬
‫را قرار می دهیم ‪.‬‬

‫‪𝑛2 + 10𝑛 ≤ 2𝑛2‬‬


‫حال برای پیدا کردن ‪ n‬دو عبارت باال را برابر هم قرار می دهیم ‪.‬‬

‫‪𝑛2 + 10𝑛 ≤ 2𝑛2‬‬ ‫‪𝑛2 + 10𝑛 = 2𝑛2‬‬ ‫‪10𝑛 = 𝑛2‬‬ ‫‪𝑛 = 10‬‬

‫در این سوال توانستیم اثبات کنیم که به ازای ضریب ‪ , 2‬تابع 𝑛‪ 𝑛2 + 10‬از نقطه ی‬
‫‪ 10‬به بعد همواره زیر نمودار تابع دیگر قرار می گیرد ‪.‬‬

‫حال به چهار خاصیت این روابط می پردازیم ‪.‬‬

‫خاصیت بازتابی ‪ :‬خاصیت بازتابی بیان می کند که هر تابع می تواند اوی بزرگ خود‬
‫یا اومگای بزرگ خود یا تتای خود باشد ‪.‬‬

‫))𝑛( 𝑓(𝑂𝜖)𝑛(𝑓‬
‫))𝑛(𝑓(‪𝑓(𝑛)𝜖Ω‬‬
‫))𝑛( 𝑓(𝜃𝜖)𝑛(𝑓‬
‫‪37‬‬
‫خاصیت تراگذاری ‪ :‬این خاصیت بیان می دارد که اگر تابعی به نام ‪ g‬با توابع ‪ f‬و ‪h‬‬
‫رابطه داشته باشد می توانیم رابطه ای میان ‪ f‬و ‪ g‬بیابیم ‪ .‬مثال برای تتا میتوان نوشت ‪:‬‬

‫))𝑛(𝑔(𝜃 = )𝑛( 𝑓‬
‫))𝑛(‪𝑓(𝑛) = 𝜃(ℎ‬‬ ‫بنابراین‬
‫))𝑛(‪𝑔(𝑛) = 𝜃(ℎ‬‬
‫خاصیت تقارن تتا ‪ :‬بر اساس این خاصیت اگر تابعی مانند ‪ g‬تتای تابع ‪ f‬باشد عکس‬
‫این قضیه هم صدق می کند ‪ .‬این خاصیت را با خاصیت بازتابی اشتباه نگیرید ‪.‬‬

‫))𝑛(𝑔(𝜃 = )𝑛(𝑓‬

‫))𝑛( 𝑓(𝜃 = )𝑛(𝑔‬


‫خاصیت تقارن ترانهاده ی اوی بزرگ ‪ :‬براساس این خاصیت اگر تابع ‪ g‬بزرگتر مساوی‬
‫‪ f‬باشد نتیجه آن است که تابع ‪ f‬کوچکتر مساوی ‪ g‬است ‪.‬‬

‫))𝑛(𝑔(𝑂 = )𝑛(𝑓‬ ‫))𝑛( 𝑓( ‪𝑔(𝑛) = Ω‬‬

‫خب سوال مهم اینجاست که از کجا باید بفهمیم که رابطه ی دو تابع چگونه است ؟‬
‫خوشبختانه برای اینکه بفهمیم رابطه ی بین توابع چگونه است میتوان از ‪ limit‬استفاده‬
‫کرد ‪.‬‬
‫‪Zero‬‬ ‫تابع )‪ g (n‬بزرگتر است >‪-‬‬
‫)𝑛(𝑓‬
‫‪lim‬‬
‫)𝑛(𝑔 ∞→𝑛‬
‫رشد برابر دارند >‪Constant -‬‬
‫∞‬ ‫تابع )‪ f (n‬بزرگتر است ‪->38‬‬
‫برای حل مسائل حد از قضایای حد استفاده کنید ‪ .‬از آنجا که قضایای حد در درس‬
‫ریاضی ارائه شده اند پیشنهاد می کنم که به درس حد و پیوستگی مراجعه کنید ‪.‬‬

‫اما پیچیدگی زمانی چگونه محاسبه میشود ؟؟؟‬

‫برای اینکه بفهمیم به ازای حجم ورودی هایی که داریم چقدر زمان میبرد تا برنامه مان‬
‫اجرا شود باید یک تابع ارائه کنیم ‪.‬‬

‫بیایید با یک مثال ساده شروع کنیم ‪.‬‬

‫فرض کنید یک ‪ if‬و ‪ else‬داریم ‪ .‬زمان اجرای دستور درون ‪ if‬برابر ‪ n‬است و زمان‬
‫اجرای دستور درون ‪ else‬برابر ‪ m‬است ‪ .‬حال پیچیدگی زمانی این برنامه برابر‬

‫) ‪ Max ( n , m‬است ‪ .‬زیرا یا ‪ if‬اجرا میشود یا ‪ else‬و در نهایت بیشینه زمان برای‬
‫اجرای این برنامه یا ‪ n‬است یا ‪. m‬‬

‫اگر بخواهیم تعداد تکرار حلقه های تکراری مانند‬

‫) ‪for ( i = a ; i <= b ; i = i + k‬‬


‫یا‬

‫) ‪for( i = b ; i >= a ; i = i - k‬‬


‫را بدست آوریم باید از این فرمول استفاده کنیم ‪.‬‬

‫‪(b–a+1)/k‬‬
‫اگر در شرط حلقه عالمت مساوی (=) نبود عدد ‪ 1‬را از فرمول حذف می کنیم ‪ .‬درصورتی‬
‫که عددی اعشاری به دست آمد باید حد باالی آن را در نظر بگیریم ‪.‬‬

‫مثال اگر عدد ‪ 4 / 1‬بدست آمد ‪ 5‬را در نظر بگیرید ‪.‬‬


‫‪39‬‬
‫حال یک مثال حل کنیم ‪.‬‬

‫)‪for(i = 0 ; i<=100 ; i = i+25‬‬


‫جواب این مثال عدد ‪ 5‬است ‪ .‬مثال بعدی را ببینید ‪.‬‬

‫)‪for(i = 0 ; i<=n ; i = i+25‬‬


‫اما در این مثال دیگر جواب یک عدد ثابت نیست و این نشان می دهد که ما به ازای‬
‫مقادیر مختلف از ‪ n‬دفعات تکرار متفاوتی داریم ‪ .‬اگر عدد ‪ n‬کوچک باشد سریع و اگر‬
‫بزرگ باشد دیر به جواب می رسیم ‪ .‬پس اگر کسی از ما درباره ی زمان اجرای این حلقه‬
‫بپرسد ما با فرمول ‪ (n+1)/25‬به او جواب می دهیم ‪ .‬تبریک می گویم این اولین تابع‬
‫پیچیدگی زمانی یا اجرایی است که بدست آورده ایم ‪.‬‬

‫توجه کنید که در برای مقایسه ی توابع پیچیدگی فقط جمله تاثیر گذار آن ها را مقایسه‬
‫می کنند ‪ .‬مثال بجای مقایسه ‪ (n+1)/25‬با تابعی دیگر فقط می نویسیم ‪ . n/25‬در‬
‫ریاضیات دبیرستان با پیدا کردن جمله تاثیر گذار آشنا شده اید ‪.‬‬

‫مثال جمله تاثیر گذار عبارت زیر ‪ n^n‬است ‪.‬‬

‫‪n^2 + n + logn + n^n + 1‬‬


‫برای پیداکردن تعداد تکرار حلقه های تکرار زیر از فرمول ‪log 𝑘 𝑏 − log 𝑘 𝑎 + 1‬‬

‫استفاده می کنیم ‪.‬‬

‫)‪for ( i = a ; i <= b ; i = i*k‬‬


‫یا‬

‫)‪for( i = b ; i >= a ; i = i /k‬‬

‫‪40‬‬
‫اگر در شرط حلقه عالمت مساوی (=) نبود عدد ‪ 1‬را از فرمول حذف می کنیم ‪ .‬درصورتی‬
‫که عددی اعشاری به دست آمد باید حد باالی آن را در نظر بگیریم ‪.‬‬

‫اگر حلقه های تو در تو داشتیم برای محاسبه ی تعداد تکرار باید تکرار حلقه ها را در هم‬
‫ضرب کنیم ‪ .‬مثال اگر به صورت زیر دو حلقه داشتیم که تعداد تکرار یکی ‪ n‬و تکرار‬
‫دیگری ‪ m‬بود می دانیم که تعداد تکرار دستور ‪ S‬برابر ‪ m*n‬است ‪.‬‬

‫( ‪ s‬یعنی ‪) sequence of statement‬‬

‫; ‪for(1 -> n ) { for(1 ->m) { S‬‬ ‫}‬ ‫}‬


‫برای محاسبه پیچیدگی اجرایی حلقه های تکرار که پشت سر هم قرار دارند باید تکرار‬
‫حلقه ی با پیچیدگی بزرگ را در نظر گرفت ‪ .‬زیرا که می دانیم حتی اگر آنها را با هم‬
‫جمع کنیم در نهایت جمله تاثیر گذار است که مقایسه می شود ‪.‬‬

‫{ ) ‪for(1 -> n‬‬


‫;‪S‬‬
‫}‬
‫{ )‪for(1 ->m‬‬
‫;‪S‬‬
‫}‬
‫تا به اینجا به نظر می رسد که محاسبه پیچیدگی بسیار ساده است ‪ .‬اما بطور مثال برای‬
‫محاسبه ی حلقه های تو در توی وابسته روش راحتی را در اختیار نداریم ‪.‬‬

‫{)‪for(i=1;i<=n;i++‬‬
‫{)‪for(j=1;j<=i;j++‬‬ ‫;‪S‬‬ ‫} }‬
‫‪41‬‬
‫یکی از روش های نسبتا ساده برای محاسبه پیچیدگی این دستور کمک گرفتن از جدول‬
‫است ‪.‬‬

‫‪i‬‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫…‬ ‫‪N‬‬


‫تعداد تکرار‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫…‬ ‫‪N‬‬

‫)‪𝑛(𝑛+1‬‬
‫است ‪ .‬که در نهایت به‬ ‫با کمک جدول فهمیدیم که تعداد تکرار کد باال برابر‬
‫‪2‬‬
‫) ‪ 𝑂(𝑛2‬میرسیم ‪.‬‬

‫‪42‬‬
‫‪)۵‬لیست پیوندی(‪)Linked List‬‬
‫لیست پیوندی از پرکاربرد ترین ساختمان داده ها است که از آن برای مدیریت داده ها‬
‫در مقیاس بزرگ استفاده می شود مخصوصاً زمانی که بخواهیم داده ها را سریعا حذف یا‬
‫درج کنیم ‪ .‬اگر به خاطر داشته باشید از آرایه بیشتر برای ذخیره اطالعات استفاده میشود‬
‫ولی در لیست پیوندی هدف دستکاری )‪ (manipulate‬داده ها است ‪ .‬لیست پیوندی‬
‫شامل حداقل یک گره می باشد که آن را ‪ Head‬می نامند ‪ .‬گره ها با اشاره گر ها به‬
‫هم وصل شدهاند ‪.‬‬

‫‪)۵-۱‬گره‬
‫گره ها واحدهای حاوی داده در ساختار لیست پیوندی می باشند که عالوه بر داده‬

‫شامل یک اشاره گر یا دو اشاره گر می باشند ‪.‬‬

‫لیست های پیوندی بر اساس تعداد اشاره گر های گره هایشان دسته بندی میشوند که‬
‫مهمترین آنها لیست پیوندی یک طرفه یا دو طرفه می باشند ‪ .‬در ادامه یک کالس گره‬
‫یک طرفه را میبینید ‪.‬‬

‫‪43‬‬
public class Node{
Object Data;
Node Link;
/////////////////////// default constructor
public Node()
{
this(null,null);
//this.Data=null; //this.Link=null;
}
/////////////////////////// one argument constructor
public Node(Object data)
{
Data=data;
Link=null;
}
/////////////////////////// two arguments constructor
public Node(Object data,Node add)
{
this.Data=data;
this.Link=add; }}

44
‫‪)۵-۲‬لیست پیوندی یک طرفه‬

‫در لیست پیوندی یک طرفه از هر گره میتوانیم صرفاً به گره بعدی دسترسی داشته‬
‫باشیم‪.‬‬

‫در ادامه کد منبع کالس پیاده سازی شده ی لیست پیوندی یک طرفه را می بینید ‪.‬‬

‫{ ‪public class LinkedList‬‬


‫;‪Node Head = null‬‬
‫‪//////////////////////////////////////////// seyed sina delavar‬‬
‫‪///////////////////////////////////////////////add methods‬‬
‫‪///////////////////////////// add first‬‬
‫{ )‪public void addFirst(Object item‬‬
‫;)‪Head = new Node(item, Head‬‬
‫}‬

‫‪///////////////////////////// add last‬‬

‫‪45‬‬
public void addLast(Object item) {
if (Head == null)
addFirst(item);
else {
Node temp = Head;
while (temp.Link != null)
temp = temp.Link;
temp.Link = new Node(item);
}
}

////////////////////////////// add after


public void addAfter(Object item, Object key) {
Node temp = Head;
while (temp != null && !temp.Data.equals(key))
temp = temp.Link;
if (temp != null)
temp.Link = new Node(item, temp.Link);
}

46
///////////////////////////// add before
public void addBefore(Object item, Object key) {
if (Head == null) {
System.out.println(this + "is empty");
} else if (Head.Data.equals(key)) {
addFirst(item);
} else {
Node pre = null;
Node temp = Head;
while (temp != null && !temp.Data.equals(key)) {
pre = temp;
temp = temp.Link;
}
if (temp != null)
pre.Link = new Node(item, temp);
}
}

////////////////////////////// delete
public void delete(Object key)

47
{
if(Head == null)
System.out.println("the linked list is empty");
else if(Head.Data.equals(key))
{
Head = Head.Link;
}
else {
Node temp = Head;
Node pre = null;
while(temp != null && !temp.Data.equals(key) )
{
pre = temp;
temp = temp.Link;
}
if(temp != null)
pre.Link = temp.Link;
}
}
///////////////////////////////////// traverse

48
public void traverse() {
Node temp = Head;
while (temp != null) {
System.out.println(temp.Data);
temp = temp.Link;
}
}

////////////////////////////////////////// search
public Object search(Object item) {
Node temp = Head;
while (temp != null) {
if (temp.Data == item)
return (temp.Data);
temp = temp.Link;
}
return (null);
}
////////////////////////////////////////// reverser
public void reverser()

49
{
if(Head==null)
System.out.println("empty");
else
{
Node p=null;
Node temp=Head;
Node t=temp.Link;
while(t!=null)
{
temp.Link=p;
p=temp;
temp=t;
t=t.Link;
}
temp.Link=p;
Head=temp;
}
}}

50
‫‪)۳-۳‬لیست پیوندی دوطرفه(‪)Doubly LinkedList‬‬

‫در لیست پیوندی دو طرفه از هر گره به گره بعد و گره قبل دسترسی داریم و این باعث‬
‫می شود تا قابلیت های بیشتری نسبت به لیست پیوندی یک طرفه داشته باشیم‪.‬‬

‫‪)۵-۴‬لیست پیوندی چرخشی‬


‫همان طور که دید یدد لی سدت هدای پیو نددی یدک طر فده و دوطر فده در یدک سدو گدره‬
‫‪ Head‬دار نددد و در سددوی دی گددر بدده ‪ null‬ا شدداره مددی کن نددد ‪ .‬لی سددت پیو ندددی کدده‬
‫آ خددرین گددره ی آن بدده گددره ‪ Head‬ا شدداره ک نددد لی سددت پیو ندددی چرخ شددی نام دیدده‬
‫مددی شددود ‪ .‬لی سددت پیو ندددی چرخ شددی میتوا نددد یددک طر فدده یددا دوطر فدده با شددد ‪.‬‬

‫‪51‬‬
‫‪)۶‬پشته(‪)Stack‬‬
‫پشته از ساده ترین و کاربردی ترین ساختمان داده ها است ‪ .‬پشته در واقع به نظم بین‬
‫داده هایی گفته می شود که اضافه شدن و حذف شدن این داده ها صرفاً از یک طرف‬
‫قابل انجام باشد ‪ .‬دقیقاً مانند سبدی که تعدادی توپ درون آن قرار دارد و توپ ها از باال‬
‫وارد سبد شده و روی هم قرار می گیرند ‪ .‬اگر بخواهیم آنها را از سبد خارج کنیم باید از‬
‫باال این کار را انجام دهیم ‪ .‬این مفهوم را به اصطالح ‪ LIFO‬مینامند ‪ LIFO .‬مخفف ‪Last‬‬
‫‪ In First Out‬است ‪ .‬پشته کاربردهای فراوانی در علوم کامپیوتر دارد ‪ .‬به طور مثال در‬
‫طراحی کامپایلرها و سیستم های عامل بسیار پر کاربرد است ‪.‬‬

‫در این جزوه برای اینکه پشته را بهتر درک کنیم تعدادی از کاربرد های آن را به عنوان‬
‫مسئله حل می کنیم و همچنین با زبان برنامه نویسی جاوا کالس پشته را طراحی می‬
‫کنیم ‪ .‬اما قبل از آن بیایید یک پشته را تصور کنیم و تعدادی حروف الفبا را درون آن‬
‫قرار دهیم ‪ .‬در ادامه میبینید که یک پشته ابتدا خالی است و سپس عناصر از باال به‬
‫درون آن وارد میشوند ‪( .‬هر ستون حالتی از پشته را نشان میدهد )‬

‫‪d‬‬
‫‪c‬‬ ‫‪c‬‬ ‫‪c‬‬
‫‪b‬‬ ‫‪b‬‬ ‫‪b‬‬ ‫‪b‬‬
‫‪a‬‬ ‫‪a‬‬ ‫‪a‬‬ ‫‪a‬‬ ‫‪a‬‬

‫حال اگر بخواهیم همین پشته که چهار عنصر درون خود دارد را خالی کنیم باید ابتدا‬
‫عناصر باال را حذف کنیم ‪.‬‬

‫‪52‬‬
‫‪d‬‬
‫‪c‬‬ ‫‪c‬‬ ‫‪c‬‬
‫‪b‬‬ ‫‪b‬‬ ‫‪b‬‬ ‫‪b‬‬
‫‪a‬‬ ‫‪a‬‬ ‫‪a‬‬ ‫‪a‬‬ ‫‪a‬‬

‫در طراحی کالس پشته باید تعدادی سازنده برای مقدار دهی آن وجود داشته باشد ‪ .‬یکی‬
‫از سازنده هایی که در کالس طراحی شده ی زیر می بینید عددی به عنوان گنجایش‬
‫پشته از شما می گیرد ‪ .‬از آنجا که جنس پشته همان آرایه است باید در کالس پشته یک‬
‫آرایه تعریف کنیم ‪ .‬دقت کنید که تفاوت پشته با آرایه در نحوه حذف و اضافه کردن داده‬
‫ها است ‪ .‬همچنین در کالس پشته باید یک اشاره گر به نام ‪ Top‬تعریف کنیم تا بتوانیم‬
‫با آن موقعیت باالترین داده را ذخیره کنیم ‪ .‬اشاره گر ‪ Top‬در ابتدا برابر ‪ -1‬قرار میدهیم‬
‫‪ .‬این مقدار نشان دهنده خالی بودن پشته است ‪ .‬برای کار با پشته باید دو تابع مهم‬
‫تعریف کرد که این دو تابع ‪ pop‬و ‪ push‬هستند ‪ .‬تابع ‪ pop‬برای حذف داده از پشته‬
‫است و ‪ push‬برای اضافه کردن داده به پشته است ‪ .‬هر بار که ‪ push‬میکنیم یعنی‬
‫‪ Top‬را یک واحد افزایش می دهیم و در خانه ای با آن اندیس مقداری را قرار می دهیم‬
‫‪ .‬زمانی که ‪ pop‬می کنیم مقدار داده باال را بازمی گردانیم و ‪ Top‬را یک واحد کم می‬
‫کنیم ‪ .‬اینگونه است که داده ها فقط از یک سمت حذف و درج می شوند ‪ .‬تابعی دیگر به‬
‫نام ‪ stackTop‬در این کالس دیده می شود که شبیه تابع ‪ pop‬است ‪ .‬اما اشاره گر‬
‫‪ Top‬را جابجا نمی کند ‪ .‬این تابع به ما کمک می کند تا مقدار داده باال را بدانیم ولی‬
‫آن را ‪ Pop‬نکنیم ‪ .‬تنها نکته باقی مانده نوع داده ی ‪ Object‬است که در اینجا استفاده‬
‫شده است ‪ .‬این نوع داده در زبان جاوا میتواند همه انواع داده از جمله ‪, double , int‬‬
‫‪ char , String‬و ‪ ....‬را در خود جای دهد ‪ .‬در جزوه مبانی برنامه نویسی جاوای بنده‬

‫‪53‬‬
‫در درس شی گرایی در مورد پیاده سازی پشته نیز صحبت کرده ام که می توانید با‬
. ‫مراجعه به آن و با کمک درس شی گرایی پیاده سازی پشته را بهتر بفهمید‬

//////////////////////////// target is lifo


public class Stack {
private int top;
private Object[] array;
/////////////////////////////////// constructor default
public Stack()
{
array= new Object[100];
top= -1 ;
}
///////////////////////////// one argument constructor
public Stack(int size)
{
array= new Object[size];
top= -1 ;
}
////////////////////////////////// other methods
//////////////////////////// push

54
public void push(Object obj)
{
array[++top]=obj;
}
//////////////////////////// empty
public boolean isEmpty()
{
if(top == -1)
return true;
else
return false;
}
/////////////////////////// pop
public Object pop() {
if(this.isEmpty())
{
System.out.println("stack is empty");
return false;
}else
{

55
‫;‪Object temp‬‬
‫;]‪temp=array[top‬‬
‫;‪--top‬‬
‫;‪return temp‬‬
‫}‬
‫}‬
‫‪//////////////////////////// stack top‬‬
‫)(‪public Object stacktop‬‬
‫{‬
‫;]‪return array[top‬‬
‫}‬
‫}‬
‫در کالس پشته ای که تعریف کرده ایم یک باگ کوچک به چشم میخورد که دوست دارم‬
‫شما آن را بیابید ‪ .‬با پیدا کردن آن از استاد طاهری نمره ای دریافت کنید !!!‬

‫‪56‬‬
‫‪)۶-۱‬باالنس پرانتز ها‬
‫یکی از ساده ترین کاربرد های پشته تشخیص باالنس بودن پرانتز هاست ‪ .‬مثال در عبارت‬

‫) ‪{ hello‬‬
‫پرانتز و آکوالد با هم همخوانی ندارند ‪ .‬ما می دانیم که برای اینکه یک عبارت باالنس‬
‫باشد پرانتز ها و آکوالد ها و براکت های آن باید با هم همخوانی داشته باشند ‪ .‬مثال به‬
‫تعداد پرانتز های باز پرانتز های بسته نیز داشته باشیم ‪.‬‬

‫برای نوشتن برنامه ای که تشخیص دهد که آیا عبارت ورودی باالنس است یا خیر باید‬
‫از پشته بهره ببریم ‪.‬‬

‫الگوریتم حل این سوال چنین فرآیندی را طی می کند ‪:‬‬

‫باید تک تک کاراکتر های رشته ورودی را بخوانیم ‪.‬‬

‫هر کاراکتر محدود کننده باز یا بسته مانند‬

‫][‬
‫}{‬
‫)(‬
‫را باید مورد بررسی قرار دهیم ‪.‬‬

‫کاراکتر های باز را درون پشته مورد نظر قرار می دهیم ‪.‬‬

‫کاراکتر های بسته را با عنصر باالیی پشته مقایسه می کنیم و اگر معادل باز آن کاراکتر‬
‫در باالی پشته بود با هم دیگر حذف می شوند ‪ .‬به این معنا که آن دو با هم متعادل می‬

‫‪57‬‬
‫شوند ‪ .‬مثال اگر که { را دیدیم که یک کاراکتر بسته است توقع داریم که آخرین عنصری‬
‫که درون پشته قرار گرفته است } باشد که هم جنس ولی باز آن کاراکتر است ‪.‬‬

‫در انتهای پیمایش رشته باید هر کاراکتر با کاراکتری دیگر به اصطالح مچ شده باشد و‬
‫پشته خالی شود ‪.‬‬

‫اگر پشته خالی نبود یعنی هنوز تعدادی کاراکتر هستند که با هیچ چیز مچ نشده اند ‪.‬‬

‫در ادامه یک پروژه میبینید که در زمان اجرای آن از شما یک رشته میگیرد و به شما‬
‫باالنس بودن یا نبودن آن را اعالم میکند ‪ .‬در این پروژه از پشته ی طراحی شده ی‬
‫کتابخانه ‪ java.util‬استفاده شده است ‪.‬‬

‫;‪import java.util.Scanner‬‬
‫;‪import java.util.Stack‬‬
‫**‪/‬‬
‫*‬
‫‪* @author SSD1377‬‬
‫‪*/‬‬
‫{ ‪public class TheMain‬‬

‫{ )‪public static void main(String[] args‬‬


‫;)"‪System.out.println("Type whatever you want ...‬‬
‫;)‪Scanner Input = new Scanner(System.in‬‬
‫;)(‪String Text = Input.nextLine‬‬
‫‪58‬‬
java.util.Stack<Character> Chars = new Stack();
for(int i = 0 ; i<Text.length() ; i++ ){
if(TheMain.isBoundingChar(Text.charAt(i)))
{
/* doubly “and” operator is Important */
if(Chars.size()>0 &&
TheMain.isMatch(Chars.lastElement(), Text.charAt(i))){
Chars.pop();
} else{
Chars.push(Text.charAt(i));
}
}
}
if(Chars.isEmpty()){
System.out.println(“All is balanced . “);
}else{
System.err.println(“All is not balanced .”);
}
}
public static boolean isBoundingChar(char c){

59
return (c==’{‘ || c==’}’ ||c==’[‘ ||c==’]’ ||c==’(‘ ||c==’)’ );
}
public static 60oolean isMatch(char c1 , char c2){
if(c1==’(‘ && c2==’)’){
return true ;
}else if(c1==’{‘ && c2==’}’){
return true ;
}else if(c1==’[‘ && c2==’]’){
return true ;
}else{
return false ;
}
}
}

‫در پروژه باال شرط‬


Chars.size()>0 && TheMain.isMatch(Chars.lastElement(), Text.charAt(i))

‫ اگر بجای استفاده از دو & از یک & استفاده کنید احتمال ایجاد خطا‬. ‫دیده میشود‬
‫ چرا ؟؟؟‬. ‫وجود دارد‬

60
‫‪)۶-۲‬تبدیل نگارش های نوشتاری‬
‫همان طور که از درس ریاضیات گسسته می دانید نگارش نوشتاری که به آن عادت داریم‬
‫و می شناسیم اینفیکس نامیده می شود ‪ .‬مثال می نویسیم ‪ 85 + 69‬ولی نوشتن عبارت‬

‫‪ 85 69 +‬برای ما بیگانه است زیرا با آن آشنایی نداریم در حالی که این دو نگارش‬


‫نوشتاری معادل همان ‪ 85 + 69‬است ‪.‬‬

‫زمانی که عملگر را قبل از عملوند هایش قرار می دهیم به آن نگارش ‪ PreFix‬گفته می‬
‫شود ‪.‬‬

‫زمانی که عملگر را بعد از عملوندهایش قرار می دهیم به آن نگارش ‪ PostFix‬گفته می‬


‫شود ‪.‬‬

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

‫برای اینکه بتوانید این مسائل را حل کنید لطفا برای خودتان مثال هایی بزنید تا بفهمید‬
‫چگونه باید این کار را انجام دهید ‪ .‬در درس توابع بازگشتی نیز همین را توصیه کردم ‪.‬‬
‫یکی از مثال ها میتواند به شکل زیر باشد ‪.‬‬

‫‪PreFix‬‬ ‫‪PostFix‬‬
‫‪- +568‬‬ ‫‪5+6–8‬‬ ‫‪56+8-‬‬
‫برای اینکه بتوانیم این کار را انجام دهیم ابتدا باید بتوانیم از یک عبارت ‪ ,‬عملگر ها و‬
‫عملوند هایش را بیرون بکشیم ‪ .‬تشخیص عملگر ها بسیار ساده است زیرا که عملگرها‬

‫‪61‬‬
‫یک کاراکتری می باشند و قابل پیش بینی اند ‪ ( .‬پیدا کردن عملگر ها در درس طراحی‬
‫کامپایلر ها کاری سخت تر است زیرا که باید انتظار عملگرهای ترکیبی و هر اتفاق دیگری‬
‫را داشته باشیم ‪) .‬‬

‫تشخیص هر عملوند از عملوند دیگر کاری حساس تر است زیرا که عملوند می تواند یک‬
‫رقمی یا همان یک کاراکتری باشد یا بیشتر ‪ .‬این کار زمانی اهمیت دارد که در حال‬
‫پیمایش رشته ورودی باشیم و ندانیم که کدام کاراکترعددی که با آن مواجه هستیم‬
‫تنهاست و یا ارقام دیگری در کنارش وجود دارد ‪ .‬پس زمانی که به یک کاراکترعددی‬
‫رسیدیم تا زمانی که ارقام دیگری بالفاصله در کنارش ببینیم همه آن ها را به عنوان ارقام‬
‫یک عدد در نظر می گیریم ‪.‬‬

‫‪ +‬آقای دالور ‪...‬‬

‫‪ +‬گیجمون نکن ‪.‬‬

‫‪ -‬چشم ‪ .‬حاال مثال می زنم ‪.‬‬

‫فرض کنید که رشته ورودی مثل ‪ 1287 + 6332‬داریم ‪.‬‬

‫برای اینکه بتوانیم عملگرها و عملوند های مسئله که عناصر سوال هستند را بیابیم باید‬
‫از سمت چپ عبارت شروع به پیمایش کاراکترهای رشته کنیم ‪ .‬ابتدا به کاراکترعددی ‪1‬‬
‫میرسیم ‪ .‬اینجا باید شک کنیم که آیا عدد ‪ 1‬را دیدیم یا عددی دیدیم که رقم بزرگ آن‬
‫‪ 1‬است ‪ .‬پس بجای روزه شک دار گرفتن بیایید کاراکتر های بعدی را بخوانیم ‪ .‬اینگونه‬
‫به ترتیب ‪ 2‬و ‪ 8‬و ‪ 7‬را میبینیم که ارقام این عدد هستند ‪ .‬ولی به محض اینکه هر چیزی‬
‫بجز ارقام دیدیم باید متوجه شویم که ارقام آن عدد تمام شده اند و باید به سراغ عملوندی‬
‫دیگر یا عملگر برویم ‪ .‬مثال اگر کاراکتر فضای خالی یا همان اسپیس یا عملگر دیدیم باید‬

‫‪62‬‬
‫عدد پیدا شده را به عنوان یک عملوند ذخیره کنیم ‪ .‬پیدا کردن عملگر هم که آب خوردن‬
‫است ‪ .‬کافیست تا یک کاراکتر دیدیم که جزوه عملگرهای مورد نظر و معتبرمان است آن‬
‫را از متن بیرون بکشیم‪.‬‬

‫تا به اینجا فقط توانستیم که عملگرها و عملوند ها را از رشته ورودی بیرون بکشیم ‪.‬‬

‫از این به بعد باید توجه کنیم که چگونه قرار است چه نگارشی را به چه نگارشی تبدیل‬
‫کنیم ‪ .‬چی گفتم !‬

‫حال تبدیل بعضی نگارشات به هم دیگر را برایتان تشریح میکنم تا بعدا خودتان بتوانید‬
‫سایر نگارش ها را به هم تبدیل کنید ‪.‬‬

‫‪Infix to PostFix‬‬
‫خب بیایید ابتدا نگارش ‪ InFix‬را به ‪ PreFix‬تبدیل کنیم ‪.‬‬

‫برای این کار باید یکی یکی عناصر ( چه عملوند چه عملگر ) را درون پشته بریزیم ‪.‬‬

‫نکته اساسی اینجاست که اگر خواستیم در طی این فرآیند یک عملوند را درون پشته‬
‫بریزیم باید بررسی کنیم که آیا عنصر پایین آن یک عملگر است یا خیر ‪ .‬اگر نبود که کار‬
‫خاصی بجز ‪ push‬کردن نمی کنیم ‪ .‬اگر که زیر عنصر جدید یک عملگر بود باید عمل‬
‫تبدیل نگارش صورت بپذیرد ‪ .‬همچنین دقت کنید که مثال عبارت ‪ + 6323 652‬به‬
‫طور کلی دیگر یک عملوند است و باید مثل یک عملوند با آن رفتار شود ‪.‬‬

‫برای مثال عبارت ‪ 5 + 6 – 8‬را با همین الگوریتم ‪ trace‬می کنیم ‪ .‬دقت کنید که هر‬
‫ستون نشان دهنده وضعیت پشته در هر مرحله است ‪ .‬همچنین وقتی که یک عملوند‬
‫وارد می شود و باالی یک عملگر قرار می گیرد عمل ترکیب چند عنصر اتفاق میفتد ‪.‬‬

‫‪63‬‬
‫‪6‬‬ ‫‪8‬‬
‫‪+‬‬ ‫‪+‬‬ ‫‪-‬‬ ‫‪-‬‬
‫‪5‬‬ ‫‪5‬‬ ‫‪5‬‬ ‫‪+56‬‬ ‫‪+56‬‬ ‫‪+56‬‬ ‫‪- +5 6 8‬‬

‫‪InFix to PostFix‬‬
‫برای تبدیل ‪ InFix‬به ‪ PostFix‬تقریبا همانند ‪ InFix‬به ‪ PreFix‬عمل می کنیم با این‬
‫تفاوت که عملگر را به بعد از عملوندهایش انتقال می دهیم ‪ .‬مثال قبل را بیاد بیاورید تا‬
‫این بار نیز آن را با این الگوریتم به ‪ PostFix‬تبدیل کنیم ‪.‬‬

‫‪6‬‬ ‫‪8‬‬
‫‪+‬‬ ‫‪+‬‬ ‫‪-‬‬ ‫‪-‬‬
‫‪5‬‬ ‫‪5‬‬ ‫‪5‬‬ ‫‪56+‬‬ ‫‪56+‬‬ ‫‪56+‬‬ ‫‪5 6 + 8-‬‬

‫در ادامه پروژه ای ضمیمه کردم که نگارش های نوشتاری را به هم تبدیل میکند ‪.‬‬

‫;‪import java.util.Scanner‬‬
‫;‪import java.util.Stack‬‬
‫{ ‪public class Formats‬‬
‫‪64‬‬
public static void main(String[] args) {
System.out.println("1 . infixToPostfix\n"
+ "2 . infixToPrefix\n"
+ "3 . prefixToInfix\n"
+ "4 . postfixToInfix\n");
Scanner input = new Scanner(System.in);
byte answer = input.nextByte();
switch(answer){
case 1 :
infixToPostfix();
break;
case 2 :
infixToPrefix();
break;
case 3 :
prefixToInfix();
break;
case 4 :
postfixToInfix();
break;

65
default:
System.out.println("Wrong input");
}
}
public static void infixToPostfix(){
System.out.println("Type Whatever you want ...");
Scanner Input = new Scanner(System.in);
String Text = Input.nextLine();
java.util.Stack<String> Elements = new Stack();

for(int i = 0 ; i<Text.length() ; i++ ){


if(Formats.isDigit(Text.charAt(i)))
{
String number = "";
for( ; i<Text.length() ; i++ ){
if(Formats.isDigit(Text.charAt(i))){
number+=Text.charAt(i);
}
else{
break;

66
}
}
if( !Elements.isEmpty() &&
isOperator(Elements.lastElement().charAt(0))){
String operator = Elements.pop();
String operand1 = Elements.pop();
String operand2 = number ;
Elements.push(operand1 +" "+ operand2 + " " +
operator );
}else{
Elements.push(number);
}
//backtrack
i--;
System.out.println(number);
}else if(isOperator(Text.charAt(i))){
System.out.println(Text.charAt(i));
Elements.push(""+Text.charAt(i));
}
}
System.out.println(Elements.pop());
67
}
public static void infixToPrefix(){
System.out.println("Type Whatever you want ...");
Scanner Input = new Scanner(System.in);
String Text = Input.nextLine();
java.util.Stack<String> Elements = new Stack();

for(int i = 0 ; i<Text.length() ; i++ ){


if(Formats.isDigit(Text.charAt(i)))
{
String number = "";
for( ; i<Text.length() ; i++ ){
if(Formats.isDigit(Text.charAt(i))){
number+=Text.charAt(i);
}
else{
break;
}
}

68
if( !Elements.isEmpty() &&
isOperator(Elements.lastElement().charAt(0))){
String operator = Elements.pop();
String operand1 = Elements.pop();
String operand2 = number ;
Elements.push(operator +" "+ operand1 +" "+
operand2);
}else{
Elements.push(number);
}
//backtrack
i--;
System.out.println(number);
}else if(Formats.isOperator(Text.charAt(i))){
System.out.println(Text.charAt(i));
Elements.push(""+Text.charAt(i));
}
}
System.out.println(Elements.pop());
}
public static void prefixToInfix(){
69
System.out.println("Type Whatever you want ...");
Scanner Input = new Scanner(System.in);
String Text = Input.nextLine();
java.util.Stack<String> Elements = new Stack();

for(int i = 0 ; i<Text.length() ; i++ ){


if(Formats.isDigit(Text.charAt(i)))
{
String number = "";
for( ; i<Text.length() ; i++ ){
if(Formats.isDigit(Text.charAt(i))){
number+=Text.charAt(i);
}
else{
break;
}
}
if( !Elements.isEmpty() &&
isDigit(Elements.lastElement().charAt(0))){
String operand1 = Elements.pop();

70
String operator = Elements.pop();
String operand2 = number ;
Elements.push( operand1 +" "+operator +" "+
operand2);
}else{
Elements.push(number);
}
//backtrack
i--;
System.out.println(number);
}else if(Formats.isOperator(Text.charAt(i))){
System.out.println(Text.charAt(i));
Elements.push(""+Text.charAt(i));
}
}
System.out.println(Elements.pop());
}
public static void postfixToInfix(){
System.out.println("Type Whatever you want ...");
Scanner Input = new Scanner(System.in);

71
String Text = Input.nextLine();
java.util.Stack<String> Elements = new Stack();

for(int i = 0 ; i<Text.length() ; i++ ){


if(Formats.isDigit(Text.charAt(i)))
{
String number = "";
for( ; i<Text.length() ; i++ ){
if(Formats.isDigit(Text.charAt(i))){
number+=Text.charAt(i);
}
else{
break;
}
}
Elements.push(number);
//backtrack
i--;
System.out.println(number);
}else if(Formats.isOperator(Text.charAt(i))){

72
System.out.println(Text.charAt(i));
if( !Elements.isEmpty() &&
isDigit(Elements.lastElement().charAt(0))){
String operand2 = Elements.pop();
String operand1 = Elements.pop();
char operator = Text.charAt(i) ;
Elements.push( operand1 +" "+operator +" "+
operand2);
}else{
Elements.push(""+Text.charAt(i));
}
}
}
System.out.println(Elements.pop());
}
public static boolean isDigit(char c){
return
c=='1'||c=='2'||c=='3'||c=='4'||c=='5'||c=='6'||c=='7'||c=='8'|
|c=='9';
}
public static boolean isOperator(char c){

73
return c=='+'||c=='-'||c=='*'||c=='/'||c=='%';
}
}

74
‫‪)۷‬صف(‪)Queue‬‬
‫برخالف پشته که درج و حذف از یک طرف انجام می پذیرد در صف درج از انتها و حذف‬
‫از ابتدا صورت می گیرد ‪ .‬در صف به دو اشاره گر به نام های ‪ front‬و ‪ rear‬نیاز داریم‬
‫که به ترتیب به ابتدای صف و انتهای صف اشاره دارند ‪ .‬برای درک بهتر صف بیایید صف‬
‫نانوایی را تصور کنید ‪ .‬هر کس که به صف نانوایی اضافه می شود انتها قرار می گیرد و‬
‫هر کس نانش را می گیرد و می خواهد مرخص شود از ابتدای صف خارج می شود ‪.‬‬

‫‪)۷-۱‬صف ساده یا خطی(‪)Linear Queue‬‬


‫صف ساده به علت سادگی راحت تر درک می شود اما ایراداتی نیز دارد برای مثال در‬
‫صف ساده ممکن است حالتی پیش آید که خانه های خالی حافظه وجود داشته باشند‬
‫اما به علت سوء مدیریت اشاره گر ها تصور شود که صف پر است ‪.‬‬

‫بیایید فرض کنیم یک صف با ظرفیت چهار خانه داریم ‪ .‬در هر مرحله یک عنصر را به‬
‫صف می افزاییم ‪ .‬در ابتدا که صف خالیست ‪ rear‬و ‪ front‬برابر ‪ -1‬هستند ‪ .‬هرگاه با‬
‫کمک تابع ‪ insert‬یک عنصر به این صف بیافزاییم ‪ rear‬یک واحد اضافه می شود ‪.‬‬

‫اینگونه ‪ rear‬هر بار با درج عناصر یک واحد افزوده می شود تا بدانیم آخرین عنصر این‬
‫صف کجا قرار گرفته است ‪( .‬هر سطر جدول زیر نماینده یک حالت از صف است )‬

‫‪a‬‬
‫‪a‬‬ ‫‪b‬‬
‫‪a‬‬ ‫‪b‬‬ ‫‪c‬‬
‫‪a‬‬ ‫‪b‬‬ ‫‪c‬‬ ‫‪d‬‬

‫‪75‬‬
‫زمانی که تمام خانه های صف پر شد دیگر نمی توانیم عنصری بیفزاییم ‪ .‬اما تصور کنید‬
‫که طی چند مرحله عناصر ابتدای صف حذف شده اند ‪.‬‬

‫‪b‬‬ ‫‪c‬‬ ‫‪d‬‬


‫‪c‬‬ ‫‪d‬‬
‫‪d‬‬

‫در طی حذف عناصر تعدادی خانه خالی می شود با وجود این خانه های خالی به علت‬
‫آن که اشاره گر ‪ rear‬در انتهای آرایه قرار گرفته است دیگر نمیتوان عنصری را اضافه‬
‫کرد ‪ .‬از همین رو به سراغ پیادی سازی صف حلقوی می رویم تا این استثنا دیگراتفاق‬
‫نیوفتد ‪ .‬اما پیش از اینکه به سراغ صف حلقوی برویم بهتر است تا با مثال نشان دهیم‬
‫دو اشاره گر صف با عملیات درج و حذف چه تغییر میکنند ‪.‬‬

‫صف زیر را در نظر بگیرید که در ابتدا خالیست ‪( .‬اندیس ها از صفر و از چپ هستند )‬

‫در زمان خالی بودن صف هر دو اشاره گر صف برابر ‪ -1‬هستند ‪.‬‬

‫تصور کنید که میخواهیم عنصر ‪ A‬را در صف درج کنیم ‪.‬‬

‫‪A‬‬
‫در این حالت فقط ‪ Rear‬یک واحد بیشتر شده و مقدار ‪ 0‬را به خود میگیرد ‪.‬‬

‫حال اگر بطور مثال عنصر ‪ B‬را اضافه کنیم مجددا ‪ Rear‬است که عوض میشود ‪.‬‬

‫‪A‬‬ ‫‪B‬‬

‫‪76‬‬
‫با درج عنصر‪ B‬اشاره گر ‪ Rear‬برابر ‪ 1‬میشود ‪ .‬به همین ترتیب اگر عنصری مثل ‪ C‬را‬
‫درج کنیم اشاره گر ‪ Rear‬برابر ‪ 2‬میشود ‪.‬‬

‫‪A‬‬ ‫‪B‬‬ ‫‪C‬‬


‫اکنون اگر قصد حذف داشته باشیم باید ابتدا ‪ A‬را حذف کنیم ‪ .‬با حذف عناصر در صف‬
‫اینبار ‪ Front‬تغییر میکند و افزایش پیدا میکند ‪.‬‬

‫‪B‬‬ ‫‪C‬‬
‫با حذف عنصر ‪ A‬اشاره گر ‪ Front‬یک واحد افزایش میابد و برابر ‪ 0‬میشود ‪.‬‬

‫حال اگر بخواهیم عنصر بعدی جلوی صف که ‪ B‬است را حذف کنیم مثل قبل اشاره گر‬
‫برابر ‪ 1‬میشود ‪.‬‬

‫‪C‬‬
‫نتیجه اینکه با درج‪,‬اشاره گر ‪ Rear‬و با حذف‪,‬اشاره گر‪ Front‬یک واحد افزایش میابد‪.‬‬

‫{‪public class Queue‬‬


‫;‪public Object[] Array‬‬
‫;‪public int Size,Front,Rear‬‬
‫‪////////////////////////////////////// constructor‬‬
‫)‪public Queue(int size‬‬
‫;‪{ Front=-1‬‬
‫;‪Rear=-1‬‬
‫;‪Size=size‬‬
‫‪77‬‬
Array=new Object[Size];
}
/////////////////////////////// insert
public void insert(Object item)
{
if(Rear==Size-1)
{
System.out.println("Queue is already full");
}
else
{
Array[++Rear]=item;
}
}
////////////////////////////////////// remove
public Object remove()
{
if(isEmpty())
System.out.println("Queue is empty"); {
return null;}

78
else
return Array[++Front];

}
public int size() {
return Rear-Front+1;
}

///////////////////////////// is empty
public boolean isEmpty()
{
if(Front==Rear)
return true;
else
return false;
}
public Object[] getArray() {
return Array;
}
}
79
‫حلقوی ( ‪)Circular Queue‬‬ ‫‪)۷-۲‬صف‬
‫در پیاده سازی صف حلقوی دستورات به گونه ای پیاده سازی شده اند که در صورت‬
‫رسیدن اشاره گر‪ rear‬به انتهای آرایه و وجود خانه خالی در ابتدای آرایه دیگر صف‬
‫پرشده به نظر نمی رسد و باز هم می توانیم عناصر را اضافه کنیم ‪ .‬این خاصیت در صف‬
‫خطی وجود نداشت ‪.‬‬

‫{ ‪public class CircularQueue‬‬


‫;‪public Object[] Array‬‬
‫;‪public int Size,Front,Rear‬‬
‫‪////////////////////////////////////// Constructor‬‬
‫)‪public CircularQueue(int size‬‬
‫{‬

‫‪80‬‬
Front=-1;
Rear=-1;
Size=size;
Array=new Object[Size];
}
/////////////////////////////// insert
public void insert(Object item)
{
Rear=(Rear+1)%Size;
if(Rear==Front)
{
System.out.println("Queue is already full");
}
else
{
Array[Rear]=item;
}
}
////////////////////////////////////// remove
public Object remove()

81
{
if(Front==Rear )
System.out.println("Queue is empty"); {
return null;}
else
{
Front=(Front+1)%Size;
return Array[Front];
}
}
public int size() {
return Rear-Front+1;
}

///////////////////////////// is empty
public boolean isFull()
{
if((Rear==Size && Front==1)||((Rear<Size-
1)&&(Front==Rear+1)))
return true;

82
else
return false;
}
public Object[] getArray() {
return Array;
}
}

83
‫از آنجا که در درس ریاضیات گسسته در مورد گراف ها آموختید در ادامه در نظر می‬
‫گیریم که با اصطالحاتی مثل گره ‪ ,‬یال و ‪ ...‬آشنایی دارید و بر این اساس درس ساختمان‬
‫داده درخت را شروع می کنیم ‪.‬‬

‫‪)۸‬درخت (‪)Tree‬‬
‫درخت مجموعه محدودی از یک یا چند گره می باشد که دارای گره خاصی به نام ریشه‬
‫است و بقیه گره ها به مجموعه مجزا تقسیم می شوند که هر یک از مجموعه ها خود نیز‬
‫یک درخت است ‪ .‬یک نمونه درخت را در ادامه می بینید ‪ .‬همانطور که میبینید گره ای‬
‫که در درس لیست پیوندی دیدید کمی با گره ای که ما در اینجا با آن سر و کار داریم‬
‫متفاوت است هرچند که این گره ها به کمک مفاهیمی که در درس لیست پیوندی‬
‫خواندید قابل پیاده سازی اند ‪ .‬در ادامه به این پیاده سازی خواهیم پرداخت ‪.‬‬

‫تعاریف اساسی در رابطه با درخت به شکل زیر است ‪.‬‬

‫گره ریشه ‪ :‬گره ای که بی پدر است !‬

‫گره برگ ‪ :‬گره بدون فرزند ‪.‬‬

‫گره داخلی ‪ :‬گره ای که نه برگ است نه ریشه ‪.‬‬

‫‪84‬‬
‫گره های برادر ‪ :‬گره هایی با یک پدر ‪.‬‬

‫توجه کنید که در درخت حلقه )‪ (cycle‬وجود ندارد ‪.‬‬

‫ارتفاع گره ‪ :‬طول بزرگترین مسیر از آن گره به برگ‬

‫ارتفاع درخت ‪ :‬ارتفاع ریشه‬

‫عمق ( سطح ) گره ‪ :‬طول مسیری از ریشه به آن گره‬

‫‪85‬‬
‫‪)۸-۱‬درخت مرتب ‪ :‬درختی که ترتیب فرزندان هر گره مشخص است ‪.‬‬

‫‪)۸-۲‬درخت دودویی )‪ : (Binary tree‬درخت مرتبی که هر عنصر آن حداکثر دو‬


‫فرزند (فرزند راست یا چپ) داشته باشد ‪.‬‬

‫‪86‬‬
‫‪)۸-۳‬درخت دودویی پر‪ :‬درختی که در آن هر گره به غیر از برگ ها دارای دو فرزند‬
‫است‪.‬‬

‫در شکل باال تعداد گره ها ‪ 31‬و ارتفاع درخت ‪ 4‬است ‪.‬‬

‫اگر در مورد درخت دودویی پر تعداد گره ها را ‪ n‬و ارتفاع را ‪ h‬در نظر بگیریم ‪:‬‬

‫تعداد برگ ها برابر ‪ 2ℎ‬است ‪.‬‬

‫تعداد گره ها ‪ 𝑛 = 2ℎ+1 − 1‬است ‪.‬‬


‫𝑛‬
‫است ‪.‬‬ ‫تعداد گره های داخلی برابر جزء صحیح‬
‫‪2‬‬

‫اگر این اعداد و ارقام را به خوبی بررسی کنید به خوبی می فهمید که چرا انیشتین تصاعد‬
‫را یکی از شگفتی های جهان می نامد ‪.‬‬

‫شما می توانید با کمک لیست پیوندی درخت ها را پیاده سازی کنید ‪ .‬فقط برای این کار‬
‫کافی است گره های لیست پیوندی شما سه فیلد داده ‪ ,‬اشاره گر راست و اشاره گر چپ‬
‫داشته باشد ‪.‬‬

‫‪87‬‬
‫بطور مثال درخت زیر را در نظر بگیرید ‪.‬‬

‫درخت باال را میتوان با لیست پیوندی به شکل زیر پیاده سازی کرد ‪.‬‬

‫‪88‬‬
‫‪)۸-۴‬پیمایش درخت دودویی‬
‫پیمایش درخت ‪ ,‬یعنی حرکت روی یالهای درخت ومالقات همه گره های آن دقیقا یکبار‪.‬‬

‫برای پیمایش درخت دودویی ‪ ,‬سه روش متداول وجود دارد ‪.‬‬

‫‪ )1‬پیشوندی )‪( (VLR‬ریشه – چپ – راست ) ‪Preorder‬‬


‫‪ )2‬میانوندی )‪( (LVR‬چپ – ریشه – راست ) ‪Inorder‬‬
‫‪ )3‬پسوندی )‪( (LRV‬چپ – راست – ریشه ) ‪Postorder‬‬

‫همانطور که از درس ریاضیات گسسته به یاد دارید پیمایش ها به صورتی که در تصویر‬


‫زیر دیده میشود اتفاق میافتند ‪.‬‬

‫برای اینکه مجددا یادآوری کنیم بطور مثال روش پیشوندی را شرح میدهیم ‪ .‬اینگونه‬
‫شما روش های دیگر را هم متوجه می شوید ‪ .‬همان طور که فهمیدید این روش ابتدا‬
‫ریشه را مالقات می کند ‪ .‬پس در این این روش ابتدا ‪ J‬را می بینیم ‪ .‬سپس فرزندان‬
‫چپ را مالقات می کند ‪ .‬وقتی سمت چپ را بررسی می کند مجددا به درختی می رسد‬
‫‪ .‬این درخت جدید که در شکل به رنگ سبز است را مجددا به شکل پیشوندی پیمایش‬
‫می کند ‪ .‬یعنی ابتدا ‪ E‬را می پیماید ‪ .‬سپس فرزند چپ را که ‪ A‬است میپیماید ‪ .‬سپس‬
‫‪89‬‬
‫به سراغ فرزند راست ‪ E‬که ‪ H‬است می رود ‪ .‬حال که سمت چپ ‪ J‬پیمایش شد به سراغ‬
‫راست می رویم ‪ .‬مجددا به درخت جدیدی که با رنگ آبی نشان داده شده است می رسیم‬
‫‪ .‬آن را نیز به شکل پیشوندی پیمایش می کنیم ‪ .‬یعنی ابتدا ‪ T‬را که ریشه است مالقات‬
‫می کنیم سپس فرزند چپ که ‪ M‬است و در انتها فرزند راستش که ‪ Y‬است ‪.‬‬

‫سایر روش های پیمایش را به دقت بررسی کنید ‪ .‬در ادامه مثالی گسترده تر برای شما‬
‫قرار داده شده که می توانید از روی آن تمرین کنید ‪.‬‬

‫برای طراحی توابعی که این درخت ها را پیمایش می کند می توانید از درس توابع‬
‫بازگشتی الهام بگیرید ‪ .‬به این صورت که ما می دانیم در هر مرحله از پیمایش همان‬
‫الگوریتم را صدا می زنیم ‪ .‬یعنی اگر که از روش میانوندی بهره می بریم در هر مالقات‬
‫مجددا همان الگوریتم میانوندی را صدا می زنیم ‪.‬‬
‫{ )‪public static void preOrder(Node n‬‬
‫{ )‪if(n==null‬‬
‫; ‪return‬‬
‫}‬
‫‪90‬‬
System.out.println(n.Data);
preOrder(n.Left);
preOrder(n.Right);
}
public static void inOrder(Node n) {
if(n==null) {
return ;
}
inOrder(n.Left);
System.out.println(n.Data);
inOrder(n.Right);
}
public static void postOrder(Node n) {
if(n==null) {
return ;
}
postOrder(n.Left);
postOrder(n.Right);
System.out.println(n.Data);
}

‫ دقت کنید‬. ‫کد باال شامل سه تابع است که پیمایش های مختلف را پیاده سازی کرده اند‬
‫ این بدین معناست که کالس گره‬. ‫که از دو اشاره گر راست و چپ استفاده شده است‬
Right ‫ و‬Left ‫مورد استفاده در اینجا بجای داشتن یک اشاره گر دو اشاره گر به نام های‬
. ‫دارد‬

91
‫‪)۸-۵‬درخت عبارت‬
‫یکی از کاربرد های درخت دودویی تفسیر ترتیب محاسبه عبارت ها ست ‪ .‬این گونه مثال‬
‫ها در درس طراحی کامپایلرها کاربرد دارد ‪ .‬تصور کنید که میخواهید عبارت زیر را‬
‫محاسبه کنید ‪.‬‬

‫)‪A-(C/5*2)+(D*5%4‬‬
‫میدانیم که ابتدا ‪ C‬را بر ‪ 5‬تقسیم میکنیم ‪ .‬سپس نتیجه را در ‪ 2‬ضرب میکنیم و ‪. ...‬‬
‫این ترتیب اعمال عملگرها روی عملوند ها را میتوان به شکل درخت دودویی نشان داد‬
‫که برگ های آن عملوند ها هستند و سایرین عملگرند ‪.‬‬

‫با دقت در درخت باال میبیند که عملگرهای با اولویت بیشتر پایین تر از عملگرهای با‬
‫اولویت کمتر قرار دارند ‪ .‬پس برای محاسبه ابتدا باید برگ ها را محاسبه کرد ‪.‬‬

‫اگر درخت عبارت را به روش های مختلف پیمایش کنید متوجه می شوید که ‪:‬‬

‫پیمایش ‪ inorder‬یک درخت عبارت ‪ ,‬نگارش ‪ infix‬را تولید می کند ‪.‬‬

‫پیمایش ‪ preorder‬یک درخت عبارت ‪ ,‬نگارش ‪ prefix‬را تولید می کند ‪.‬‬

‫پیمایش ‪ postorder‬یک درخت عبارت ‪ ,‬نگارش ‪ postfix‬را تولید می کند ‪.‬‬

‫‪92‬‬
‫در درس طراحی کامپایلر ها باید بررسی کنیم که آیا یک عبارت میتواند چند درخت‬
‫معادل داشته باشد یا خیر ‪ .‬در یک زبان اگر بتوان برای یک عبارت بیش از یک درخت‬
‫عبارت رسم کرد آن زبان مبهم است ‪ .‬این بدان معناست که زبانی که داریم برای آن‬
‫کامپایلر طراحی می کنیم می تواند جمالتی تولید کند که دو پهلو هستند و می توانند‬
‫به چند شکل تفسیر شود ‪.‬‬

‫‪)۸-۶‬درخت دودوئی کامل‬


‫هر سطح ‪ ,‬به جز احتماال آخرین سطح ‪ ,‬به طور کامل پر شده است و همه گره ها تا‬
‫جایی که ممکن است در چپ درخت قرار می گیرند ‪.‬‬

‫در ادامه یک نمونه درخت دودوئی کامل و غیر کامل می بینید ‪.‬‬

‫‪93‬‬
‫‪)۸-۷‬درخت جستجوی دودویی )‪) Binary Search Tree( (BST‬‬

‫درختی درخت ‪ BST‬است که عناصر زیر درخت زیر درخت راست ‪ ,‬بزرگ تر از ریشه‬
‫باشند و عناصر زیر درخت چپ ‪ ,‬کوچک تر از ریشه باشند ‪ .‬زیر درختان چپ و راست ‪,‬‬
‫درختان جستجوی دودویی باشند ‪ .‬همچنین مراقب باشید که گره با عنصر تکراری در‬
‫‪ BST‬نداریم ‪.‬‬

‫الزاما ‪ BST‬از جنس اعداد نیست ‪ .‬مثال ‪:‬‬

‫‪94‬‬
‫‪)۸-۸‬هرم (‪)Heap‬‬

‫حال با یک نوع درخت به نام ‪ Heap‬یا همان هرم آشنا می شویم ‪.‬‬

‫هرم ها دو نوعند ‪.‬‬

‫‪Max-Heap .1‬‬
‫یک درخت دودوئی کامل که مقدار کلید هر گره آن بزرگتر یا مساوی مقدار‬
‫کلیدهای فرزندانش باشد ‪.‬‬

‫‪Min-Heap .2‬‬
‫یک درخت دودوئی کامل که مقدار کلید هر گره آن کوچکتر یا مساوی مقدار‬
‫کلیدهای فرزندانش باشد ‪.‬‬

‫‪95‬‬
‫حال سوال اینجاست که درج در هرم چگونه است ؟‬

‫همانطور که دیدید رابطه ای بین هر گره پدر با فرزندان چپ و راستش وجود دارد ‪ .‬حال‬
‫بیایید تا یک مثال بزنیم تا متوجه شوید که چگونه میتوان با در نظر گرفتن روابط بین‬
‫گره ها درج را در جای درستی انجام داد ‪.‬‬

‫در این مثال میخواهیم عنصر ‪ 5‬را در هرم جای دهیم ‪ .‬از آنجا که هرم یک درخت کامل‬
‫است باید درج در سمت چپ ترین قسمت قرار گیرد ‪ .‬اما خب چون هرم باال یک هرم‬
‫کمینه )‪ (Min Heap‬است و ‪ 35‬که گره پدر است از ‪ 5‬بیشتر است پس باید جای این‬

‫‪96‬‬
‫دوعنصر عوض شود ‪ .‬اما باز هم ‪ 6‬که این بار پدر ‪ 5‬است از آن بیشتر است باید جای ‪5‬‬
‫و ‪ 6‬را نیز عوض کنیم ‪ .‬در انتها چون پدر گره ‪ 5‬که گره ‪ 3‬است از آن کمتر است این‬
‫جابجایی را متوقف می کنیم و اینگونه مراحل درج به پایان می رسد ‪ .‬به این مراحل‬
‫‪ Reheap‬گفته میشود ‪.‬‬

‫‪)۸-۹‬صف اولویت )‪(Priority Queue‬‬


‫همان طور که درباره صف می دانید داده ها به ترتیب خاصی خدمات رسانی می شوند ‪.‬‬
‫در صف معمولی هر داده که زودتر وارد شده است زودتر خارج می شود )‪ . (FIFO‬اما در‬
‫صف اولویت بعضی داده ها با پارتی بازی از سایرین جلو می زنند ! اشتباه نکنید ‪ .‬این‬
‫پارتی بازی عدالت را زیر سوال نمیبرد ‪ .‬بلکه از آن پارتی هایی است که همه مان استفاده‬
‫کرده ایم و میکنیم ‪.‬‬

‫مثال در بیمارستان اولویت با بیماران قلبی یا تصادفی یا کرونایی است نه بیمارانی که‬
‫میخواهد عمل زیبایی انجام دهند ! هر چند که او از همه زودتر در مطب حاضر شده‬
‫باشد‪.‬‬

‫در درس سیستم عامل که با آن بیشتر آشنا می شوید می آموزید که بعضی از برنامه ها‬
‫اولویت باالتری نسبت به سایر برنامه ها در استفاده از ‪ CPU‬و سایر منابع سیستم مانند‬
‫حافظه و لوازم جانبی دارند ‪.‬‬

‫در مبحث صف اولویت هر داده اولویت خاص خود را دارد ‪ .‬عنصری که باالترین اولویت‬
‫را دارد زودتر به آن سرویس داده می شود و حذف می گردد ‪.‬‬

‫‪97‬‬
‫پس صف اولویت دو نوع است ‪:‬‬

‫‪ .1‬صف اولویت بیشینه )‪(max-priority queue‬‬


‫‪ .2‬صف اولویت کمینه )‪(min-priority queue‬‬
‫حال بیایید صف اولویت را پیاده سازی کنیم ‪.‬‬

‫تصور کنید تعدادی دانش آموز با رتبه های مختلف برای دریافت جایزه پشت در اتاق‬
‫رئیس آموزش و پرورش نشسته اند ‪ .‬از آنجا که اولین جایزه برای دانش آموز رتبه یک‬
‫است پس اول او باید وارد اتاق رئیس شود ‪ ( .‬در درس سیستم عامل اتاق مدیر تشبهی‬
‫از ‪ CPU‬است )‬

‫این اولویت بندی برای ورود افراد بر اساس اولویتشان به سه حالت امکان پذیر است ‪.‬‬

‫‪ .1‬با استفاده از آرایه نامرتب‬


‫در این حالت داده ها بدون نظم خاصی در کنار هم قرار می گیرند ‪ .‬تصور کنید‬
‫دانش آموزان بدون در نظر گرفته شدن رتبه شان روی صندلی های یک تا ‪N‬‬
‫بنشینند ‪ .‬در این حالت درج داده ها آسان است زیرا که هیچ قانونی وجود ندارد و‬
‫داده به راحتی در انتها قرار می گیرد ‪ .‬اما اگر یک داده حذف شود سایر داده ها‬
‫باید برای پرکردن فضای خالی آن شیفت پیدا کنند ‪ .‬این مسئله پیچیدگی حذف‬
‫داده ها را )‪ O(n‬میکند ‪.‬‬
‫‪ .2‬با استفاده از آرایه مرتب‬
‫در این حالت هر داده که وارد میشود بر اساس اولویت نسبت به سایرین قرار می‬
‫گیرد ‪ .‬تصور کنید که دانش آموز رتبه یک هنگام ورود با غرور به شخصی که روی‬
‫صندلی اول نشسته بگویید که این جای من است و تو باید جای خودت بنشینی !‬

‫‪98‬‬
‫خب از همین االن مشخص شد که غرور دانش آموز های بیش از حد درس خون‬
‫باعث می شود که پیچیدگی درج داده ها )‪ O(n‬شود ‪.‬‬
‫‪ .3‬با استفاده از آرایه نیمه مرتب )‪(Heap‬‬
‫در درس هرم یاد گرفتیم که درج عناصر می تواند تا حدودی قانونمند باشد ‪ .‬نکته ای که‬
‫باعث شده از هرم در پیاده سازی صف اولویت استفاده کنیم این است که درج و حذف‬
‫به کمک هرم هر دو پیچیدگی )‪ O(logn‬را دارند ‪ .‬این پیچیدگی لگاریتمی نشان دهنده‬
‫مزیت این روش برای پیاده سازی صف اولویت است ‪.‬‬

‫‪99‬‬
‫‪)۹‬درهم سازی(‪)Hashing‬‬
‫هزینه اعمال " جست و جو ‪ ,‬درج و حذف " بر روی مجموعه ای ‪ n‬عنصری در ساختمان‬
‫داده ها لیست ها یا ‪ BST‬در بدترین حالت )‪ o(n‬و بسیار کند است ‪ .‬البته با بهبود درخت‬
‫دودویی جستجو این اعمال در بدترین حالت در )‪ o(log n‬انجام می شود ‪ .‬با استفاده از‬
‫ساختمان داده جدول درهم سازی و روش درهم سازی ‪ ,‬هزینه هر کدام از اعمال ‪ ,‬در‬
‫بدترین حالت و نیز در حالت میانگین می تواند )‪ o(1‬شود ‪.‬‬

‫در مسائله درهم سازی می خواهیم یک فایل یا ‪ n‬رکورد که کلیدهای غیر تکراری دارند‬
‫را در یک جدول با ‪ m‬خانه در حافظه ذخیره کنیم ‪.‬‬

‫از کاربرد های درهم سازی میتوان به تخصیص حافظه در درس سیستم های عامل نام‬
‫برد ‪.‬‬

‫فاکتور لود ( ضریب باردهی )‬

‫نسبت ‪ n/m‬را فاکتور لود می گویند ‪ .‬یعنی نسبت تعداد رکوردهای فایل به تعداد‬
‫آدرسهای درهم ساز ‪ .‬این نسبت را با 𝜆 نمایش می دهند ‪.‬‬

‫‪100‬‬
‫‪)۹-۱‬توابع درهم سازی (‪)Hash Functions‬‬
‫توابع در هم سازی به طور کلی توابعی هستند که با دریافت کلید یک عنصر مکان قرار‬
‫گرفتن آن عنصر را در جدول در هم سازی به ما تحویل می دهند ‪.‬‬

‫‪Key‬‬ ‫‪Hash Function‬‬ ‫‪Address‬‬

‫خب بیایید مثال بزنیم ‪. . .‬‬

‫فکر کنید که بوی نامطبوع فصل امتحانات می آید ‪ .‬همان طور که می دانید هر دانشجو‬
‫سر جلسه امتحان صندلی مخصوص به خود را دارد ‪ .‬برای مثال صندلی شماره ‪8569‬‬
‫برای آقای بهبود در جلسه امتحان ساختمان داده ها در نظر گرفته شده است ‪ .‬خب سوال‬
‫اینجاست که چگونه جایگاه تعداد زیاد افراد در امتحانات سازمان دهی می شود ؟؟؟‬

‫برای درک این مسئله یک کالس را در نظر بگیرید که قرار است هر کس روی صندلی‬
‫خاص خود بنشیند ‪.‬‬

‫‪ -‬میدانیم که دو نفر نمیتوانند روی یک صندلی بنشینند ‪.‬‬


‫‪ -‬تعداد افرادی که خواستار صندلی هستند باید کمتر مساوی گنجایش کالس‬
‫باشد ‪.‬‬
‫‪ -‬این سازماندهی نباید سلیقه ای باشد ‪.‬‬
‫‪ -‬معیار تحویل صندلی به هر شخص طوری باشد که در انتهای مسئله همه راضی‬
‫باشند ‪.‬‬

‫‪101‬‬
‫‪ -‬هر کسی یک کلید اختصاصی مانند کد ملی یا شماره دانشجویی دارد ‪.‬‬

‫حال که مسئله را متوجه شدید بیایید کمی تخصصی تر این مبحث را دنبال کنیم ‪ .‬اگر‬
‫ما جدولی به شکل زیر به نام ‪ T‬داشته باشیم باید به کمک تابع درهم سازی تمام عناصر‬
‫ورودی را درون خانه های جدول قرار دهیم ‪ .‬جدول زیر یک جدول با ‪ m‬تا ظرفیت است‬
‫‪ .‬اندیس های آن از ‪ 0‬تا ‪ m – 1‬است ‪( .‬دقیقا مثل آرایه در جاوا)‬

‫‪0‬‬
‫‪1‬‬
‫‪2‬‬
‫‪3‬‬
‫‪4‬‬
‫‪5‬‬
‫‪.‬‬
‫‪.‬‬
‫‪.‬‬
‫‪.‬‬
‫‪m–1‬‬

‫یکی از ساده ترین روش ها برای درهم سازی روش باقی مانده است ‪ .‬یعنی هر‬
‫کلید را بر ظرفیت جدول تقسیم می کنیم و عدد به دست آمده اندیس جایگاه‬
‫عنصر است ‪ .‬مثال اگر یک جدول با ظرفیت ‪ 10‬تا خانه داشته باشیم و یک عنصر‬
‫با کلید ‪ 69‬داشته باشیم باقی مانده تقسیم ‪ 69‬به ‪ 10‬برابر ‪ 9‬است ‪ .‬بنابراین عنصر‬

‫‪102‬‬
‫ورودی باید درون خانه ی با اندیس ‪ 9‬قرار گیرد ‪ .‬هیچ وقت باقی مانده بیش از‬
‫ظرفیت نمی شود و عدد های بدست آمده در بازه ای از ‪ 0‬تا ‪ m-1‬قرار می گیرند‬
‫آن هم بدلیل اینکه کلید ها را تقسیم بر ظرفیت جدول می کنیم ‪ .‬حال بیایید‬
‫عنصری با کلید ‪ 89‬را درون این جدول قرار دهیم ‪ .‬اگر اندیس قرار گیری این‬
‫عنصر را محاسبه کنید می بینید که این عنصر نیز درون خانه ‪ 9‬قرار می گیرد !‬
‫حال مهم ترین ایراد درهم سازی را متوجه شدید ‪ .‬همیشه امکان دارد به ازای‬
‫عناصر مختلف اندیس های یکسان ایجاد شود ‪ .‬به این اتفاق که همیشه امکان‬
‫وقوع دارد برخورد )‪ (Collision‬می گویند ‪.‬‬

‫‪)۹-۲‬روش های برخورد با برخورد !‬


‫دو روش برای حل مشکل برخورد وجود دارد ‪.‬‬
‫‪ .1‬روش زنجیره ای ‪Chaining‬‬
‫‪ .2‬روش آدرس دهی باز ‪Open Addressing‬‬

‫‪ )۹-۲-۱‬روش زنجیره ای ‪ :‬در این روش اگر دو عنصر به یک آدرس نگاشت‬


‫شدند باید آن ها را در قالب لیست پیوندی به هم زنجیر کرد ‪ .‬قبال گفتیم که بر‬
‫اساس معیار باقی مانده ‪ ,‬عدد ‪ 89‬و ‪ 69‬هر دو در خانه ‪ 9‬قرار می گیرند اما خب‬
‫از آنجا که این مسئله امکان پذیر نیست می توانیم در خانه ‪ 9‬یک لیست پیوندی‬
‫داشته باشیم که عناصر تداخلی را به هم زنجیر کند ‪.‬‬

‫شما می توانید برای پیاده سازی روش زنجیره ای آرایه ای تعریف کنید که درون هر کدام‬
‫یک گره ‪ Head‬قرار گرفته است ‪ .‬هر بار که عنصری درون یکی از خانه های آرایه وارد‬
‫می شود باید خالی بودن گره ‪ Head‬درون آن بررسی شود ‪ .‬اگر گره ‪ Head‬درون آن‬

‫‪103‬‬
‫خانه خالی باشد گره ‪ Head‬مقدار عنصر را می گیرد ‪ .‬اگر گره ‪ Head‬پر بود باید یک‬
‫گره جدید بسازیم و آن را به قبل از ‪ Head‬بچسبانیم ‪ .‬در واقع بجای اضافه کردن عناصر‬
‫جدید به انتهای لیست پیوندی عناصر را به ابتدا اضافه می کنیم ‪ .‬این کار سرعت این‬
‫الگوریتم را باال می برد ‪ .‬در نتیجه این کار باعث می شود که پیچیدگی اجرایی آن از‬
‫مرتبه )‪ O(1‬باشد ‪.‬‬

‫‪ )۹-۲-۲‬روش آدرس دهی باز ‪:‬‬

‫در این روش دو حالت در نظر گرفته میشود ‪.‬‬

‫حالت اول این است که تعداد عناصر ورودی را می دانیم ‪.‬‬

‫حالت دوم این است که تعداد عناصر ورودی را نمی دانیم ‪(dynamic hashing).‬‬

‫حالت اول درهم سازی به روش آدرس دهی باز ‪:‬‬


‫اگر که برخورد اتفاق افتاد ‪ ,‬میبایست خانه های بعدی را از نظر خالی یا پر بودن بررسی‬
‫کنیم ‪ .‬تا زمانی که خانه های بعدی پر بود به جستجو ادامه می دهیم ‪ .‬وقتی که به اولین‬
‫خانه خالی رسیدیم عنصر را همان جا نگاشت می کنیم ‪ .‬اگر که به انتهای فضا رسیدیم‬
‫و هیچ خانه خالی را پیدا نکردیم جستجو را از ابتدای فضا آغاز می کنیم ‪ .‬به این جستجو‬

‫‪104‬‬
‫جستجوی خطی حلقوی می گویند ‪ .‬مثال اگر خواستیم برای کلید ‪ 69‬فضایی اختصاص‬
‫دهیم و یک برخورد اتفاق افتاد باید به خانه بعدی برویم ‪ .‬در این مثال اگر برخورد در‬
‫خانه ‪ 9‬اتفاق افتاد باید خانه ‪ 10‬را بررسی کنیم ‪ .‬اگر خانه بعدی وجود داشت و همچنین‬
‫خالی بود عنصر ورودی را درون آن قرار می دهیم ‪ .‬اگر که خانه ی بعدی موجود نبود به‬
‫سراغ خانه اول می رویم و جستجوی خانه ی خالی را از آنجا بررسی می کنیم ‪.‬‬
‫برای مثال درمورد روش آدرس دهی باز در حالت اول یک جدول ‪ 10‬خانه ای تصور کنید‬
‫که در ابتدا خالیست ‪ .‬سپس قرار است اعداد ‪ 78 , 69 , 88 , 55 , 15 , 10‬را در آن‬
‫قرار دهیم ‪.‬‬

‫‪0‬‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬ ‫‪6‬‬ ‫‪7‬‬ ‫‪8‬‬ ‫‪9‬‬
‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null Null‬‬

‫اگر از عدد ‪ 10‬سپس ‪ 15‬و سپس ‪ 55‬و ‪ ...‬شروع به جایگذاری داده ها در خانه هایشان‬
‫کنیم با در نظر گرفتن عملگر باقی مانده به عنوان تابع درهم ساز ‪ ,‬کلید ‪ 10‬درون خانه‬
‫شماره ‪ 0‬قرار می گیرد ‪ .‬زیرا باقی مانده ‪ 10‬به تعداد خانه های این لیست که بازهم ‪10‬‬
‫است صفر است ‪.‬‬

‫‪0‬‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬ ‫‪6‬‬ ‫‪7‬‬ ‫‪8‬‬ ‫‪9‬‬
‫‪10‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null Null‬‬

‫سپس عدد ‪ 15‬را تقسیم به ‪ 10‬که همان ظرفیت لیست است می کنیم و نتیجه باقی‬
‫مانده برابر ‪ 5‬است ‪ .‬در نتیجه ‪ 15‬درون خانه ی ‪ 5‬قرار می گیرد ‪.‬‬

‫‪105‬‬
‫‪0‬‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬ ‫‪6‬‬ ‫‪7‬‬ ‫‪8‬‬ ‫‪9‬‬
‫‪10‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪15‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null Null‬‬

‫حال با ورود ‪ 55‬به دلیل برخورد آن با عدد ‪ 15‬که درون خانه ‪ 5‬است باید خانه بعدی‬
‫را بررسی کنیم ‪ .‬خانه بعدی خانه ‪ 6‬است ‪ .‬بدلیل خالی بودن این خانه عدد ‪ 55‬را در‬
‫خانه ‪ 6‬قرار می دهیم ‪.‬‬

‫‪0‬‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬ ‫‪6‬‬ ‫‪7‬‬ ‫‪8‬‬ ‫‪9‬‬
‫‪10‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪15‬‬ ‫‪55‬‬ ‫‪null‬‬ ‫‪null Null‬‬

‫در ادامه کلید ‪ 88‬را در خانه ‪ 8‬قرار میدهیم و ‪ 69‬را در خانه ی ‪ 9‬قرار میدهیم ‪.‬‬

‫‪0‬‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬ ‫‪6‬‬ ‫‪7‬‬ ‫‪8‬‬ ‫‪9‬‬
‫‪10‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪15‬‬ ‫‪55‬‬ ‫‪null‬‬ ‫‪88‬‬ ‫‪69‬‬

‫اکنون با وارد شدن عدد ‪ 78‬برخورد اتفاق میافتد ‪ .‬خانه ‪ 9‬که خانه بعدی است اشغال‬
‫شده است ‪ .‬پس به ناچار به اول لیست مراجعه میکنیم ‪ .‬خانه ‪ 0‬هم اشغال شده و در‬
‫نهایت خانه ‪ 1‬پیدا شده و به عنوان محل کلید ‪ 78‬انتخاب میشود ‪.‬‬

‫‪0‬‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬ ‫‪6‬‬ ‫‪7‬‬ ‫‪8‬‬ ‫‪9‬‬

‫‪106‬‬
‫‪10‬‬ ‫‪78‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪15‬‬ ‫‪55‬‬ ‫‪null‬‬ ‫‪88‬‬ ‫‪69‬‬

‫مراحل باال مجددا بررسی شود تا اگر خطایی در آن وجود دارد مرتفع گردد ‪.‬‬

‫در حالت استفاده از روش زنجیره ای به جای پیدا کردن مکان جدید کلید های برخورد‬
‫پیدا کرده را زنجیر می کنیم ‪( .‬با حالت اول روش آدرس دهی باز مقایسه شود)‬

‫‪0‬‬ ‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬ ‫‪6‬‬ ‫‪7‬‬ ‫‪8‬‬ ‫‪9‬‬
‫‪10‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪55‬‬ ‫‪null‬‬ ‫‪null‬‬ ‫‪78‬‬ ‫‪69‬‬

‫‪15‬‬ ‫‪88‬‬

‫‪null‬‬ ‫‪null‬‬

‫دقت کنید که همانطور که گفته شد کلید های جدید به ابتدای لیست پیوندی اضافه می‬
‫شوند ‪ .‬این کار سرعت درج کردن را باال می برد ‪ .‬زیرا اگر بخواهیم کلید های جدید را به‬
‫انتها بیفزاییم باید کل لیست پیوندی را طی کنیم ‪.‬‬

‫حالت دوم درهم سازی به روش آدرس دهی باز ‪:‬‬


‫در این حالت بدلیل اینکه تعداد داده های ورودی را نمی دانیم اگر تعداد داده های ورودی‬
‫بیشتر از تعداد خانه های حافظه شد باید حافظه را دو برابر کنیم ‪ .‬اگر با تعدادی عمل‬

‫‪107‬‬
‫حذف ‪ ,‬تعداد خانه های خالی حافظه زیاد شد می توانیم حافظه مان را نصف کنیم ‪ .‬نکته‬
‫مهم در مورد گسترش یا فشرده سازی حافظه این است که باید یک حافظه دیگر را وارد‬
‫ماجرا کنیم که نصف یا دوبرابر حافظه قبلی است و اطالعات حافظه قبلی را در حافظه‬
‫جدید کپی کنیم ‪ .‬مثال اگر تعداد داده های ورودی بیشتر از ظرفیت حافظه شد باید یک‬
‫حافظه به ظرفیت دو برابر حافظه قبلی داشته باشیم تا اطالعات حافظه قبلی را در آن‬
‫کپی کنیم ‪ .‬طبیعتا بعد از کپی کردن حافظه قبلی در حافظه جدید باید حافظه قبلی را‬
‫پاک کنیم زیرا که دیگر نیازی به آن نداریم ‪ .‬مثال اگر بخواهیم که شش عنصر را وارد‬
‫یک حافظه تک خانه ای کنیم به صورت زیر باید حافظه گسترش پیدا کند ‪.‬‬

‫‪A‬‬

‫‪A‬‬ ‫‪B‬‬

‫‪A‬‬ ‫‪B‬‬ ‫‪C‬‬

‫‪A‬‬ ‫‪B‬‬ ‫‪C‬‬ ‫‪D‬‬

‫‪A‬‬ ‫‪B‬‬ ‫‪C‬‬ ‫‪D‬‬ ‫‪E‬‬

‫‪A‬‬ ‫‪B‬‬ ‫‪C‬‬ ‫‪D‬‬ ‫‪E‬‬ ‫‪F‬‬

‫‪108‬‬
‫به همین ترتیب گسترش جدول درهم سازی اتفاق میافتد ‪ .‬بر عکس این ماجرا یعنی‬
‫فشرده سازی هم به شکل بر عکس این فرآیند اتفاق میافتد ‪.‬‬

‫‪109‬‬
‫منابع‬
‫کتاب های‬

‫کرمن )‪(CLRS‬‬

‫هورویتز‬

‫دکتر قدسی‬

‫لیپ شوتز‬

‫شیرافکن‬

‫سنجش و دانش‬

‫و ‪...‬‬

‫‪---------------------------------------‬‬

‫جزوه ی‬

‫مبانی برنامه نویسی جاوا تالیف سید سینا دالور‬

‫‪--------------------------------------‬‬

‫دوره های‬

‫برنامه نویسی جاوا از دکتر مصطفی کالمی هریس‬

‫ساختمان داده ها از فرشید شیرافکن‬

‫‪110‬‬
‫مبانی برنامه نویسی جاوا از سید سینا دالور‬

‫توابع بازگشتی از سید سینا دالور‬

‫‪111‬‬

You might also like