Data Structure 15
Data Structure 15
18بهمن1398
2
آنچه خواهید خواند ...
پیشنهادات و انتقادات و راه های ارتباطی با ما6.........................................................................
)۲آرایه ها10......................................................................................................................................
)۳توابع بازگشتی15........................................................................................................................
)۳-۱فاکتوریل16..........................................................................................................
)۳-۲جمع اعداد20........................................................................................................
)۳-۳فیبوناچی21............................................................................................................
)۳-۵برج هانوی24..........................................................................................................
)۳-۶جستجوی دودوئی27.........................................................................................
)۳-۷سایرتوابع30...........................................................................................................
)۴پیچیدگی زمانی33.......................................................................................................................
)۵لیست پیوندی43...........................................................................................................................
)۵-۱گره43.......................................................................................................................
)۶پشته 52..........................................................................................................................................
3
)۶-۱باالنس پرانتز ها57....................................................................................................................
)۷صف75...................................................................................................................................... ......
)۷-۱صف خطی75.................................................................................................................................
)۷-۲صف حلقوی80..............................................................................................................................
)۸درخت 84......................................................................................................................................
)۸-۱درخت مرتب86......................................................................................................................
)۸-۲درخت دودویی86.................................................................................................................
)۸-۵درخت عبارت92...................................................................................................................
)۸ -۸هرم95...................................................................................................................................
)۸-۹صف اولویت…………………………………97……………………………....………..
منابع110...................................................................................................................................
5
پیشنهادات و انتقادات و راه های ارتباطی با ما
از آنجا که فقط خداوند عالمیان است که کامل و بی نقص است اگر دانشجویان عزیز با
مطالعه این جزوه متوجه نقاط ضعف شدند و حس کردند که جزوه نیازمند ویرایش می
باشد می توانند از طریق هر پیام رسانی بجز سروش با شناسه @ssd1377یا با
با اینجانب در میان بگذارند .شما می توانید شماره تلفن همراه ۰۹۳۶۲۱۷۵۹۴۷
با همکار بنده آقای بهبود نیز با شماره تلفن همراه ۰۹۱۷۷۸۷۱۰۹۴در ارتباط باشید
.از راه های گفته شده نیز میتوانید برای درخواست کالس ساختمان داده ها یا جاوا یا
سی شارپ و ( . . .کالس های رفع اشکال یا کالس های جمع بندی و )....اقدام کنید .
بنده در تالشم تا هر چه زودتر به عنوان مسئول برگزاری کارگاه ها و کالس های انجمن
دوره های خوب و پرباری را برایتان تدارک ببینم .
6
)۱ساختمان داده ها چیست؟
بیایید برای درک ساختمان داده ها یک کتابخانه را تصور کنیم .درون هر کتابخانه
تعدادی کتاب وجود دارد که آنها را به داده ها تشبیه می کنیم .نظمی که کتاب ها در
کنار هم دیگر دارند را ساختمان داده ها تصور کنید .به طور مثال میتوانیم کتابها را در
کنار همدیگر یا روی هم و غیره قرار دهیم .
شکل تعدادی از ساختمان داده های معروف را در ادامه می بینید که هر کدام قوانین
متفاوتی دارند .
7
به مجموع این داده ها (کل کتابخانه) که در کنار همدیگر قرار داده شدهاند پایگاه داده
ها گفته می شود .دقیقا بر خالف تصور بعضی که پایگاه داده ها را همان ساختمان
داده ها میدانند !
درس ساختمان داده ها به بررسی تعدادی از ساختمان داده های معروف میپردازد که
کاربرد زیادی در امور کامپیوتر دارند از جمله پشته ,لیست پیوندی ,صف و غیره .
ساختمان داده های خطی :آرایه ,لیست پیوندی ,صف ,پشته و ...
اما در درس ساختمان داده ها سرفصل های دیگری مانند توابع بازگشتی ،پیچیدگی
اجرایی و درهم سازی و غیره نیز وجود دارند که مباحث کمکی می باشند .بنده در این
جزوه قصد دارم تا سرفصل های مهم و چکیده ساختمان داده ها را در این جزوه تشریح
کنم و از زیاده گویی پرهیز کنم .دراین جزوه تمرکز اصلی بر روی مفهوم مطالب است
8
زیرا که هدف درس ساختمان داده ها آشنایی با کاربرد ساختمان داده ها است و نباید
تمرکز به سمت زبان برنامه نویسی خاصی برود .
تصور کنید که می خواهید تعداد زیادی داده را مدیریت یا ذخیره کنید .در این حالت
استفاده از متغیرها اصال توصیه نمی شود زیرا مدیریت تعداد زیادی داده در متغیرها با
محدودیت هایی همراه است .از این رو برای رفع این مشکل از ساختمان داده هایی
همچون آرایه و امثال آن استفاده میکنیم تا بتوانیم بر این محدودیت ها غلبه کنیم .
9
)۲آرایه()Array
آرایه از مهم ترین و کاربردی ترین ساختمان داده ها است .همانطور که کم و بیش درباره
آرایه شناختی دارید آرایه تعدادی خانه حافظه در برنامه است .با توجه به تجربه ای که
از تدریس این مبحث دارم پیشنهاد می کنم که برای یادگیری هر چه عمیق تر آرایه
فنجان ذهنتان را خالی کنید تا بنده بتوانم آرایه را به شکل درست در ضمیرتان قرار دهم
.اگر شما برنامه نویس حرفه ای هستید و با آرایه نیز آشنایی عمیقی دارید نیازی به
مطالعه این قسمت نخواهید داشت .برای عزیزان عالقه مند به این مبحث جزوه مبانی
برنامه نویسی جاوای خودم را توصیه می کنم زیرا مفصال درباره تعریف و استفاده از آرایه
در جاوا شرح داده ام .
آرایه در واقع مانند جدولی یا لیستی است که ما داده هایمان را در هر خانه اش وارد
میکنیم .لیستی که در پایین می بینید را به عنوان یک آرایه در نظر بگیرید تا بتوانید
خانه های آرایه که در کنار هم هستند را به درستی متصور شوید .دراین مثال داده
هایمان تعدادی نام هستند .
۲۰ ۱۵ ۱۷ ۶ ۱۸ ۸ ۱۲ ۳ ۱۰ ۱۳
حتی می توانیم آرایه ای داشته باشیم که بدون محدودیت همه نوع داده ای را ذخیره
کند .
در ابتدا برای استفاده از آرایه باید آن را تعریف کنیم و سپس قادر خواهیم بود تا مقادیر
درون آن را مقدار دهی کنیم .برای اینکه بتوانیم داده مان را درون خانه مورد نظرمان
10
قرار دهیم می بایست اندیس همان خانه را در زمان مقدار دهی ذکر کنیم .هر خانه یک
عدد اختصاصی دارد که اندیس ) ( indexنامیده میشود .اندیس ها مانند کد پستی
هستند .برای اینکه بتوانیم چیزی را به جای مشخصی هدایت کنیم باید عددی را داشته
باشیم که به ما کمک کند تا بتوانیم آدرس دقیق را پیدا کنیم .اندیس ها می توانند از
صفر یا از یک شروع شوند .زبان جاوا که زبانی zero-baseاست اندیس آرایه هایش از
صفر شروع می شوند .بنابراین می توانیم بگوییم که عدد ۲۰در آرایه باال در خانه ۰است
و عدد ۱۷در خانه با اندیس ۲است .طول این آرایه نیز برابر ۱۰است و اندیس خانه
آخر آن ۹است.
آرایه می تواند بیش از یک بعد داشته باشد .بطور مثال در ادامه یک آرایه دو بعدی را
میبینید که اندیس هر خانه به تفکیک در آن ذکر شده .
در ادامه یک مکعب می بینید که در تصویر سازی آرایه سه بعدی درذهن شما موثر است.
تصور بیش از سه بعد غیر ممکن است .اما می توانید از آرایه با ابعاد بیشتر از ۳بعد
استفاده کنید .
11
با اینکه شما در درس برنامه نویسی پیشرفته با تعریف و مقدار دهی آرایه آشنا شدید اما
در اینجا چند مثال برای شما ضمیمه می کنم تا اگر فراموش کردید بیاد بیاورید .
در زیر آرایه ای به نام numsاز جنس اعداد صحیح ۳۲بیتی تعریف کردیم که یک
بعدی است و طول آن ۱۰است .
-برای قرار دادن داده درون آرایه باید حتما اندیس را ذکر کنیم .این در صورتی ایراد به
شمار می رود که ندانیم کدام خانه خالی و کدام پر است .
-برای حذف هر داده احتماال نیاز به عمل شیفت داریم .اگر بخواهیم داده ای حذف کنیم
در واقع بین انبوه داده ها خالیی ایجاد کرده ایم .برای پرکردن این خال باید تک تک
داده ها را جابجا کنیم تا این خال به درستی حذف شود .این مسئله در مقیاس بزرگ
بسیار اهمیت دارد .
12
13
-برای اینکه ترتیب پشت سر هم قرار گرفتن داده ها را تغییر دهیم باید از الگوریتم هایی
با پیچیدگی اجرایی باال بهره ببریم .
...-
در زبان جاوا ArrayListساختمان داده ای است که تا حد زیادی محدودیت های آرایه
را کمرنگ کرده است .
14
)۳توابع بازگشتی
برای حل هر مسئله باید روشی ارائه کنیم .در درس طراحی الگوریتم ها با یکی از این
روش ها که به آن تقسیم و غلبه گفته میشود آشنا می شویم .در این روش حل به سراغ
مسائلی می رویم که قابل شکسته شدن به تکه های کوچک تر باشد .بنابراین می توانیم
مسائلی را که برایمان دشوار است با حل کردن تکه های کوچک حل کنیم .این روش
روش بسیار کار آمدی است که نخستین بار توسط ناپلئون بکار گرفته شد .حتی در
کتاب "قورباغه ات را قورت بده" مفصال درباره آن بحث شده است .شخصا توصیه میکنم
این کتاب را مطالعه کنید .
در درس ساختمان داده ها با این روش مختصرا آشنا می شوید و یاد می گیرید که چگونه
توابعی طراحی کنید تا بتواند مسائل را با توجه به این روش حل کنید .
توابع بازگشتی توابعی هستند که غالبا دستورات کمی دارند و برای حل مسئله خودشان
یا توابع دیگر را فراخوانی می کنند .مثال :
15
)۳-۱فاکتوریل()Factorial
اولین تابع بازگشتی که به آن می پردازیم تابع فاکتوریل است که معموال اولین مثال برای
تدریس مبحث تابع بازگشتی است .همانطور که می دانید فاکتوریل عدد صحیحی مانند
3برابر 6است .در تعریف فاکتوریل به زبان ساده داریم :
)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می رویم .این تابع بسیار شبیه تابع فاکتوریل
می باشد .از همین جهت پیاده سازی این تابع را به عنوان هومورک به شما می سپارم .
20
)۳-۳فیبوناچی()Fibonacci
اکنون فیبوناچی را به عنوان تمرین حل می کنیم که مثال ساده ای از توابع بازگشتی
است .توجه کنید که حل سوال فیبوناچی به روش تقسیم و غلبه نتیجه مطلوبی ندارد و
این مسئله با روش برنامه نویسی پویا نتیجه بهینه تری از خود نشان میدهد .در اینجا
صرفا و استثنا برای یادگیری توابع بازگشتی مسئله فیبوناچی را با توابع بازگشتی حل می
کنیم .
فیبوچی بنده خدا یک دنباله معروف به نام خودش داره .هر جمله این دنباله حاصل
جمع دو عدد پیش از خود است .حال شاید بپرسید که خب جمله اول و دوم که دو
جمله قبل از خود ندارند .پس آنها چگونه محاسبه می شوند ؟؟؟ باید بگویم که بصورت
قرار داد در بعضی منابع دو جمله اول هر دو یک هستند و در بعضی منابع جمله اول 0
و جمله دوم 1است .که ما همان قرار داد دوم را در نظر میگیریم .اوکی ؟!
0 1 1 2 3 5 8 13 21 34 . . .
حال سه قدم طالیی بنده را طی می کنیم .
در قدم اول میخواهیم بدانیم که به ازای چه مقدار ورودی چه خروجی می خواهیم .
پس مثال میزنیم .مثال می خواهیم بدانیم جمله چهارم دنباله فیبوناچی چیست .
21
در قدم دوم باید یک فرمول بیابیم تا بتوان رابطه بازگشتی را پیدا کرد .این فرمول
همانطور که متوجه شدید به شکل زیر است .
n=1یا n=2
از این پس می دانیم که اگر این تابع را با مقدار 1یا 2صدا زدیم نتیجه برابر 0و 1است.
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
در انتهای درس توابع بازگشتی به عنوان تمرین توابعی مانند عملگر ضرب و عملگر توان
و عملگر ترکیب و عملگر باقی مانده و عملگر خارج قسمت صحیح را پیاده سازی کنید
که البته به دلیل عطوفتی که بنده دارم این توابع را برایتان ضمیمه میکنم که امیدوارم
باعث تنبلی شما نشود ! ! ! همچنان تاکید دارم که می توانید با روشی که گفتم این توابع
را پیاده سازی کنید اما این به شرطی است که مراحل را قدم به قدم بگذرانید .
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اندازه می گیرد .
همان طور که متوجه شدید قرار است تا یک تابع ارائه کنیم که بتواند سرعت اجرای
الگورتیم یک برنامه را توصیف کند .هر الگوریتم یک تابع پیچیدگی مخصوص به خودش
دارد .در میان الگوریتم هایی که همگی یک سوال را حل می کنند برای انتخاب الگوریتم
بهتر از لحاظ سرعت اجرا باید توابع پیچیدگیشان را با هم از نظر بزرگی مقایسه کنیم .
الگوریتمی که تابع پیچیدگی کوچک تری دارد برنده ی ماجراست .
قبل از اینکه پیچیدگی اجرایی را خودمان بدست بیاوریم باید پیچیدگی های معروف و
پرکاربرد را بشناسیم .در جدول زیر تعدادی پیچیدگی معروف را می بینید .
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
این جمله به این معناست که
جواب :
برای این که نشان دهیم ) 𝑛2 + 10𝑛 ∈ 𝑂(𝑛2باید عبارت زیر را اثبات کنیم .
𝑛2 + 10𝑛 ≤ 2𝑛2 𝑛2 + 10𝑛 = 2𝑛2 10𝑛 = 𝑛2 𝑛 = 10
در این سوال توانستیم اثبات کنیم که به ازای ضریب , 2تابع 𝑛 𝑛2 + 10از نقطه ی
10به بعد همواره زیر نمودار تابع دیگر قرار می گیرد .
خاصیت بازتابی :خاصیت بازتابی بیان می کند که هر تابع می تواند اوی بزرگ خود
یا اومگای بزرگ خود یا تتای خود باشد .
))𝑛( 𝑓(𝑂𝜖)𝑛(𝑓
))𝑛(𝑓(𝑓(𝑛)𝜖Ω
))𝑛( 𝑓(𝜃𝜖)𝑛(𝑓
37
خاصیت تراگذاری :این خاصیت بیان می دارد که اگر تابعی به نام gبا توابع fو h
رابطه داشته باشد می توانیم رابطه ای میان fو gبیابیم .مثال برای تتا میتوان نوشت :
))𝑛(𝑔(𝜃 = )𝑛( 𝑓
))𝑛(𝑓(𝑛) = 𝜃(ℎ بنابراین
))𝑛(𝑔(𝑛) = 𝜃(ℎ
خاصیت تقارن تتا :بر اساس این خاصیت اگر تابعی مانند gتتای تابع fباشد عکس
این قضیه هم صدق می کند .این خاصیت را با خاصیت بازتابی اشتباه نگیرید .
))𝑛(𝑔(𝜃 = )𝑛(𝑓
خب سوال مهم اینجاست که از کجا باید بفهمیم که رابطه ی دو تابع چگونه است ؟
خوشبختانه برای اینکه بفهمیم رابطه ی بین توابع چگونه است میتوان از limitاستفاده
کرد .
Zero تابع ) g (nبزرگتر است >-
)𝑛(𝑓
lim
)𝑛(𝑔 ∞→𝑛
رشد برابر دارند >Constant -
∞ تابع ) f (nبزرگتر است ->38
برای حل مسائل حد از قضایای حد استفاده کنید .از آنجا که قضایای حد در درس
ریاضی ارائه شده اند پیشنهاد می کنم که به درس حد و پیوستگی مراجعه کنید .
برای اینکه بفهمیم به ازای حجم ورودی هایی که داریم چقدر زمان میبرد تا برنامه مان
اجرا شود باید یک تابع ارائه کنیم .
فرض کنید یک ifو elseداریم .زمان اجرای دستور درون ifبرابر nاست و زمان
اجرای دستور درون elseبرابر mاست .حال پیچیدگی زمانی این برنامه برابر
) Max ( n , mاست .زیرا یا ifاجرا میشود یا elseو در نهایت بیشینه زمان برای
اجرای این برنامه یا nاست یا . m
(b–a+1)/k
اگر در شرط حلقه عالمت مساوی (=) نبود عدد 1را از فرمول حذف می کنیم .درصورتی
که عددی اعشاری به دست آمد باید حد باالی آن را در نظر بگیریم .
توجه کنید که در برای مقایسه ی توابع پیچیدگی فقط جمله تاثیر گذار آن ها را مقایسه
می کنند .مثال بجای مقایسه (n+1)/25با تابعی دیگر فقط می نویسیم . n/25در
ریاضیات دبیرستان با پیدا کردن جمله تاثیر گذار آشنا شده اید .
40
اگر در شرط حلقه عالمت مساوی (=) نبود عدد 1را از فرمول حذف می کنیم .درصورتی
که عددی اعشاری به دست آمد باید حد باالی آن را در نظر بگیریم .
اگر حلقه های تو در تو داشتیم برای محاسبه ی تعداد تکرار باید تکرار حلقه ها را در هم
ضرب کنیم .مثال اگر به صورت زیر دو حلقه داشتیم که تعداد تکرار یکی nو تکرار
دیگری mبود می دانیم که تعداد تکرار دستور Sبرابر m*nاست .
{)for(i=1;i<=n;i++
{)for(j=1;j<=i;j++ ;S } }
41
یکی از روش های نسبتا ساده برای محاسبه پیچیدگی این دستور کمک گرفتن از جدول
است .
)𝑛(𝑛+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
)۵-۲لیست پیوندی یک طرفه
در لیست پیوندی یک طرفه از هر گره میتوانیم صرفاً به گره بعدی دسترسی داشته
باشیم.
در ادامه کد منبع کالس پیاده سازی شده ی لیست پیوندی یک طرفه را می بینید .
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);
}
}
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
در لیست پیوندی دو طرفه از هر گره به گره بعد و گره قبل دسترسی داریم و این باعث
می شود تا قابلیت های بیشتری نسبت به لیست پیوندی یک طرفه داشته باشیم.
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
در درس شی گرایی در مورد پیاده سازی پشته نیز صحبت کرده ام که می توانید با
. مراجعه به آن و با کمک درس شی گرایی پیاده سازی پشته را بهتر بفهمید
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
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 ;
}
}
}
اگر بجای استفاده از دو & از یک & استفاده کنید احتمال ایجاد خطا. دیده میشود
چرا ؟؟؟. وجود دارد
60
)۶-۲تبدیل نگارش های نوشتاری
همان طور که از درس ریاضیات گسسته می دانید نگارش نوشتاری که به آن عادت داریم
و می شناسیم اینفیکس نامیده می شود .مثال می نویسیم 85 + 69ولی نوشتن عبارت
زمانی که عملگر را قبل از عملوند هایش قرار می دهیم به آن نگارش PreFixگفته می
شود .
خب دیگه یاد آوری تا همینجا کافیه .شما باید عمیقا به این درس مسلط باشید تا بتوانید
الگوریتم هایی را در درس ساختمان داده ها طراحی کنید که این نگارش ها را به هم
تبدیل کند .
برای اینکه بتوانید این مسائل را حل کنید لطفا برای خودتان مثال هایی بزنید تا بفهمید
چگونه باید این کار را انجام دهید .در درس توابع بازگشتی نیز همین را توصیه کردم .
یکی از مثال ها میتواند به شکل زیر باشد .
PreFix PostFix
- +568 5+6–8 56+8-
برای اینکه بتوانیم این کار را انجام دهیم ابتدا باید بتوانیم از یک عبارت ,عملگر ها و
عملوند هایش را بیرون بکشیم .تشخیص عملگر ها بسیار ساده است زیرا که عملگرها
61
یک کاراکتری می باشند و قابل پیش بینی اند ( .پیدا کردن عملگر ها در درس طراحی
کامپایلر ها کاری سخت تر است زیرا که باید انتظار عملگرهای ترکیبی و هر اتفاق دیگری
را داشته باشیم ) .
تشخیص هر عملوند از عملوند دیگر کاری حساس تر است زیرا که عملوند می تواند یک
رقمی یا همان یک کاراکتری باشد یا بیشتر .این کار زمانی اهمیت دارد که در حال
پیمایش رشته ورودی باشیم و ندانیم که کدام کاراکترعددی که با آن مواجه هستیم
تنهاست و یا ارقام دیگری در کنارش وجود دارد .پس زمانی که به یک کاراکترعددی
رسیدیم تا زمانی که ارقام دیگری بالفاصله در کنارش ببینیم همه آن ها را به عنوان ارقام
یک عدد در نظر می گیریم .
برای اینکه بتوانیم عملگرها و عملوند های مسئله که عناصر سوال هستند را بیابیم باید
از سمت چپ عبارت شروع به پیمایش کاراکترهای رشته کنیم .ابتدا به کاراکترعددی 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();
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();
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();
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();
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نیاز داریم
که به ترتیب به ابتدای صف و انتهای صف اشاره دارند .برای درک بهتر صف بیایید صف
نانوایی را تصور کنید .هر کس که به صف نانوایی اضافه می شود انتها قرار می گیرد و
هر کس نانش را می گیرد و می خواهد مرخص شود از ابتدای صف خارج می شود .
بیایید فرض کنیم یک صف با ظرفیت چهار خانه داریم .در هر مرحله یک عنصر را به
صف می افزاییم .در ابتدا که صف خالیست rearو frontبرابر -1هستند .هرگاه با
کمک تابع insertیک عنصر به این صف بیافزاییم rearیک واحد اضافه می شود .
اینگونه rearهر بار با درج عناصر یک واحد افزوده می شود تا بدانیم آخرین عنصر این
صف کجا قرار گرفته است ( .هر سطر جدول زیر نماینده یک حالت از صف است )
a
a b
a b c
a b c d
75
زمانی که تمام خانه های صف پر شد دیگر نمی توانیم عنصری بیفزاییم .اما تصور کنید
که طی چند مرحله عناصر ابتدای صف حذف شده اند .
در طی حذف عناصر تعدادی خانه خالی می شود با وجود این خانه های خالی به علت
آن که اشاره گر rearدر انتهای آرایه قرار گرفته است دیگر نمیتوان عنصری را اضافه
کرد .از همین رو به سراغ پیادی سازی صف حلقوی می رویم تا این استثنا دیگراتفاق
نیوفتد .اما پیش از اینکه به سراغ صف حلقوی برویم بهتر است تا با مثال نشان دهیم
دو اشاره گر صف با عملیات درج و حذف چه تغییر میکنند .
A
در این حالت فقط Rearیک واحد بیشتر شده و مقدار 0را به خود میگیرد .
حال اگر بطور مثال عنصر Bرا اضافه کنیم مجددا Rearاست که عوض میشود .
A B
76
با درج عنصر Bاشاره گر Rearبرابر 1میشود .به همین ترتیب اگر عنصری مثل Cرا
درج کنیم اشاره گر Rearبرابر 2میشود .
B C
با حذف عنصر Aاشاره گر Frontیک واحد افزایش میابد و برابر 0میشود .
حال اگر بخواهیم عنصر بعدی جلوی صف که Bاست را حذف کنیم مثل قبل اشاره گر
برابر 1میشود .
C
نتیجه اینکه با درج,اشاره گر Rearو با حذف,اشاره گر Frontیک واحد افزایش میابد.
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به انتهای آرایه و وجود خانه خالی در ابتدای آرایه دیگر صف
پرشده به نظر نمی رسد و باز هم می توانیم عناصر را اضافه کنیم .این خاصیت در صف
خطی وجود نداشت .
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
گره های برادر :گره هایی با یک پدر .
85
)۸-۱درخت مرتب :درختی که ترتیب فرزندان هر گره مشخص است .
86
)۸-۳درخت دودویی پر :درختی که در آن هر گره به غیر از برگ ها دارای دو فرزند
است.
در شکل باال تعداد گره ها 31و ارتفاع درخت 4است .
اگر در مورد درخت دودویی پر تعداد گره ها را nو ارتفاع را hدر نظر بگیریم :
اگر این اعداد و ارقام را به خوبی بررسی کنید به خوبی می فهمید که چرا انیشتین تصاعد
را یکی از شگفتی های جهان می نامد .
شما می توانید با کمک لیست پیوندی درخت ها را پیاده سازی کنید .فقط برای این کار
کافی است گره های لیست پیوندی شما سه فیلد داده ,اشاره گر راست و اشاره گر چپ
داشته باشد .
87
بطور مثال درخت زیر را در نظر بگیرید .
درخت باال را میتوان با لیست پیوندی به شکل زیر پیاده سازی کرد .
88
)۸-۴پیمایش درخت دودویی
پیمایش درخت ,یعنی حرکت روی یالهای درخت ومالقات همه گره های آن دقیقا یکبار.
برای پیمایش درخت دودویی ,سه روش متداول وجود دارد .
برای اینکه مجددا یادآوری کنیم بطور مثال روش پیشوندی را شرح میدهیم .اینگونه
شما روش های دیگر را هم متوجه می شوید .همان طور که فهمیدید این روش ابتدا
ریشه را مالقات می کند .پس در این این روش ابتدا 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ضرب میکنیم و . ...
این ترتیب اعمال عملگرها روی عملوند ها را میتوان به شکل درخت دودویی نشان داد
که برگ های آن عملوند ها هستند و سایرین عملگرند .
با دقت در درخت باال میبیند که عملگرهای با اولویت بیشتر پایین تر از عملگرهای با
اولویت کمتر قرار دارند .پس برای محاسبه ابتدا باید برگ ها را محاسبه کرد .
اگر درخت عبارت را به روش های مختلف پیمایش کنید متوجه می شوید که :
92
در درس طراحی کامپایلر ها باید بررسی کنیم که آیا یک عبارت میتواند چند درخت
معادل داشته باشد یا خیر .در یک زبان اگر بتوان برای یک عبارت بیش از یک درخت
عبارت رسم کرد آن زبان مبهم است .این بدان معناست که زبانی که داریم برای آن
کامپایلر طراحی می کنیم می تواند جمالتی تولید کند که دو پهلو هستند و می توانند
به چند شکل تفسیر شود .
در ادامه یک نمونه درخت دودوئی کامل و غیر کامل می بینید .
93
)۸-۷درخت جستجوی دودویی )) Binary Search Tree( (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گفته میشود .
مثال در بیمارستان اولویت با بیماران قلبی یا تصادفی یا کرونایی است نه بیمارانی که
میخواهد عمل زیبایی انجام دهند ! هر چند که او از همه زودتر در مطب حاضر شده
باشد.
در درس سیستم عامل که با آن بیشتر آشنا می شوید می آموزید که بعضی از برنامه ها
اولویت باالتری نسبت به سایر برنامه ها در استفاده از CPUو سایر منابع سیستم مانند
حافظه و لوازم جانبی دارند .
در مبحث صف اولویت هر داده اولویت خاص خود را دارد .عنصری که باالترین اولویت
را دارد زودتر به آن سرویس داده می شود و حذف می گردد .
97
پس صف اولویت دو نوع است :
تصور کنید تعدادی دانش آموز با رتبه های مختلف برای دریافت جایزه پشت در اتاق
رئیس آموزش و پرورش نشسته اند .از آنجا که اولین جایزه برای دانش آموز رتبه یک
است پس اول او باید وارد اتاق رئیس شود ( .در درس سیستم عامل اتاق مدیر تشبهی
از CPUاست )
این اولویت بندی برای ورود افراد بر اساس اولویتشان به سه حالت امکان پذیر است .
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
توابع در هم سازی به طور کلی توابعی هستند که با دریافت کلید یک عنصر مکان قرار
گرفتن آن عنصر را در جدول در هم سازی به ما تحویل می دهند .
فکر کنید که بوی نامطبوع فصل امتحانات می آید .همان طور که می دانید هر دانشجو
سر جلسه امتحان صندلی مخصوص به خود را دارد .برای مثال صندلی شماره 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می گویند .
شما می توانید برای پیاده سازی روش زنجیره ای آرایه ای تعریف کنید که درون هر کدام
یک گره 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
108
به همین ترتیب گسترش جدول درهم سازی اتفاق میافتد .بر عکس این ماجرا یعنی
فشرده سازی هم به شکل بر عکس این فرآیند اتفاق میافتد .
109
منابع
کتاب های
کرمن )(CLRS
هورویتز
دکتر قدسی
لیپ شوتز
شیرافکن
سنجش و دانش
و ...
---------------------------------------
جزوه ی
--------------------------------------
دوره های
110
مبانی برنامه نویسی جاوا از سید سینا دالور
111