پیاده‎سازی الگوریتم Binary Search در زبان برنامه‌نویسی جاوااسکریپت

دنیای دولوپرها دنیای حل مسائلی است که نه‌تنها باید برای‌شان راه‌حلی صحیح پیدا کرد بلکه در صورت امکان باید آن راه‌حل بهینه نیز باشد. در واقع، اگر یک دولوپر برای مسأله‌ای الگوریتمی را انتخاب کند که به اندازۀ کافی بهینه نباشد، منجر به کدی می‌شود که یا پیچیده است یا به کُندی اجرا خواهد شد و یا از این‌ها بدتر، منجر به کِرَش کردن برنامه خواهد شد.

عمل جستجو در آرایه‌ها و لیست‌ها در حوزهٔ توسعهٔ نرم‌افزار بسیار پرکاربرد است و معمولاً متدهایی برای منظور در زبان‌های برنامه‌نویسی مختلف در نظر گرفته شده‌اند و در زبان جاوااسکریپت نیز متدهای زیادی برای این کار وجود دارد اما نکته‌ای که باید مد نظر قرار داد این است که این متدهای پیش‌فرض غالباً از Linear Search Algorithm استفاده می‌کنند. سازوکار این الگوریتم بدین صورت است که همۀ اعضای آرایه یا لیست به ترتیب با آیتمی که قرار است پیدا شود مقایسه می‌شوند و با پیدا شدن آیتم مورد نظر و یا در صورتی که هیچ آیتمی برای مقایسه باقی نمانده باشد، کار این الگوریتم هم پایان می‌یابد. در این روش زمانی که تعداد عناصر موجود در لیست زیاد نباشد مشکلی وجود ندارد اما در شرایطی که لیست بزرگی داشته باشیم، که مثلاً دارای چند هزار یا چند میلیون عضو است، باید از یک الگوریتم سریع‌تر نظیر Binary Search Algorithm استفاده کنیم.

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

پیاده‌سازی الگوریتم Linear Search در جاوااسکریپت
برای پیاده‌سازی Linear Search در جاوااسکریپت فانکشنی به نام ()linearSearch می‌سازیم که پارامترهای آن شامل یک مقدار (Value) از نوع اینتجر یا استرینگ و همچنین آرایه‌ای از مقادیر مختلف است که باید داخل آن عمل جستجو را انجام دهیم و خروجی این فانکشن یک عدد صحیح است که در صورت وجود داشتن مقدار مد نظر، مکان قرارگیری آن در آرایه و در غیر این صورت عدد 1- خواهد بود.

