الخوارزميات وهياكل البيانات بلغة C++: من أين يبدأ المبتدئ
دراسة لغة C++ مستحيلة دون فهم الخوارزميات الأساسية وهياكل البيانات. هذا هو الأساس الذي يُبنى عليه أي تطبيق - من آلة حاسبة بسيطة إلى محركات ألعاب معقدة. في هذه المقالة، سنشرح المفاهيم الأساسية بلغة بسيطة، مع أمثلة برمجية وتوضيحات. المادة موجهة لمن هم على دراية بأساسيات بناء جملة C++ (المتغيرات، الحلقات، الدوال)، ولكنهم يرغبون في التعمق في الخوارزميات.
لماذا من المهم دراسة الخوارزميات وهياكل البيانات بلغة C++؟
C++ هي لغة ذات إدارة يدوية للذاكرة وأداء عالٍ. هذا يعني أن الاختيار الخاطئ لهيكل البيانات أو الخوارزمية غير الفعالة يمكن أن يؤدي إلى مشاكل خطيرة: تسرب الذاكرة، تباطؤ أداء البرنامج، أو حتى إنهاء غير طبيعي. فهم كيفية عمل المصفوفات، القوائم، الأكوام، والأشجار يسمح بكتابة كود سريع وموثوق.
علاوة على ذلك، فإن معرفة الخوارزميات هي شرط أساسي لاجتياز مقابلات العمل في شركات تكنولوجيا المعلومات. حتى إذا كنت تخطط للعمل مع Python أو Java، فإن القاعدة الخوارزمية بلغة C++ تعطي فهماً عميقاً لما يحدث "تحت الغطاء".
هياكل البيانات الأساسية في C++
1. المصفوفات (Array)
المصفوفة هي أبسط وأسرع هيكل بيانات. جميع العناصر موجودة في الذاكرة بشكل متسلسل، والوصول إلى أي عنصر بواسطة الفهرس يستغرق O(1). ومع ذلك، فإن حجم المصفوفة ثابت: لا يمكن تغييره بعد الإنشاء.
#include <iostream>using namespace std;
int main() { // مصفوفة ثابتة int arr[5] = {10, 20, 30, 40, 50}; // الوصول بواسطة الفهرس cout << "العنصر الثالث: " << arr[2] << endl; // تكرار العناصر for (int i = 0; i < 5; i++) { cout << arr[i] << " "; } return 0;}متى تستخدم: عندما يكون عدد العناصر معروفاً مسبقاً ولا يتغير. على سبيل المثال، تخزين المعاملات أو جدول الثوابت.
2. المصفوفات الديناميكية (std::vector)
في C++، توفر المكتبة القياسية std::vector - مصفوفة ديناميكية يمكن أن تنمو تلقائياً. هذا بديل مناسب للمصفوفات العادية.
#include <iostream>#include <vector>using namespace std;
int main() { vector<int> vec = {1, 2, 3, 4, 5}; // إضافة عنصر vec.push_back(6); // حذف العنصر الأخير vec.pop_back(); // طباعة الحجم cout << "الحجم: " << vec.size() << endl; for (int x : vec) { cout << x << " "; } return 0;}هام: push_back قد يسبب إعادة توزيع للذاكرة، مما يستغرق O(n) في أسوأ الحالات. إذا كنت تعرف الحجم مسبقاً، استخدم reserve().
3. القوائم المرتبطة (std::list, std::forward_list)
على عكس المصفوفات، لا تخزن القوائم العناصر بشكل متسلسل في الذاكرة. كل عنصر (عقدة) يحتوي على مؤشر للعنصر التالي (والسابق في القائمة المزدوجة). الإدراج والحذف في منتصف القائمة يتمان في زمن O(1)، ولكن الوصول بواسطة الفهرس يستغرق O(n).
#include <iostream>#include <list>using namespace std;
int main() { list<int> myList = {10, 20, 30}; // إدراج في البداية myList.push_front(5); // إدراج بعد العنصر الأول auto it = myList.begin(); advance(it, 1); myList.insert(it, 15); for (int x : myList) { cout << x << " "; } return 0;}متى تستخدم: عندما تحتاج إلى إدراج أو حذف عناصر بشكل متكرر في منتصف المجموعة. على سبيل المثال، تنفيذ طابور أو مخزن مؤقت.
4. المكدس (std::stack) والطابور (std::queue)
يعمل المكدس وفق مبدأ LIFO (Last In, First Out - آخر دخول أول خروج). يعمل الطابور وفق مبدأ FIFO (First In, First Out - أول دخول أول خروج). توفر C++ تطبيقات جاهزة لهذه الهياكل من خلال std::stack و std::queue.