المشروع الختامي
المشروع ودراسة الحالة والمحاكاة كما تطلبها الخطة الرسمية. ⏱ نحو 60 دقيقة.
- تنفذ قائمة مترابطة كاملة برأس وذيل وتختبرها.
- تحلل تعقيد كل عملية وتقيسه تجريبيًّا مقارنة بـ
listوdeque. - تستخدم المحاكاة لتحليل خوارزمية ترتيب في حالاتها المختلفة.
- تكتب تقريرًا يحقق معايير التقييم.
١ثلاثة أنشطة من الخطة الرسمية
الخطة التدريبية للمقرر تحدد ثلاثة أنشطة عملية، تنفذها في المعمل وتسلّمها لمعلمك:
| النشاط | المطلوب | الناتج |
|---|---|---|
| 1. المشروع: هيكل بيانات | تنفيذ قائمة مترابطة (أو مصفوفة ديناميكية) بلغة بايثون: إنشاء، وإضافة، وحذف، وبحث. | كود يعمل + تقرير يحلل تعقيد كل عملية. |
| 2. دراسة حالة: تحليل خوارزمية | تحليل الترتيب السريع أو البحث الثنائي: كيف يعمل، وتعقيده، ومقارنته بغيره. | تقرير: شرح وتحليل وتوصيات استخدام. |
| 3. المحاكاة | تنفيذ خوارزمية بحث أو ترتيب خطوة بخطوة بأداة محاكاة (المحاكي في هذا الموقع، أو Visualgo). | تقرير بالخطوات والتحليل الزمني والتحسينات. |
٢النشاط 1: القائمة المترابطة الكاملة
هذا هيكل المشروع. أكمل الدوال، واختبر كل واحدة بـ assert، ثم قِس زمن العمليات وقارنه بـ list:
class Node: def __init__(self, data): self.data = data self.next = None class LinkedList: """قائمة مترابطة: رأس وذيل وحجم""" def __init__(self): self.head = self.tail = None self.size = 0 def push_front(self, x): # O(1) n = Node(x) n.next = self.head self.head = n if self.tail is None: self.tail = n self.size += 1 def push_back(self, x): # O(1) بفضل الذيل n = Node(x) if self.tail is None: self.head = self.tail = n else: self.tail.next = n self.tail = n self.size += 1 def pop_front(self): # O(1) if self.head is None: raise IndexError("القائمة فارغة") x = self.head.data self.head = self.head.next if self.head is None: self.tail = None self.size -= 1 return x def find(self, x): # O(n) cur, i = self.head, 0 while cur: if cur.data == x: return i cur, i = cur.next, i + 1 return -1 def __len__(self): return self.size def __iter__(self): cur = self.head while cur: yield cur.data cur = cur.next # اختباراتL = LinkedList()assert len(L) == 0 and L.find(5) == -1L.push_back(2); L.push_back(3); L.push_front(1)assert list(L) == [1, 2, 3] and len(L) == 3assert L.find(3) == 2assert L.pop_front() == 1 and list(L) == [2, 3]L.pop_front(); L.pop_front()assert L.head is None and L.tail is Noneprint("نجحت كل الاختبارات")
٣قياس الأداء للتقرير
import timefrom collections import deque class Node: __slots__ = ("data", "next") def __init__(self, d): self.data, self.next = d, None class LinkedList: def __init__(self): self.head = None def push_front(self, x): n = Node(x); n.next = self.head; self.head = n def bench(n): t = time.perf_counter(); a = [] for i in range(n): a.insert(0, i) t_list = time.perf_counter() - t t = time.perf_counter(); L = LinkedList() for i in range(n): L.push_front(i) t_ll = time.perf_counter() - t t = time.perf_counter(); d = deque() for i in range(n): d.appendleft(i) t_dq = time.perf_counter() - t return t_list, t_ll, t_dq for n in [10_000, 40_000]: a, b, c = bench(n) print(f"n={n:>6} | list.insert(0): {a*1000:7.1f} ms | LinkedList: {b*1000:6.1f} ms | deque: {c*1000:5.1f} ms")
حين يتضاعف n أربع مرات، يتضاعف زمن list.insert(0) نحو 16 مرة (O(n²) للكل)، ويتضاعف زمن القائمة المترابطة و deque نحو 4 مرات فقط (O(n) للكل). الأرقام تختلف من جهاز لآخر، والنمط ثابت.
٤النشاط 2 و 3: تحليل ومحاكاة
استخدم المحاكي لتتبع الترتيب السريع على قائمتك، وسجّل عدد المقارنات والتبديلات في تقريرك. ثم جرّب قائمة مرتبة لترى الحالة الأسوأ:
٥معايير التقييم
| المعيار | الوزن | ممتاز حين… |
|---|---|---|
| صحة الكود | 30% | كل العمليات تعمل، ومعها اختبارات assert للحالات الحدّية. |
| تحليل التعقيد | 25% | تعقيد زمني ومساحي صحيح لكل عملية، مع السبب. |
| القياس التجريبي | 20% | قياس على حجمين أو أكثر، ومقارنة بـ list أو deque، وتفسير النمط. |
| دراسة الحالة والمحاكاة | 15% | شرح خطوة بخطوة، وحالات أفضل وأسوأ، وتوصيات. |
| جودة الكود والتقرير | 10% | أسماء واضحة، وتعليقات، وتقرير منظم. |
توقّع وتحقّق
بوجود مؤشر الذيل، الإضافة في آخر القائمة المترابطة:
إضافة n عنصر في أول list بـ insert(0) كلفتها الكلية:
عند حذف آخر عقدة في القائمة يجب جعل:
أداة المحاكاة المذكورة في الخطة:
الخلاصة
- المشروع: قائمة مترابطة بإضافة وحذف وبحث، مع اختبارات.
- الذيل يجعل الإضافة في الآخر
O(1). - القياس على أحجام متعددة يكشف النمط:
O(n)مقابلO(n²). - التقرير: كود، وتحليل، وقياس، وتفسير، وتوصيات.
تحدٍّ برمجي: طابور بقائمة مترابطة
متقدماستخدم القائمة المترابطة لبناء طابور: enqueue بالإضافة في الذيل، و dequeue بالحذف من الرأس، ثم حاكِ طابور 5 عملاء يُخدم منهم 3.
- enqueue و dequeue كلاهما
O(1). - أضف: أحمد، سارة، خالد، منى، فهد.
- اخدم ثلاثة: اطبع «خدمة: …» لكل منهم.
- اطبع الباقين: الباقون:
['منى', 'فهد']
ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:
class Node: def __init__(self, data): self.data = data self.next = None class Queue: def __init__(self): self.head = self.tail = None def enqueue(self, x): pass def dequeue(self): pass
💡 تلميح 1
💡 تلميح 2
tail = None.حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
class Node: def __init__(self, data): self.data = data self.next = None class Queue: def __init__(self): self.head = self.tail = None def enqueue(self, x): n = Node(x) if self.tail is None: self.head = self.tail = n else: self.tail.next = n self.tail = n def dequeue(self): x = self.head.data self.head = self.head.next if self.head is None: self.tail = None return x def items(self): out, cur = [], self.head while cur: out.append(cur.data) cur = cur.next return out q = Queue()for name in ["أحمد", "سارة", "خالد", "منى", "فهد"]: q.enqueue(name)for _ in range(3): print("خدمة:", q.dequeue())print("الباقون:", q.items())