الخوارزميات الجشعة وأقصر مسار
اختر الأفضل الآن: متى تقود هذه الفكرة إلى الحل الأمثل، ومتى تضلّك؟ ⏱ نحو 40 دقيقة.
- تشرح الخوارزمية الجشعة ومتى تنجح ومتى تخطئ.
- تحل جدولة القاعة بالقاعدة الجشعة الصحيحة.
- تنفذ خوارزمية دايكسترا بطابور الأولوية وتتتبع المسار.
- تعرف شرط الأوزان الموجبة وتعقيد دايكسترا.
١الخوارزمية الجشعة: الأفضل الآن
الخوارزمية الجشعة Greedy تختار في كل خطوة ما يبدو الأفضل حالًا، ولا تراجع اختيارها. سريعة وبسيطة. لكن هل يقود الأفضل محليًّا إلى الأفضل كليًّا؟ أحيانًا نعم، وأحيانًا لا.
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]
مع فئات الريال تعطي الطريقة الجشعة أقل عدد قطع. لكن مع الفئات 1 و 3 و 4 تأخذ 4 أولًا فتحتاج 3 قطع، والأمثل قطعتان (3 + 3). ولهذا احتجنا البرمجة الديناميكية في الدرس 21.
٢متى تنجح الجشعة؟ جدولة القاعة
قاعة تدريب واحدة، وطلبات حجز لها بداية ونهاية. أكبر عدد من الحجوزات بلا تعارض؟ القاعدة الجشعة الصحيحة: اختر دائمًا الحجز الذي ينتهي أبكر، فيترك أطول وقت للبقية. وهذه القاعدة مثبتة رياضيًّا أنها تعطي الأمثل:
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)
٣أقصر مسار بالكيلومترات: دايكسترا
BFS يجد أقصر مسار بعدد المحطات. أما بالمسافات فنحتاج خوارزمية دايكسترا Dijkstra، وهي جشعة صحيحة: في كل خطوة تحسم المدينة الأقرب من البداية بين المدن التي لم تُحسم، باستخدام طابور أولوية:
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)))
إلى مكة طريقان: عبر بدر مباشرة 150 + 330 = 480 كم، أو عبر بدر ورابغ وجدة 150 + 120 + 150 + 80 = 500 كم؛ فاختارت دايكسترا الأقصر. التعقيد O((V + E) log V) بالكومة.
٤لماذا هي صحيحة؟ ومتى تخطئ؟
- حين نُخرج مدينة من الكومة بأصغر مسافة، لا يمكن أن يوجد طريق أقصر إليها لاحقًا؛ لأن أي طريق آخر يمر بمدن أبعد، والأوزان موجبة.
- الشرط: كل الأوزان موجبة. مع أوزان سالبة (مثل خصم يُضاف عند المرور بطريق) قد تخطئ، وتُستخدم خوارزمية أخرى اسمها
Bellman-Ford. - تطبيقات الخرائط تستخدم دايكسترا أو تطويرات لها مثل
A* التي تضيف تقديرًا للمسافة المتبقية.
توقّع وتحقّق
الجشعة تختار:
Greedy.جدولة القاعة الجشعة الصحيحة: اختر الحجز…
دايكسترا تحتاج أن تكون الأوزان:
التركيب الذي يجعل دايكسترا سريعة:
الخلاصة
- الجشعة: الأفضل الآن دون تراجع؛ سريعة لكنها لا تضمن الأمثل دائمًا.
- الصرف بالريال ينجح جشعًا، وبالفئات 1 و 3 و 4 يخطئ.
- جدولة القاعة: الأبكر انتهاءً أولًا.
- دايكسترا: احسم الأقرب أولًا بكومة، والأوزان موجبة،
O((V + E) log V).
تحدٍّ برمجي: أقصر طريق من ينبع
متقدماستخدم دايكسترا لحساب أقصر مسافة من ينبع إلى الطائف، واطبع المسافة والمسار.
- استخدم الكود الجاهز في الدرس.
- اطبع: المسافة: 500 كم
- ثم اطبع المسار مفصولًا بـ « ← ».
ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:
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] # اكتب هنا
💡 تلميح 1
dist, parent = dijkstra("ينبع")💡 تلميح 2
path_to(parent, "الطائف")حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
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, "الطائف")))