القائمة المترابطة
آخر تحديث
تخزّن القائمة المترابطة تسلسلاً على شكل سلسلة من العقد، حيث تحمل كل عقدة قيمةً ومؤشراً إلى العقدة التالية. على عكس المصفوفة، العقد ليست متجاورة في الذاكرة - فتتّبع مؤشرات next للتنقل عبر القائمة. اضغط تشغيل في الأعلى لمشاهدة العقد وهي تُربط في البداية والنهاية، وقيمة يجري البحث عنها بالتنقل عبر السلسلة، وعقدة تُزال بإعادة توصيل مؤشر.
الإدراج أو الحذف في البداية هو O(1) لأنك تعيد توجيه الرأس فقط. الوصول إلى موضع في المنتصف أو إلى النهاية هو O(n) لأنه يجب عليك المشي إلى هناك أولاً. هذه المقايضة -أطراف رخيصة، لا وصول عشوائي- هي ما يميّز القائمة المترابطة عن المصفوفة.
التعقيد الزمني
| العملية | التعقيد | ملاحظات |
|---|---|---|
| الإدراج في البداية | O(1) | إعادة توجيه الرأس |
| الإدراج في النهاية | O(n) | المشي إلى النهاية أولاً (O(1) مع مؤشر ذيل) |
| البحث | O(n) | اتّباع مؤشرات next |
| حذف الرأس | O(1) | إعادة توجيه الرأس إلى ما بعدها |
| الوصول بالفهرس | O(n) | لا وصول عشوائي |
القائمة المترابطة مقابل المصفوفة
| الجانب | القائمة المترابطة | المصفوفة |
|---|---|---|
| الذاكرة | عقد متناثرة + مؤشرات | كتلة متجاورة |
| الوصول العشوائي | O(n) | O(1) |
| الإدراج/الحذف في المقدمة | O(1) | O(n) (إزاحة) |
| ملاءمة الذاكرة المؤقتة | ضعيفة | جيدة |
مثال محلول
بناء القائمة [10, 20]، ثم إضافة 5 في المقدمة، ثم حذف 20:
| الخطوة | البنية | الإجراء |
|---|---|---|
| البداية | head -> null | قائمة فارغة |
| إدراج 10 في البداية | head -> 10 -> null | إعادة توجيه الرأس إلى عقدة جديدة يكون next الخاص بها هو الرأس القديم (null) |
| إدراج 20 في النهاية | head -> 10 -> 20 -> null | المشي إلى العقدة 10 وضبط next الخاص بها على عقدة جديدة 20 |
| إدراج 5 في البداية | head -> 5 -> 10 -> 20 -> null | العقدة الجديدة 5 تشير إلى الرأس القديم 10؛ والرأس يشير الآن إلى 5 |
| حذف 20 | head -> 5 -> 10 -> null | المشي إلى 10 وضبط next الخاص بها من 20 إلى null؛ العقدة 20 تُفصل |
متى تستخدم القائمة المترابطة
| استخدمها عندما | تجنّبها عندما |
|---|---|
| تُدرج أو تحذف عند الأطراف (أو عند عقدة تحتفظ بها) أكثر بكثير مما تفهرس | تحتاج إلى وصول عشوائي سريع حسب الموضع (فهرسة O(1)) |
| يتغيّر الحجم كثيراً ولا تريد تكلفة إعادة التحجيم/النسخ | تمرّ بحلقات محكمة حيث تهيمن محلية الذاكرة المؤقتة على الأداء |
| تبني طابوراً أو مكدساً أو قائمة تجاور | الذاكرة محدودة - فكل عقدة تدفع مؤشراً إضافياً لكل عنصر |
| تحتاج إلى مراجع مستقرة إلى عقد تبقى بعد عمليات إدراج في أماكن أخرى | تقرأ في الغالب ونادراً ما تعدّل، فالمصفوفة أبسط وأسرع |
كود Linked List
تنفيذ نظيف وقابل للتشغيل لخوارزمية Linked List بلغات Python, JavaScript, Java, C++, C. اختر لغة، وانسخ الكود، أو افتحه محمّلًا مسبقًا في ساحة تجربة Coddy.
كود Linked List بلغة Python
1class Node:2 def __init__(self, value):3 self.value = value4 self.next = None5
6
7class LinkedList:8 def __init__(self):9 self.head = None10
11 def append(self, value):12 node = Node(value)13 if self.head is None:14 self.head = node15 return16 current = self.head17 while current.next:18 current = current.next19 current.next = node20
21 def find(self, value):22 current = self.head23 while current:24 if current.value == value:25 return True26 current = current.next27 return False28
29 def delete(self, value):30 # Re-link the previous node around the match31 current, prev = self.head, None32 while current:33 if current.value == value:34 if prev is None:35 self.head = current.next36 else:37 prev.next = current.next38 return True39 prev, current = current, current.next40 return False41
42 def __str__(self):43 values, current = [], self.head44 while current:45 values.append(str(current.value))46 current = current.next47 return " -> ".join(values) + " -> None"48
49
50lst = LinkedList()51for value in [3, 7, 1, 9]:52 lst.append(value)53
54print("List: ", lst)55print("find(7): ", lst.find(7))56lst.delete(1)57print("After delete(1):", lst)كود Linked List بلغة JavaScript
1class Node {2 constructor(value) {3 this.value = value;4 this.next = null;5 }6}7
8class LinkedList {9 constructor() {10 this.head = null;11 }12
13 append(value) {14 const node = new Node(value);15 if (!this.head) {16 this.head = node;17 return;18 }19 let current = this.head;20 while (current.next) current = current.next;21 current.next = node;22 }23
24 find(value) {25 for (let n = this.head; n; n = n.next) {26 if (n.value === value) return n;27 }28 return null;29 }30
31 delete(value) {32 if (!this.head) return false;33 if (this.head.value === value) {34 this.head = this.head.next;35 return true;36 }37 // Walk to the node just before the one to remove38 for (let n = this.head; n.next; n = n.next) {39 if (n.next.value === value) {40 n.next = n.next.next;41 return true;42 }43 }44 return false;45 }46
47 toArray() {48 const out = [];49 for (let n = this.head; n; n = n.next) out.push(n.value);50 return out;51 }52}53
54const list = new LinkedList();55for (const value of [10, 20, 30, 40]) list.append(value);56console.log("List:", list.toArray().join(" -> "));57console.log("find(30):", list.find(30) !== null);58list.delete(20);59console.log("After delete(20):", list.toArray().join(" -> "));كود Linked List بلغة Java
1public class Main {2 static class Node {3 int value;4 Node next;5 Node(int value) { this.value = value; }6 }7
8 static Node head;9
10 static void append(int value) {11 Node node = new Node(value);12 if (head == null) { head = node; return; }13 Node cur = head;14 while (cur.next != null) cur = cur.next;15 cur.next = node;16 }17
18 static boolean find(int value) {19 for (Node cur = head; cur != null; cur = cur.next) {20 if (cur.value == value) return true;21 }22 return false;23 }24
25 // Unlink the first node holding value26 static void delete(int value) {27 if (head == null) return;28 if (head.value == value) { head = head.next; return; }29 Node cur = head;30 while (cur.next != null && cur.next.value != value) cur = cur.next;31 if (cur.next != null) cur.next = cur.next.next;32 }33
34 static void print() {35 StringBuilder sb = new StringBuilder();36 for (Node cur = head; cur != null; cur = cur.next) {37 sb.append(cur.value).append(" -> ");38 }39 System.out.println(sb.append("null"));40 }41
42 public static void main(String[] args) {43 append(3); append(7); append(1); append(9);44 print();45 System.out.println("find 7: " + find(7));46 delete(7);47 print();48 System.out.println("find 7: " + find(7));49 }50}كود Linked List بلغة C++
1#include <iostream>2
3struct Node {4 int value;5 Node* next = nullptr;6 explicit Node(int v) : value(v) {}7};8
9struct LinkedList {10 Node* head = nullptr;11
12 void append(int value) {13 Node* node = new Node(value);14 if (head == nullptr) {15 head = node;16 return;17 }18 Node* cur = head;19 while (cur->next != nullptr) cur = cur->next;20 cur->next = node;21 }22
23 bool find(int value) const {24 for (Node* cur = head; cur != nullptr; cur = cur->next) {25 if (cur->value == value) return true;26 }27 return false;28 }29
30 void remove(int value) {31 if (head == nullptr) return;32 if (head->value == value) { // removing the head is a special case33 Node* old = head;34 head = head->next;35 delete old;36 return;37 }38 for (Node* cur = head; cur->next != nullptr; cur = cur->next) {39 if (cur->next->value == value) {40 Node* old = cur->next;41 cur->next = old->next;42 delete old;43 return;44 }45 }46 }47
48 void print() const {49 for (Node* cur = head; cur != nullptr; cur = cur->next) {50 std::cout << cur->value << " -> ";51 }52 std::cout << "null\n";53 }54};55
56int main() {57 LinkedList list;58 for (int value : {10, 20, 30, 40}) list.append(value);59 list.print();60 std::cout << std::boolalpha << "find(30): " << list.find(30) << "\n";61 list.remove(20);62 list.remove(10);63 list.print();64 return 0;65}كود Linked List بلغة C
1#include <stdbool.h>2#include <stdio.h>3#include <stdlib.h>4
5typedef struct Node {6 int value;7 struct Node* next;8} Node;9
10Node* head = NULL;11
12void append(int value) {13 Node* node = malloc(sizeof(Node));14 node->value = value;15 node->next = NULL;16 if (head == NULL) {17 head = node;18 return;19 }20 Node* cur = head;21 while (cur->next != NULL) cur = cur->next;22 cur->next = node;23}24
25bool find(int value) {26 for (Node* cur = head; cur != NULL; cur = cur->next) {27 if (cur->value == value) return true;28 }29 return false;30}31
32void deleteValue(int value) {33 if (head == NULL) return;34 if (head->value == value) { // removing the head is a special case35 Node* old = head;36 head = head->next;37 free(old);38 return;39 }40 for (Node* cur = head; cur->next != NULL; cur = cur->next) {41 if (cur->next->value == value) {42 Node* old = cur->next;43 cur->next = old->next;44 free(old);45 return;46 }47 }48}49
50void printList(void) {51 for (Node* cur = head; cur != NULL; cur = cur->next) {52 printf("%d -> ", cur->value);53 }54 printf("NULL\n");55}56
57int main(void) {58 int values[] = {10, 20, 30, 40};59 for (int i = 0; i < 4; i++) append(values[i]);60 printList();61 printf("find(30): %s\n", find(30) ? "true" : "false");62 deleteValue(20);63 deleteValue(10);64 printList();65 return 0;66}الأسئلة الشائعة حول القوائم المترابطة
ما الفرق بين القائمة المترابطة والمصفوفة؟
O(1) لكن عمليات إدراج/حذف بـ O(n) تزيح العناصر. أما القائمة المترابطة فتخزّن العقد في أي مكان في الذاكرة موصولةً بمؤشرات، مما يمنح عمليات إدراج/حذف بـ O(1) عند موضع معروف لكن وصولاً بـ O(n) لأنه يجب عليك التنقل عبر السلسلة.متى يجب أن أستخدم القائمة المترابطة؟
ما هو التعقيد الزمني للقائمة المترابطة؟
O(1). البحث، أو الوصول إلى النهاية دون مؤشر ذيل، هو O(n) لأنك تتّبع مؤشرات next واحداً تلو الآخر. لا يوجد وصول عشوائي بـ O(1) - فالفهرسة داخل القائمة المترابطة هي O(n).ما الفرق بين القائمة المترابطة الأحادية والمزدوجة؟
next فقط لكل عقدة، لذا يمكنك التنقل في اتجاه واحد ويتطلب حذف عقدة مرجعاً إلى سابقتها. أما القائمة المترابطة المزدوجة فتضيف مؤشر prev، مما يتيح التنقل للخلف والحذف بـ O(1) لعقدة محتفظ بها، مقابل مؤشر إضافي لكل عقدة والمزيد من التدبير عند كل إدراج وحذف.لماذا يكون الإدراج في النهاية O(n) إذا كان الإدراج في البداية O(1)؟
head فقط، وهو عمل ثابت. أما الوصول إلى النهاية فيعني اتّباع مؤشرات next من الرأس حتى النهاية، وهو O(n). الاحتفاظ بمؤشر tail منفصل يجعل الإدراج في النهاية O(1) أيضاً، ولهذا كثيراً ما تتتبّع القوائم الواقعية كلا الطرفين.هل تتمتع القوائم المترابطة بإدراج O(1) في كل مكان؟
O(1) فقط بمجرد أن تحتفظ بمؤشر إلى العقدة التي تُدرج بعدها. أما العثور على ذلك الموضع بالقيمة أو بالفهرس فما زال يكلّف O(n) لأنه يجب عليك التنقل عبر السلسلة للوصول إلى هناك.