الخوارزميات وهياكل البيانات باستخدام TypeScript: من النظرية إلى التطبيق
\nفهم الخوارزميات وهياكل البيانات هو الأساس الذي يميز المطور المتمرس عن المبتدئ. TypeScript، بفضل كتابته الصارمة للأنواع وإمكانياته الحديثة، يسمح بكتابة كود موثوق وقابل للقراءة لحل المشكلات الكلاسيكية. في هذه المقالة، سنشرح هياكل البيانات والخوارزميات الرئيسية من خلال أمثلة عملية باستخدام TypeScript. ستتعلم كيفية تنفيذ المكدس، الطابور، البحث الثنائي والترتيب السريع، بالإضافة إلى التعرف على أساسيات العمل مع الرسوم البيانية.
\n\n1. هياكل البيانات الأساسية: المكدس والطابور
\nالمكدس والطابور هيكلان بسيطان ولكنهما مفيدان للغاية. يعمل المكدس وفق مبدأ LIFO (Last In, First Out)، بينما يعمل الطابور وفق مبدأ FIFO (First In, First Out). في TypeScript، من السهل تنفيذهما باستخدام مصفوفة مع استخدام الأدوية (Generics) لضمان أمان الأنواع.
\n\nتنفيذ المكدس (Stack)
\nclass Stack<T> {\n private items: T[] = [];\n\n push(item: T): void {\n this.items.push(item);\n }\n\n pop(): T | undefined {\n return this.items.pop();\n }\n\n peek(): T | undefined {\n return this.items[this.items.length - 1];\n }\n\n isEmpty(): boolean {\n return this.items.length === 0;\n }\n\n size(): number {\n return this.items.length;\n }\n}\n\n// مثال على الاستخدام\nconst stack = new Stack<number>();\nstack.push(10);\nstack.push(20);\nconsole.log(stack.pop()); // 20\nconsole.log(stack.peek()); // 10\n\nتنفيذ الطابور (Queue)
\nclass Queue<T> {\n private items: T[] = [];\n\n enqueue(item: T): void {\n this.items.push(item);\n }\n\n dequeue(): T | undefined {\n return this.items.shift();\n }\n\n front(): T | undefined {\n return this.items[0];\n }\n\n isEmpty(): boolean {\n return this.items.length === 0;\n }\n\n size(): number {\n return this.items.length;\n }\n}\n\n// مثال على الاستخدام\nconst queue = new Queue<string>();\nqueue.enqueue('A');\nqueue.enqueue('B');\nconsole.log(queue.dequeue()); // A\nconsole.log(queue.front()); // B\nلاحظ: الطريقة المضمنة shift() لها تعقيد O(n)، لذلك بالنسبة للبيانات الكبيرة، من الأفضل تنفيذ الطابور باستخدام قائمة مرتبطة.
2. خوارزميات البحث: البحث الخطي والثنائي
\nالبحث عن العناصر هو أحد أكثر العمليات شيوعًا. البحث الخطي بسيط ولكنه بطيء مع البيانات الكبيرة. البحث الثنائي يعمل بتعقيد O(log n)، ولكنه يتطلب مصفوفة مرتبة.
\n\nالبحث الخطي
\nfunction linearSearch<T>(arr: T[], target: T): number {\n for (let i = 0; i < arr.length; i++) {\n if (arr[i] === target) {\n return i;\n }\n }\n return -1;\n}\n\nconst numbers = [3, 7, 1, 9, 4];\nconsole.log(linearSearch(numbers, 9)); // 3\n\nالبحث الثنائي (النسخة العودية)
\nfunction binarySearch<T>(arr: T[], target: T, left: number = 0, right: number = arr.length - 1): number {\n if (left > right) {\n return -1;\n }\n\n const mid = Math.floor((left + right) / 2);\n\n if (arr[mid] === target) {\n return mid;\n }\n\n if (arr[mid] > target) {\n return binarySearch(arr, target, left, mid - 1);\n } else {\n return binarySearch(arr, target, mid + 1, right);\n }\n}\n\nconst sortedArray = [1, 3, 5, 7, 9, 11];\nconsole.log(binarySearch(sortedArray, 7)); // 3\nconsole.log(binarySearch(sortedArray, 6)); // -1\nللنسخة التكرارية، استخدم حلقة while. يساعد TypeScript في تجنب الأخطاء المتعلقة بالأنواع: الدالة العامة <T> تضمن أن المصفوفة والعنصر المستهدف من نفس النوع.
3. الترتيب: الترتيب السريع (Quick Sort)
\nالترتيب السريع هو أحد أكثر الخوارزميات فعالية بمتوسط تعقيد O(n log n). ر