البحث الثنائي
آخر تحديث
يعثر البحث الثنائي على قيمة مستهدفة في مصفوفة **مرتبة** عبر تقسيم نافذة البحث إلى النصف مرارًا. يقارن العنصر الأوسط بالقيمة المستهدفة: التطابق ينهي البحث؛ وإلا يُستبعد النصف الذي لا يمكن أن يحتوي على الهدف وتتقلص النافذة إلى النصف الآخر. كل مقارنة تستبعد نصف العناصر المتبقية، ولهذا يعمل في O(log n) — فالبحث في مليون قيمة مرتبة يحتاج على الأكثر إلى نحو 20 مقارنة.
تعرض الرسوم المتحركة أعلاه المؤشرات lo وmid وhi وتُخفت النصف المستبعد بعد كل مقارنة. الشرط المسبق الوحيد غير القابل للتفاوض: يجب أن تكون المصفوفة مرتبة مسبقًا — مع البيانات غير المرتبة تحتاج إلى البحث الخطي أو إلى ترتيب أولًا (انظر الترتيب بالدمج). وفكرة التقسيم إلى النصف نفسها هي أساس شجرة البحث الثنائية.
التعقيد الزمني والمكاني
| الحالة | التعقيد | ملاحظات |
|---|---|---|
| أفضل حالة | O(1) | العنصر الأوسط هو الهدف في المقارنة الأولى. |
| الحالة المتوسطة | O(log n) | كل مقارنة تقسم النافذة المتبقية إلى النصف. |
| أسوأ حالة | O(log n) | تتقلص النافذة إلى عنصر واحد قبل العثور على الهدف أو التأكد من غيابه. |
| المساحة | O(1) | النسخة التكرارية تحتفظ فقط بالمؤشرات lo وhi وmid. |
خطوة بخطوة
| الخطوة | ما الذي يحدث |
|---|---|
| 1 | اضبط lo على أول مؤشر وhi على آخر مؤشر في المصفوفة المرتبة. |
| 2 | احسب المؤشر الأوسط: mid = (lo + hi) // 2. |
| 3 | إذا كان a[mid] يساوي الهدف، أعد mid — تم العثور عليه. |
| 4 | إذا كان a[mid] **أصغر** من الهدف، فلا يمكن أن يكون الهدف إلا في النصف الأيمن: اضبط lo = mid + 1. |
| 5 | إذا كان a[mid] **أكبر** من الهدف، فابحث في النصف الأيسر: اضبط hi = mid - 1. |
| 6 | كرر من الخطوة 2 ما دام lo <= hi؛ إذا فرغت النافذة فالهدف غير موجود في المصفوفة. |
مثال محلول
البحث عن 5 في [1, 2, 3, 5, 7, 8, 9]:
| الجولة | النافذة (lo..hi) | mid | a[mid] | الإجراء |
|---|---|---|---|---|
| 1 | [1, 2, 3, 5, 7, 8, 9] (0..6) | 3 | 5 | a[3] = 5 — تم العثور على الهدف عند المؤشر 3. |
بحث فاشل، خطوة بخطوة
البحث عن 4 في المصفوفة نفسها يوضح كيف تفرغ النافذة:
| الجولة | النافذة (lo..hi) | mid | a[mid] | الإجراء |
|---|---|---|---|---|
| 1 | 0..6 | 3 | 5 | 5 > 4 — ابحث في النصف الأيسر، hi = 2. |
| 2 | 0..2 | 1 | 2 | 2 < 4 — ابحث في النصف الأيمن، lo = 2. |
| 3 | 2..2 | 2 | 3 | 3 < 4 — يصبح lo مساويًا 3 وتفرغ النافذة: غير موجود. |
متى تستخدم البحث الثنائي
| استخدمه عندما | تجنبه عندما |
|---|---|
| تكون البيانات مرتبة مسبقًا (أو تبحث فيها مرات عديدة) | تكون البيانات غير مرتبة وتُبحث مرة واحدة فقط — الترتيب أولًا يكلف O(n log n) |
| تدعم المجموعة الوصول العشوائي السريع (المصفوفات) | لا تملك سوى وصول تسلسلي (القوائم المتصلة) |
تكون مجموعة البيانات كبيرة — O(log n) يتألق على نطاق واسع | تكون مجموعة البيانات صغيرة جدًا — المسح البسيط بنفس السرعة وأبسط |
كود Binary Search
تنفيذ نظيف وقابل للتشغيل لخوارزمية Binary Search بلغات Python, JavaScript, Java, C++, C, Pseudocode. اختر لغة، وانسخ الكود، أو افتحه محمّلًا مسبقًا في ساحة تجربة Coddy.
كود Binary Search بلغة Python
1def binary_search(a, target):2 lo, hi = 0, len(a) - 13 while lo <= hi:4 mid = (lo + hi) // 25 if a[mid] == target:6 return mid7 if a[mid] < target:8 lo = mid + 1 # search the right half9 else:10 hi = mid - 1 # search the left half11 return -112
13
14nums = [1, 2, 3, 5, 7, 8, 9] # must be sorted15print("Index of 5:", binary_search(nums, 5))16print("Index of 4:", binary_search(nums, 4))كود Binary Search بلغة JavaScript
1function binarySearch(a, target) {2 let lo = 0;3 let hi = a.length - 1;4 while (lo <= hi) {5 const mid = Math.floor((lo + hi) / 2);6 if (a[mid] === target) return mid;7 if (a[mid] < target) {8 lo = mid + 1; // search the right half9 } else {10 hi = mid - 1; // search the left half11 }12 }13 return -1;14}15
16const nums = [1, 2, 3, 5, 7, 8, 9]; // must be sorted17console.log("Index of 5:", binarySearch(nums, 5));18console.log("Index of 4:", binarySearch(nums, 4));كود Binary Search بلغة Java
1public class Main {2 static int binarySearch(int[] a, int target) {3 int lo = 0;4 int hi = a.length - 1;5 while (lo <= hi) {6 int mid = (lo + hi) / 2;7 if (a[mid] == target) return mid;8 if (a[mid] < target) {9 lo = mid + 1; // search the right half10 } else {11 hi = mid - 1; // search the left half12 }13 }14 return -1;15 }16
17 public static void main(String[] args) {18 int[] nums = {1, 2, 3, 5, 7, 8, 9}; // must be sorted19 System.out.println("Index of 5: " + binarySearch(nums, 5));20 System.out.println("Index of 4: " + binarySearch(nums, 4));21 }22}كود Binary Search بلغة C++
1#include <iostream>2#include <vector>3
4int binarySearch(const std::vector<int>& a, int target) {5 int lo = 0;6 int hi = static_cast<int>(a.size()) - 1;7 while (lo <= hi) {8 int mid = lo + (hi - lo) / 2;9 if (a[mid] == target) return mid;10 if (a[mid] < target) {11 lo = mid + 1; // search the right half12 } else {13 hi = mid - 1; // search the left half14 }15 }16 return -1;17}18
19int main() {20 std::vector<int> nums = {1, 2, 3, 5, 7, 8, 9}; // must be sorted21 std::cout << "Index of 5: " << binarySearch(nums, 5) << "\n";22 std::cout << "Index of 4: " << binarySearch(nums, 4) << "\n";23 return 0;24}كود Binary Search بلغة C
1#include <stdio.h>2
3int binary_search(const int a[], int n, int target) {4 int lo = 0;5 int hi = n - 1;6 while (lo <= hi) {7 int mid = lo + (hi - lo) / 2;8 if (a[mid] == target) return mid;9 if (a[mid] < target) {10 lo = mid + 1; /* search the right half */11 } else {12 hi = mid - 1; /* search the left half */13 }14 }15 return -1;16}17
18int main(void) {19 int nums[] = {1, 2, 3, 5, 7, 8, 9}; /* must be sorted */20 int n = sizeof(nums) / sizeof(nums[0]);21 printf("Index of 5: %d\n", binary_search(nums, n, 5));22 printf("Index of 4: %d\n", binary_search(nums, n, 4));23 return 0;24}كود Binary Search بلغة Pseudocode
1DECLARE nums : ARRAY[1:7] OF INTEGER2DECLARE n : INTEGER3n ← 74// The array must be sorted for binary search5nums[1] ← 16nums[2] ← 27nums[3] ← 38nums[4] ← 59nums[5] ← 710nums[6] ← 811nums[7] ← 912
13FUNCTION binarySearch(target : INTEGER) RETURNS INTEGER14 DECLARE lo : INTEGER15 DECLARE hi : INTEGER16 DECLARE mid : INTEGER17 lo ← 118 hi ← n19 WHILE lo <= hi DO20 mid ← (lo + hi) DIV 221 IF nums[mid] = target THEN22 RETURN mid23 ENDIF24 IF nums[mid] < target THEN25 // Target is larger — search the right half26 lo ← mid + 127 ELSE28 // Target is smaller — search the left half29 hi ← mid - 130 ENDIF31 ENDWHILE32 RETURN -133ENDFUNCTION34
35OUTPUT "Index of 5 is ", binarySearch(5)36OUTPUT "Index of 4 is ", binarySearch(4)الأسئلة الشائعة عن البحث الثنائي
ما التعقيد الزمني للبحث الثنائي؟
O(log n) في الحالتين المتوسطة والأسوأ، لأن كل مقارنة تقسم نافذة البحث المتبقية إلى النصف، وO(1) في أفضل حالة عندما يكون أول عنصر أوسط هو الهدف. تستخدم النسخة التكرارية مساحة إضافية O(1).لماذا يتطلب البحث الثنائي مصفوفة مرتبة؟
ما الفرق بين البحث الثنائي والبحث الخطي؟
O(n)) ويعمل مع أي مصفوفة؛ بينما يقسم البحث الثنائي نافذة البحث في مصفوفة مرتبة إلى النصف (O(log n)) لكنه يتطلب مدخلات مرتبة. مع حفنة من العناصر يكون الفرق ضئيلًا — أما على نطاق واسع فيفوز البحث الثنائي بفارق حاسم.كم عدد المقارنات التي يحتاجها البحث الثنائي؟
log2(n) + 1: تغطي 10 مقارنات 1,000 عنصر، و20 مقارنة تغطي 1,000,000. هذا النمو اللوغاريتمي هو ما يجعله وسيلة البحث الافتراضية في البيانات المرتبة.ما خطأ الفيض (overflow) الكلاسيكي في البحث الثنائي؟
(lo + hi) / 2 قد يسبب فيضًا في الأعداد الصحيحة ذات الحجم الثابت عندما يتجاوز lo + hi الحد الأقصى للنوع. الصيغة الآمنة هي mid = lo + (hi - lo) / 2. في Python لا يهم هذا (أعداد صحيحة بدقة غير محدودة)، لكنه في Java/C/C++ خطأ حقيقي وشهير.