برای مثال، اگر ([linearSearch(1, [3, 4, 2, 1, 5 را صدا بزنیم، از آنجا که عدد 1 در جایگاه سوم قرار گرفته است (اولین خانۀ آرایه جایگاه صفر را دارد) پس به عنوان خروجی باید عدد 3 ریترن شود و اگر ([linearSearch(0, [3, 4, 2, 1, 5 را صدا بزنیم چون عدد ۰ در این آرایه وجود ندارد، 1- به عنوان خروجی ریترن می‌شود.

Pseudocode یا «شِبه‌کد» بیانی از الگوریتم است که معمولاً شبیه به یک زبان برنامه‌نویسی نوشته می‌شود اما قابل‌اجرا در کامپیوتر نیست بلکه برای آسان نمودن درک و فهم یک الگوریتم برای دولوپرها مورد استفاده قرار می‌گیرد. حال با این توضیحات، سودوکد الگوریتم فوق عبارت است از:

Set found to false
Set position to −1
Set index to 0
while found is false and index < number of elements
    if list[index] is equal to search value
        Set found to true
        Set position to index
    else Add 1 to index
return position

در کد زیر الگوریتم Linear Search در قالب فانکشنی با نام ()linearSearch با زبان جاوااسکریپ پیاده‌سازی شده است:

function linearSearch(value, list) {
    let found = false;
    let position = -1;
    let index = 0;
    while(!found && index < list.length) {
        if(list[index] == value) {
            found = true;
            position = index;
        } else {
            index += 1;
        }
    }
    return position;
}

در تفسیر کدهای فوق باید گفت ابتدا به ساکن دست به تعریف متغیرهای position ،found و index زده‌ و مقادیر پیش‌فرضی هم برای تک‌تک آن‌ها در نظر گرفته‌ایم و این در حالی است که الگوریتم اصلی داخل بلوک while نوشته شده است که به عنوان شرط بلوک while گفته‌ایم مادامی که مقدار متغیر found برابر با true نباشد و مقدار متغیر index از طول آرایهٔ list کوچک‌تر باشد، جستجو باید ادامه یابد.

در ادامه، با استفاده از یک دستور شرطی if تست کرده‌ایم ببینیم که آیا اندیس در نظر گرفته شده برای آرایهٔ []list برابر با مقدار value است یا خیر (به عبارتی، در دور اول این قضیه تست می‌شود که آیا اندیس صِفرم آرایهٔ list برابر با value می‌باشد یا خیر.) اگر پاسخ true بود، دستورات داخل بلوک if انجام می‌شوند که عبارتند از تغییر مقدار پیش‌فرض متغیر found از false به true و اختصاص مقدار در نظر گرفته شده برای index به متغیر position و در غیر این صورت وارد بلوک else می‌شویم که در این بخش مقدار قبلی متغیر index با عدد ۱ جمع می‌شود و در نهایت و پس از پایان یافتن جستجو در آرایهٔ ورودی، مقدار متغیر position ریترن می‌شود.

نکتۀ قابل‌توجه در Linear Search (جستجوی خطی) این است که مرتب بودن لیست ورودی برای این الگوریتم اهمیتی ندارد مضاف بر اینکه در سناریوهای مختلف می‌توانیم این الگوریتم را مختص به آن سناریو تغییر دهیم به طوری که اگر مثلاً لیستی از یک نوع آبجکت داریم که دارای یک یا چند عضو کلیدی است، می‌توانیم با استفاده از آن کلید/کلیدها به دنبال آیتم مورد نظرمان بگردیم. 

در دو مثالی که در بخش قبل برای سودوکد آوردیم، آرایه‌ای که به عنوان آرگومان ورودی فانکشن ()linearSearch در نظر گرفتیم 5 عضو داشت که در بدترین حالت، یعنی زمانی که آیتم مورد نظر وجود نداشته باشد و یا در شرایطی که آن آیتم در آخرین جایگاه قرار گرفته باشد، 5 عمل مقایسه انجام خواهد شد. در واقع، در این مثال‌ها چون آرایۀ کوچکی را در نظر گرفته‌ایم نیازی به بهینه ساختن کد با یک الگوریتم بهتر نداریم اما اگر آرایه بزرگ‌تر شود، از یک جا به بعد دیگر این الگوریتم جوابگوی نیاز ما نخواهد بود و باید از الگوریتمی بهتر مثل Binary Search استفاده نماییم.

پیاده‌سازی Binary Search در جاوااسکریپت
فرض کنید که قرار است نوعی بازی انجام دهیم که حول حدس زدن اعداد می‌چرخد و در این بازی عددی در یک بازۀ مشخص، مثلاً بین 1 تا 100، در نظر گرفته می‌شود و ما به تعداد محدود می‌توانیم حدس بزنیم تا آن عدد را پیدا کنیم. هر بار که عددی را حدس می‌زنیم، اگر درست نباشد به عنوان راهنمایی گفته می‌شود که عددِ مورد نظر از عددی که حدس زده‌ایم کمتر یا بیشتر است.

در این بازی می‌توانیم الگوریتم «حدسِ تصادفی» را به کار ببریم بدین صورت که هر عددی به ذهن‌مان رسید بگوییم و ببینیم درست است یا خیر اما در عین حال الگوریتم دیگر این است که از عدد 1 شروع کنیم و یکی‌یکی بشماریم تا عدد مورد نظر را پیدا کنیم که این حالت شبیه به Linear Search است. همچنین استراتژی دیگری که می‌توانیم در نظر بگیریم تا با تعداد حدس کمتری به نتیجه برسیم، انتخاب عدد میانی (در این مثال عدد 50) است به طوری که اگر عدد در نظر گرفته شده بیشتر از 50 باشد، اعداد 1 تا 49 را حذف خواهیم کرد و این بار عدد میانی 50 تا 100 که می‌شود 75 را به عنوان حدس‌مان انتخاب می‌کنیم و به همین ترتیب با توجه به راهنمایی‌هایی که می‌گیریم، عدد بعدی را طوری انتخاب می‌کنیم که در میان بازۀ بالا یا پایین قرار بگیرد تا در نهایت به جواب برسیم. این روش کار در واقع همان الگوریتم Binary Search است.

بر خلاف Linear Search، در Binary Search لیستی که در آن به دنبال آبجکت مورد نظرمان می‌گردیم باید مرتب‌شده باشد و همان‌طور که در مثال بازی حدس زدن اعداد مشاهده کردید، در این حالت برای جستجوی خود باید عدد میانی را به عنوان حدس‌مان در نظر بگیریم و آن را با عدد مورد نظر مقایسه کنیم که اگر با هم برابر بودند، به هدف‌مان رسیده‌ایم اما اگر عدد مورد نظر از عدد میانی لیست بزرگ‌تر بود، نیمۀ بالایی لیست را در نظر می‌گیریم و در مقابل اگر آبجکت مورد نظر از عدد میانی کوچک‌تر بود، نیمه پایینی لیست را انتخاب می‌کنیم. عمل جستجو را به همین ترتیب در نیمه‌ای از لیست که انتخاب شده است ادامه می‌دهیم تا مقدار مورد نظر پیدا شود.

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

1 2 3 4 5 6 7 8 9 10

طبق الگوریتمی که توضیح داده شد، ابتدا عدد میانی را با استفاده از کد (Math.floor((first + last) / 2 پیدا می‌کنیم. در این کد متغیر first ایندکسِ (اندیس) اولین عدد و last ایندکسِ آخرین عدد در این آرایه است و از آنجا که ایندکس آرایه نمی‌تواند یک عدد اعشاری باشد، از فانکشن ()floor که توسط طراحان زبان جاوااسکریپت در این زبان تعبیه شده است استفاده می‌کنیم که حاصل عبارت 2 / (first + last) را در صورت اعشاری بودن به یک عدد صحیح رو به پایین رُند می‌کند (مثلاً 3/5 را تبدیل به 3 می‌کند.) در لیست مذکور، عدد میانی 5 است و مقداری که به دنبالش می‌گردیم 9 است که از 5 بزرگ‌تر است و از همین روی در نیمۀ بالایی لیست جستجو می‌کنیم که عبارت است از:

6 7 8 9 10

عدد میانیِ این لیست 8 است و 9 از 8 بزرگ‌تر است فلذا دوباره در نیمۀ بالایی می‌گردیم که لیست زیر می‌شود:

9 10

عدد میانی در این لیست 9 است و این همان عددی است که به دنبالش می‌گردیم. در نتیجه جستجو به پایان می‌رسد.

آنچه برای نحوهٔ عملکرد الگوریتم Binary Search توضیح دادیم را می‌توان در قالب یک سودوکد به صورت زیر نوشت:

Set first to 0
Set last to the last index in the list
Set found to false
Set position to −1
while found is false and first is less than or equal to last
    Set middle to the index halfway between first and last
    if list[middle] equals the desired value
        Set found to true
        Set position to middle
    else if list[middle] is greater than the desired value
        Set last to middle − 1
    else
        Set first to middle + 1
return position

برای پیاده‌سازی Binary Search با استفاده از جاوااسکریپت، فانکشنی به نام ()binarySearch تعریف می‌کنیم که پارامترهای ورودی آن یک آرایه و یک عددی است که قرار است به دنبالش بگردیم. این فانکشن به عنوان خروجی، اندیس عدد مذکور در آرایه را ریترن می‌کند و اگر این مقدار در آرایه وجود نداشته باشد، عدد 1- بازگردانده خواهد شد:

function binarySearch(value, list) {
    let first = 0; //left endpoint
    let last = list.length - 1; //right endpoint
    let position = -1;
    let found = false;
    let middle;
    while (found === false && first <= last) {
        middle = Math.floor((first + last) / 2);
        if (list[middle] == value) {
            found = true;
            position = middle;
        } else if (list[middle] > value) { //if in lower half
            last = middle - 1;
        } else { //in in upper half
            first = middle + 1;
        }
    }
    return position;
}

در تفسیر اسکریپت فوق، باید گفت که پیش از هر چیز با استفاده از کلیدواژهٔ let از خط دوم تا ششم اقدام به تعریف پنج متغیر کرده‌ایم. به عنوان شرط بلوک while هم گفته‌ایم چنانچه مقدار متغیر found برابر با false بود و مقدار متغیر first کوچک‌تر یا مساوی با مقدار متغیر last بود، چرخش ادامه یابد.

داخل بلوک while هم پیش از هر چیز، مقدار متغیر middle را یافته‌ایم بدین صورت که با استفاده از کلاس پیش‌فرض زبان جاوااسکریپت تحت عنوان Math گفته‌ایم مقدار رُند حاصل‌جمع متغیرهای first و last تقسیم‌ بر عدد ۲ به عنوان مقدار متغیر middle در نظر گرفته شود. سپس با استفاده از یک دستور شرطی چک کرده‌ایم ببینیم آیا آرایهٔ []list با اندیسی همچون middle برابر با مقدار پارامتر ورودی value می‌باشد یا خیر که اگر این مقدار true بود، وارد بلوک if شده و مقدار متغیر found را از false به true تغییر می‌دهیم سپس مقدار متغیر middle را برای متغیر position در نظر می‌گیریم.

اما اگر خروجی این شرط false بود، وارد بلوک else if می‌شویم تا شرط دیگری را تست کنیم مبنی بر اینکه اگر آرایهٔ []list با اندیس middle بزرگ‌تر مقدار value بود، ۱ واحد از مقدار متغیر middle کسر کرده و نتیجه را به متغیر last اختصاص دهیم.

حال اگر نه پاسخ به شرط if و نه پاسخ به شرط else if برابر با true بود، وارد بلوک else خواهیم شد که در آن دستور داده‌ایم ۱ واحد به مقدار متغیر middle اضافه شده و حاصل‌جمع به عنوان مقدار متغیر first در نظر گرفته شود و در نهایت هم پس از تکمیل فرآیند مقدار متغیر position ریترن خواهد شد.

جمع‌بندی
در این مقاله دیدیم که چگونه می‌توان Linear Search و Binary Search را با استفاده از جاوااسکریپت پیاده‌سازی کرد. الگوریتم Linear Search ساده‌تر است و لیست ورودی به آن الزاماً نباید مرتب‌شده باشد اما از طرف دیگر در مواجه با آرایه‌های بزرگ این الگوریتم اصلاً بهینه نیست و در بدترین حالت منجر به انجام n عمل مقایسه می‌شود که در اینجا n تعداد اعضای آرایه است.

در مقابل، الگوریتم Binary Search نیاز دارد تا لیست ورودی حتماً مرتب‌شده باشد و پیاده‌سازی آن نیز کمی پیچیده‌تر است اما حتی با در نظر گرفتن هزینه‌ای که برای مرتب کردن آرایه تحمیل می‌شود از یکسو و همچنین صرف زمان برای کدنویسی آن از سوی دیگر، این الگوریتم سریع‌تر از جستجویِ خطی است. برای مثال، اگر یک آرایه با 10 عضو داشته باشیم، در Binary Search حداکثر 4 مقایسه انجام می‌دهیم و در الگوریتم Linear Search تعداد مقایسه‌ها 10 بار خواهد بود که شاید تفاوت زیادی با Binary Search نداشته باشد اما برای یک آرایه با یک میلیون عضو، استفاده از الگوریتم Binary Search حداکثر منجر به 20 عمل مقایسه می‌شود که نسبت به Linear Search که حداکثر یک میلیون مقایسه انجام خواهد داد پیشرفت قابل‌توجهی است!

در آخر باید گفت که مهارت استفاده از Binary Search تنها برای نشان دادن دانش‌تان در یک مصاحبۀ کاری نیست بلکه این مهارت می‌تواند منجر به نوشتن کدی بهینه شود که در نهایت پرفورمنس اپلیکیشن شما را ارتقاء خواهد بخشید.

نظرات
اگر login نکردی برامون ایمیلت رو بنویس: