طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 101 · تركيب البيانات والخوارزميات
الدرس 24 · الوحدة 11: اختيار وتطبيق الحلول المناسبة

الخوارزميات الجشعة وأقصر مسار

اختر الأفضل الآن: متى تقود هذه الفكرة إلى الحل الأمثل، ومتى تضلّك؟ ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح الخوارزمية الجشعة ومتى تنجح ومتى تخطئ.
  • تحل جدولة القاعة بالقاعدة الجشعة الصحيحة.
  • تنفذ خوارزمية دايكسترا بطابور الأولوية وتتتبع المسار.
  • تعرف شرط الأوزان الموجبة وتعقيد دايكسترا.

١الخوارزمية الجشعة: الأفضل الآن

الخوارزمية الجشعة Greedy تختار في كل خطوة ما يبدو الأفضل حالًا، ولا تراجع اختيارها. سريعة وبسيطة. لكن هل يقود الأفضل محليًّا إلى الأفضل كليًّا؟ أحيانًا نعم، وأحيانًا لا.

main.py
def greedy_change(coins, amount):    used = []    for c in sorted(coins, reverse=True):     # الأكبر أولًا        while amount >= c:            amount -= c            used.append(c)    return used print(greedy_change([1, 5, 10, 50, 100], 167))   # الريالات: صحيحprint(greedy_change([1, 3, 4], 6))               # خطأ! الأمثل [3, 3]
Shell×
>>> %Run main.py
[100, 50, 10, 5, 1, 1]
[4, 1, 1]
>>>

مع فئات الريال تعطي الطريقة الجشعة أقل عدد قطع. لكن مع الفئات 1 و 3 و 4 تأخذ 4 أولًا فتحتاج 3 قطع، والأمثل قطعتان (3 + 3). ولهذا احتجنا البرمجة الديناميكية في الدرس 21.

٢متى تنجح الجشعة؟ جدولة القاعة

قاعة تدريب واحدة، وطلبات حجز لها بداية ونهاية. أكبر عدد من الحجوزات بلا تعارض؟ القاعدة الجشعة الصحيحة: اختر دائمًا الحجز الذي ينتهي أبكر، فيترك أطول وقت للبقية. وهذه القاعدة مثبتة رياضيًّا أنها تعطي الأمثل:

main.py
bookings = [("ورشة Excel", 8, 10), ("اجتماع", 9, 11), ("محاضرة", 10, 12),            ("مقابلة", 11, 12), ("تدريب", 12, 14), ("زيارة", 13, 15), ("دورة", 14, 16)] chosen = []end = 0for name, s, e in sorted(bookings, key=lambda b: b[2]):   # الأبكر انتهاءً أولًا    if s >= end:        chosen.append(name)        end = eprint(len(chosen), chosen)
Shell×
>>> %Run main.py
4 ['ورشة Excel', 'محاضرة', 'تدريب', 'دورة']
>>>

٣أقصر مسار بالكيلومترات: دايكسترا

BFS يجد أقصر مسار بعدد المحطات. أما بالمسافات فنحتاج خوارزمية دايكسترا Dijkstra، وهي جشعة صحيحة: في كل خطوة تحسم المدينة الأقرب من البداية بين المدن التي لم تُحسم، باستخدام طابور أولوية:

2301501801201508090330المدينةينبعبدرجدةمكةالطائفرابغ
من المدينة إلى الطائف: أي الطرق أقصر بالكيلومترات؟
main.py
roads = [("المدينة", "ينبع", 230), ("المدينة", "بدر", 150), ("ينبع", "رابغ", 180),         ("بدر", "رابغ", 120), ("رابغ", "جدة", 150), ("جدة", "مكة", 80),         ("مكة", "الطائف", 90), ("بدر", "مكة", 330)]graph = {}for a, b, km in roads:    graph.setdefault(a, []).append((b, km))    graph.setdefault(b, []).append((a, km))import heapq def dijkstra(start):    dist = {start: 0}    parent = {start: None}    heap = [(0, start)]    done = set()    while heap:        d, city = heapq.heappop(heap)        # أقرب مدينة لم تُحسم بعد        if city in done:            continue        done.add(city)        for nb, km in graph[city]:            nd = d + km            if nd < dist.get(nb, float("inf")):   # وجدنا طريقًا أقصر                dist[nb] = nd                parent[nb] = city                heapq.heappush(heap, (nd, nb))    return dist, parent def path_to(parent, goal):    p = []    while goal is not None:        p.append(goal)        goal = parent[goal]    return p[::-1] dist, parent = dijkstra("المدينة")for city in sorted(dist, key=dist.get):    print(f"{city}: {dist[city]} كم ← " + " ← ".join(path_to(parent, city)))
Shell×
>>> %Run main.py
المدينة: 0 كم ← المدينة
بدر: 150 كم ← المدينة ← بدر
ينبع: 230 كم ← المدينة ← ينبع
رابغ: 270 كم ← المدينة ← بدر ← رابغ
جدة: 420 كم ← المدينة ← بدر ← رابغ ← جدة
مكة: 480 كم ← المدينة ← بدر ← مكة
الطائف: 570 كم ← المدينة ← بدر ← مكة ← الطائف
>>>

إلى مكة طريقان: عبر بدر مباشرة 150 + 330 = 480 كم، أو عبر بدر ورابغ وجدة 150 + 120 + 150 + 80 = 500 كم؛ فاختارت دايكسترا الأقصر. التعقيد O((V + E) log V) بالكومة.

٤لماذا هي صحيحة؟ ومتى تخطئ؟

توقّع وتحقّق

الجشعة تختار:

Greedy.

جدولة القاعة الجشعة الصحيحة: اختر الحجز…

مثبتة.

دايكسترا تحتاج أن تكون الأوزان:

شرطها.

التركيب الذي يجعل دايكسترا سريعة:

الأقرب أولًا.

الخلاصة

تحدٍّ برمجي: أقصر طريق من ينبع

متقدم

استخدم دايكسترا لحساب أقصر مسافة من ينبع إلى الطائف، واطبع المسافة والمسار.

المطلوب:
  • استخدم الكود الجاهز في الدرس.
  • اطبع: المسافة: 500 كم
  • ثم اطبع المسار مفصولًا بـ « ← ».

ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:

main.py
roads = [("المدينة", "ينبع", 230), ("المدينة", "بدر", 150), ("ينبع", "رابغ", 180),         ("بدر", "رابغ", 120), ("رابغ", "جدة", 150), ("جدة", "مكة", 80),         ("مكة", "الطائف", 90), ("بدر", "مكة", 330)]graph = {}for a, b, km in roads:    graph.setdefault(a, []).append((b, km))    graph.setdefault(b, []).append((a, km))import heapq def dijkstra(start):    dist = {start: 0}    parent = {start: None}    heap = [(0, start)]    done = set()    while heap:        d, city = heapq.heappop(heap)        # أقرب مدينة لم تُحسم بعد        if city in done:            continue        done.add(city)        for nb, km in graph[city]:            nd = d + km            if nd < dist.get(nb, float("inf")):   # وجدنا طريقًا أقصر                dist[nb] = nd                parent[nb] = city                heapq.heappush(heap, (nd, nb))    return dist, parent def path_to(parent, goal):    p = []    while goal is not None:        p.append(goal)        goal = parent[goal]    return p[::-1] # اكتب هنا
المطلوب×
المسافة: 500 كم
ينبع ← رابغ ← جدة ← مكة ← الطائف
💡 تلميح 1
dist, parent = dijkstra("ينبع")
💡 تلميح 2
path_to(parent, "الطائف")

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

main.py
roads = [("المدينة", "ينبع", 230), ("المدينة", "بدر", 150), ("ينبع", "رابغ", 180),         ("بدر", "رابغ", 120), ("رابغ", "جدة", 150), ("جدة", "مكة", 80),         ("مكة", "الطائف", 90), ("بدر", "مكة", 330)]graph = {}for a, b, km in roads:    graph.setdefault(a, []).append((b, km))    graph.setdefault(b, []).append((a, km))import heapq def dijkstra(start):    dist = {start: 0}    parent = {start: None}    heap = [(0, start)]    done = set()    while heap:        d, city = heapq.heappop(heap)        # أقرب مدينة لم تُحسم بعد        if city in done:            continue        done.add(city)        for nb, km in graph[city]:            nd = d + km            if nd < dist.get(nb, float("inf")):   # وجدنا طريقًا أقصر                dist[nb] = nd                parent[nb] = city                heapq.heappush(heap, (nd, nb))    return dist, parent def path_to(parent, goal):    p = []    while goal is not None:        p.append(goal)        goal = parent[goal]    return p[::-1] dist, parent = dijkstra("ينبع")print("المسافة:", dist["الطائف"], "كم")print(" ← ".join(path_to(parent, "الطائف")))
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس