الخوارزميات وهياكل البيانات بلغة C++: من النظرية إلى المشاريع الحقيقية
إتقان الخوارزميات وهياكل البيانات هو الأساس الذي يميز المبرمج المبتدئ عن المهندس المحترف. في لغة C++، واحدة من أقوى اللغات وأكثرها أداءً، يتيح فهم هذه المفاهيم كتابة كود ليس فقط يعمل، بل سريع وقابل للتوسع وموثوق. في هذه المقالة، سنشرح هياكل البيانات والخوارزميات الرئيسية عمليًا، مع أمثلة جاهزة يمكنك استخدامها فورًا في مشاريعك.
لن نتعمق في النظرية المجردة — كل قسم يحتوي على كود محدد بلغة C++ وشروحات حول كيفية ولماذا يعمل هذا البناء أو ذاك. المادة موجهة للمطورين الذين هم على دراية بالفعل بتركيب لغة C++، لكنهم يرغبون في تنظيم معرفتهم بالخوارزميات.
1. هياكل البيانات الأساسية: المصفوفات، القوائم، والهياكل الديناميكية
لنبدأ بالأكثر ضرورة. في C++، توفر المكتبة القياسية (STL) حاويات قوية، لكن فهم بنيتها الداخلية أمر بالغ الأهمية للتحسين.
المصفوفة الديناميكية (std::vector)
std::vector هو الحاوية الأكثر استخدامًا. إنها تمثل مصفوفة ديناميكية تتوسع تلقائيًا عند إضافة العناصر. ومع ذلك، من المهم تذكر التعقيد المطفأ: الإدراج في النهاية هو O(1) في المتوسط، لكن الإدراج في المنتصف هو O(n).
#include <iostream>#include <vector>
int main() { std::vector<int> numbers = {10, 20, 30, 40}; // إضافة في النهاية numbers.push_back(50); // إدراج في البداية (عملية مكلفة) numbers.insert(numbers.begin(), 5); // حذف العنصر الأخير numbers.pop_back(); for (int num : numbers) { std::cout << num << " "; } // الإخراج: 5 10 20 30 40 return 0;}القائمة المرتبطة (std::list)
على عكس المتجه، يتم تنفيذ std::list كقائمة مزدوجة الارتباط. إنها مثالية عندما تقوم بإدراج أو حذف العناصر بشكل متكرر في منتصف الحاوية — تتم هذه العمليات في زمن O(1). الثمن هو عدم وجود وصول مباشر عبر الفهرس.
#include <list>
std::list<std::string> tasks = {"كتابة الكود", "إجراء الاختبارات"};tasks.push_front("تخطيط البنية");tasks.push_back("التوثيق");
// إدراج في المنتصفauto it = tasks.begin();std::advance(it, 2); // تحريك المؤقت إلى العنصر الثانيtasks.insert(it, "إعادة الهيكلة");المكدس والطابور (std::stack, std::queue)
هذه هي محولات الحاويات التي تقيد وظائف الحاوية الأساسية. يعمل المكدس وفقًا لمبدأ LIFO (آخر دخول، أول خروج)، والطابور وفقًا لمبدأ FIFO (أول دخول، أول خروج).
#include <stack>#include <queue>
// مكدسstd::stack<int> st;st.push(1);st.push(2);st.pop(); // يحذف 2
// طابورstd::queue<int> q;q.push(10);q.push(20);q.pop(); // يحذف 10 (الأول)2. خوارزميات الفرز والبحث: من الفقاعة إلى البحث الثنائي
الفرز هو مشكلة كلاسيكية، لكن من المهم عمليًا معرفة متى تستخدم الخوارزميات المضمنة ومتى تكتب خوارزمياتك الخاصة.
الفرز السريع (QuickSort) — التنفيذ في C++
تستخدم STL الفرز الاستبطاني (هجين من QuickSort و HeapSort و InsertionSort). لكن للفهم، سنكتب QuickSort الكلاسيكي:
#include <iostream>#include <vector>
int partition(std::vector<int>& arr, int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; std::swap(arr[i], arr[j]); } } std::swap(arr[i + 1], arr[high]); return i + 1;}
void quickSort(std::vector<int>& arr, int low, int high) { if (low < hi