🎯 مقدمة:
أغلب لغات البرمجة الحديثة تعتمد على هياكل بيانات جاهزة (List, Vector, Array…)
لكن في C++ أنت بتشتغل “قريب من العتاد”، وبتبني الهياكل بيدك.
وأول هيكل بيانات لازم تفهمه “يدويًا” هو:
القائمة المتصلة Singly Linked List
هياكل مهمة جدًا في:
- تصميم الأنظمة
- الذاكرة الديناميكية
- الـ allocators
- محركات الألعاب
- أنظمة التشغيل
- شبكات البيانات
- الكومبايلرات
- وغيرها…
افهم هذا الدرس، وبصراحة: جميع هياكل البيانات التالية رح تصير “تفاهة” بالنسبة إلك.
🧩 أولًا: ما هي القائمة المتصلة (Singly Linked List)؟
هي “سلسلة” من العقد (Nodes)،
كل عقدة تحتوي على:
- بيانات (data)
- رابط pointer للعقدة التالية (next)
بهذا الشكل:
[ data | next ] → [ data | next ] → [ data | next ] → NULL
✔ الخصائص الأساسية:
- كل عنصر (Node) مخزّن في مكان مختلف من الذاكرة
- لا يوجد فهرسة index (زي المصفوفات)
- للوصول لعنصر معيّن لازم تمشي عقدة عقدة
- الذاكرة ديناميكية 100%
- إضافة وحذف العناصر سريعة جدًا (O(1)) إذا كان عندك pointer للمكان المناسب
- الوصول للعناصر بطيء (O(n))
✔ لاحظ الفرق الأساسي بين Linked List والـ Vector:
| الجانب | Linked List | Vector |
|---|---|---|
| مكان العناصر | مبعثرة في الذاكرة | متواصلة في الذاكرة |
| الإضافة في البداية | سريع جدًا | بطيء |
| الوصول لعناصر | بطيء | سريع جدًا |
| استخدام pointer | لازم | غير ضروري |
| يحتاج مساحة متواصلة | ❌ | ✔ |
🧱 ثانيًا: بنية العقدة (Node Structure)
أول شيء لازم تبنيه:
struct Node {
int data;
Node* next;
};
شرح:
data→ القيمة المخزنةnext→ مؤشر يشير للعقدة التالية- وآخر عقدة يشير next فيها إلى NULL
🧱 ثالثًا: إنشاء قائمة بسيطة
Node* head = nullptr; // القائمة فارغة بالبداية
✨ إنشاء أول عقدة:
Node* node1 = new Node();
node1->data = 10;
node1->next = nullptr;
head = node1;
الهيكل صار هيك:
[10 | NULL]
🚀 رابعًا: إضافة عقدة في بداية القائمة
من أسرع العمليات:
void insertAtHead(Node*& head, int value) {
Node* newNode = new Node();
newNode->data = value;
newNode->next = head;
head = newNode;
}
مثال:
القديمة: [10 → NULL]
بعد إضافة 20:
[20 → 10 → NULL]
──────────────────────────────────────────────
🚀 خامسًا: إضافة عقدة في نهاية القائمة
void insertAtTail(Node*& head, int value) {
Node* newNode = new Node();
newNode->data = value;
newNode->next = nullptr;
if (!head) {
head = newNode;
return;
}
Node* temp = head;
while (temp->next != nullptr)
temp = temp->next;
temp->next = newNode;
}
🚀 سادسًا: الطباعة
void printList(Node* head) {
Node* temp = head;
while (temp != nullptr) {
cout << temp->data << " ";
temp = temp->next;
}
}
🔥 سابعًا: حذف أول عقدة
void deleteHead(Node*& head) {
if (!head) return;
Node* temp = head;
head = head->next;
delete temp;
}
🔥 ثامنًا: حذف عقدة بقيمة معينة
void deleteValue(Node*& head, int value) {
if (!head) return;
if (head->data == value) {
deleteHead(head);
return;
}
Node* temp = head;
while (temp->next && temp->next->data != value)
temp = temp->next;
if (temp->next) {
Node* toDelete = temp->next;
temp->next = toDelete->next;
delete toDelete;
}
}
✨ تاسعًا: البحث في القائمة
bool search(Node* head, int value) {
Node* temp = head;
while (temp != nullptr) {
if (temp->data == value)
return true;
temp = temp->next;
}
return false;
}
🧠 عاشرًا: تحليل الزمن (Time Complexity)
| العملية | الزمن |
|---|---|
| الإضافة في البداية | O(1) |
| الحذف في البداية | O(1) |
| الإضافة في النهاية | O(n) |
| الحذف من الوسط | O(n) |
| البحث | O(n) |
| الوصول لعقدة معينة | O(n) |
ملاحظة مهمة:
لو خزنّا pointer لنهاية القائمة
توصير الإضافة Tail insertion:
O(1)
──────────────────────────────────────────────
🔥 الحادي عشر: تمثيل الرسومي للذاكرة
تخيّل هذا الهيكل:
head → A → B → C → NULL
في الذاكرة، المربعات (العقد) مبعثرة:
0x1000: [A data | next = 0x3300]
0x3300: [B data | next = 0x2800]
0x2800: [C data | next = null]
وhead فقط يخزن عنوان أول عقدة.
🔥 الثاني عشر: لماذا نستخدم Linked List بدل vector؟
✔ عندما تحتاج:
- إدخال سريع في البداية
- حذف سريع من البداية
- التعامل المستمر مع عناصر ديناميكية
- العمل على البيانات المتغيرة باستمرار
- لا تحتاج الوصول السريع عن طريق index
✔ أمثلة الحياة الواقعية:
- Queue في أنظمة التشغيل
- Undo/Redo System
- محركات الذاكرة في الألعاب
- Memory Allocators
- Graph adjacency list
🧩 الثالث عشر: أخطاء خطيرة جداً تحدث مع Linked List
❌ 1. نسيان delete
يعمل Memory Leak
❌ 2. حذف العقدة الخطأ
يؤدي لفقدان جزء من القائمة
❌ 3. استخدام temp->next بدون فحص temp
يؤدي إلى Segmentation Fault
❌ 4. التلاعب بـ head بدون تمريـره reference
فلا يتغير الأصل
❌ 5. المرور على القائمة بعد حذف node
قد تكون الذاكرة invalid
⚡ الرابع عشر: تطبيق عملي كامل لقائمة متصلة
#include <iostream>
using namespace std;
struct Node {
int data;
Node* next;
};
void insertAtHead(Node*& head, int val) {
Node* n = new Node();
n->data = val;
n->next = head;
head = n;
}
void insertAtTail(Node*& head, int val) {
Node* n = new Node();
n->data = val;
n->next = nullptr;
if (!head) {
head = n;
return;
}
Node* temp = head;
while (temp->next)
temp = temp->next;
temp->next = n;
}
void printList(Node* head) {
while (head) {
cout << head->data << " ";
head = head->next;
}
cout << endl;
}
int main() {
Node* head = nullptr;
insertAtHead(head, 10);
insertAtTail(head, 20);
insertAtTail(head, 30);
insertAtHead(head, 5);
printList(head); // 5 10 20 30
}
🎯 الخلاصة النهائية:
القائمة المتصلة (Singly Linked List) هي أحد أهم هياكل البيانات الأساسية.
✔ تتكون من Nodes تحتوي على data و pointer
✔ زيادة الحجم ديناميكية
✔ إضافة وحذف سريع في البداية
✔ وصول بطيء
✔ استعمال قوي جدًا لبرمجة الأنظمة
✔ أساس كثير من الهياكل مثل:
– Doubly Linked List
– Stack
– Queue
– Hash chaining
– Adjacency list
وما تعلمته اليوم هو حجر الأساس لفهم:
- الذاكرة
- المؤشرات
- تخصيص الهيبات
- هياكل البيانات المتقدمة
مستواك يرتفع رسميًا بعد هذا الدرس.
اكتشاف المزيد من كود التطور
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.


