الخوارزميات وهياكل البيانات بلغة JavaScript: من النظرية إلى التطبيق
\nالقدرة على العمل مع الخوارزميات وهياكل البيانات هي مهارة أساسية لأي مطور، بغض النظر عن لغة البرمجة. JavaScript (خاصة في بيئة Node.js) ليست استثناءً. في هذه المقالة، سنشرح هياكل البيانات الأساسية والخوارزميات الكلاسيكية، وننفذها باستخدام JavaScript الخالص. سترى كيف يتم تطبيق المكدس، والطابور، والقائمة المرتبطة، والشجرة، وخوارزميات الترتيب/البحث في المهام الواقعية لتطوير الواجهة الخلفية باستخدام Node.js.
\n\n1. المكدس والطابور: هياكل أساسية لإدارة البيانات
\nالمكدس (Stack) والطابور (Queue) هما هيكلان خطيان للبيانات يختلفان في قواعد إضافة وإزالة العناصر. يعمل المكدس وفق مبدأ LIFO (آخر دخول، أول خروج)، بينما يعمل الطابور وفق مبدأ FIFO (أول دخول، أول خروج).
\n\nتنفيذ المكدس بلغة JavaScript
\nفي JavaScript، يمكن تنفيذ المكدس باستخدام مصفوفة أو قائمة مرتبطة. لنلقِ نظرة على نسخة بسيطة باستخدام المصفوفة:
\nclass Stack {\n constructor() {\n this.items = [];\n }\n\n push(element) {\n this.items.push(element);\n }\n\n pop() {\n if (this.isEmpty()) return null;\n return this.items.pop();\n }\n\n peek() {\n if (this.isEmpty()) return null;\n return this.items[this.items.length - 1];\n }\n\n isEmpty() {\n return this.items.length === 0;\n }\n\n size() {\n return this.items.length;\n }\n}\n\n// مثال على الاستخدام\nconst stack = new Stack();\nstack.push('A');\nstack.push('B');\nstack.push('C');\nconsole.log(stack.pop()); // 'C'\nconsole.log(stack.peek()); // 'B'\nيُستخدم المكدس غالبًا للتحقق من توازن الأقواس، وتنفيذ زر "الرجوع" في المتصفح، أو لاجتياز الأشجار بالعمق أولاً.
\n\nتنفيذ الطابور بلغة JavaScript
\nيمكن أيضًا بناء الطابور باستخدام مصفوفة، ولكن للحصول على أداء أفضل (تجنب إعادة الفهرسة عند استخدام shift) نستخدم قائمة مرتبطة أو مكدسين. إليك تنفيذ بسيط باستخدام المصفوفة:
\nclass Queue {\n constructor() {\n this.items = [];\n }\n\n enqueue(element) {\n this.items.push(element);\n }\n\n dequeue() {\n if (this.isEmpty()) return null;\n return this.items.shift();\n }\n\n front() {\n if (this.isEmpty()) return null;\n return this.items[0];\n }\n\n isEmpty() {\n return this.items.length === 0;\n }\n\n size() {\n return this.items.length;\n }\n}\n\n// مثال على الاستخدام\nconst queue = new Queue();\nqueue.enqueue(1);\nqueue.enqueue(2);\nqueue.enqueue(3);\nconsole.log(queue.dequeue()); // 1\nconsole.log(queue.front()); // 2\nتُستخدم الطوابير في أنظمة إدارة المهام، وفي خوارزميات BFS (البحث بالعرض أولاً)، ولتخزين البيانات مؤقتًا في Node.js (مثل التدفقات).
\n\n2. القائمة المرتبطة: هيكل ديناميكي للإدراج المتكرر
\nالقائمة المرتبطة (Linked List) هي سلسلة من العقد، تحتوي كل عقدة على بيانات ومرجع إلى العقدة التالية. على عكس المصفوفة، يتم إدراج وحذف العناصر في منتصف القائمة بتعقيد O(1) عند وجود مرجع إلى العقدة.
\n\nتنفيذ قائمة مرتبطة أحادية الاتجاه
\nclass ListNode {\n constructor(value, next = null) {\n this.value = value;\n this.next = next;\n }\n}\n\nclass LinkedList {\n constructor() {\n this.head = null;\n this.tail = null;\n this.length = 0;\n }\n\n append(value) {\n const newNode = new ListNode(value);\n if (!this.head) {\n this.head = newNode;\n this.tail = newNode;\n } else {\n this.tail.next = newNode;\n this.tail = newNode;\n }\n this.length++;\n return this;\n }\n\n prepend(value) {\n const newNode = new ListNode(value, this.head);\n this.head = newNode;\n if (!this.tail) this.tail = newNode;\n