0Pricing
C Academy · درس

القوائم المرتبطة ثنائية الاتجاه

روابط في الاتجاهين

القوائم المرتبطة ثنائية الاتجاه درس مجاني في C Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C Academy 4 دروس في المجموع.

الروابط في الاتجاهين

تمنح القائمة المرتبطة ثنائية الاتجاه كل عقدة مؤشرين: أحدهما إلى عقدة next والآخر إلى العقدة prev (السابقة).

يتيح لك ذلك التجوّل في القائمة في كلا الاتجاهين، كما يسهّل الحذف.

#include <stdio.h>

struct Node {
    int value;
    struct Node *prev;
    struct Node *next;
};

int main(void) {
    printf("Each node links forward and backward\n");
    return 0;
}

تعريف العقدة

يضيف الهيكل مؤشر prev إلى جانب next. ويكون كلاهما NULL عند طرفي القائمة.

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };

int main(void) {
    struct Node *n = malloc(sizeof(struct Node));
    n->value = 1; n->prev = NULL; n->next = NULL;
    printf("%d\n", n->value);
    free(n);
    return 0;
}

دالة مساعدة للإنشاء

كما في السابق، تتولى دالة مساعدة مركزية عملية التخصيص. وتضبط كلًا من prev وnext إلى NULL.

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };

struct Node *make(int v) {
    struct Node *n = malloc(sizeof(struct Node));
    n->value = v; n->prev = NULL; n->next = NULL;
    return n;
}

int main(void) {
    struct Node *n = make(42);
    printf("%d\n", n->value);
    free(n);
    return 0;
}

ربط العقد في الاتجاهين

عند ربط عقدتين، يجب تحديث الاتجاهين: مؤشر next للعقدة الأولى ومؤشر prev للعقدة الثانية.

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };
struct Node *make(int v){struct Node*n=malloc(sizeof*n);n->value=v;n->prev=NULL;n->next=NULL;return n;}

int main(void) {
    struct Node *a = make(1), *b = make(2);
    a->next = b;
    b->prev = a;
    printf("forward %d, back %d\n", a->next->value, b->prev->value);
    free(a); free(b);
    return 0;
}

الإدراج في البداية

عند الدفع في البداية: يشير next للعقدة الجديدة إلى الرأس القديم، ويشير prev للرأس القديم إلى العقدة الجديدة، ثم ينتقل الرأس إلى العقدة الجديدة.

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };
struct Node *make(int v){struct Node*n=malloc(sizeof*n);n->value=v;n->prev=NULL;n->next=NULL;return n;}

void push(struct Node **head, int v) {
    struct Node *n = make(v);
    n->next = *head;
    if (*head) (*head)->prev = n;
    *head = n;
}

int main(void) {
    struct Node *head = NULL;
    push(&head, 2); push(&head, 1);
    printf("%d %d\n", head->value, head->next->value);
    return 0;
}

التجوّل إلى الأمام

التجوّل إلى الأمام مماثل تمامًا للتجوّل في قائمة مرتبطة أحادية الاتجاه: اتبع next حتى تصل إلى NULL.

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };
struct Node *make(int v){struct Node*n=malloc(sizeof*n);n->value=v;n->prev=NULL;n->next=NULL;return n;}

int main(void) {
    struct Node *a = make(1), *b = make(2);
    a->next = b; b->prev = a;
    for (struct Node *p = a; p; p = p->next) printf("%d ", p->value);
    printf("\n");
    free(a); free(b);
    return 0;
}

التجوّل إلى الخلف

الميزة الكبرى هي أنه يمكنك، من أي عقدة، التجوّل إلى الخلف باتباع مؤشرات prev حتى تصل إلى الرأس.

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };
struct Node *make(int v){struct Node*n=malloc(sizeof*n);n->value=v;n->prev=NULL;n->next=NULL;return n;}

int main(void) {
    struct Node *a = make(1), *b = make(2), *c = make(3);
    a->next = b; b->prev = a; b->next = c; c->prev = b;
    for (struct Node *p = c; p; p = p->prev) printf("%d ", p->value);
    printf("\n");
    free(a); free(b); free(c);
    return 0;
}

الحذف أسهل

لأن كل عقدة تعرف سابقتها، يمكنك حذفها دون البحث عن العقدة السابقة.

ما عليك سوى ربط node->prev بـ node->next في كلا الاتجاهين.

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };
struct Node *make(int v){struct Node*n=malloc(sizeof*n);n->value=v;n->prev=NULL;n->next=NULL;return n;}

void del(struct Node **head, struct Node *n) {
    if (n->prev) n->prev->next = n->next; else *head = n->next;
    if (n->next) n->next->prev = n->prev;
    free(n);
}

int main(void) {
    struct Node *a = make(1), *b = make(2), *c = make(3);
    a->next=b; b->prev=a; b->next=c; c->prev=b;
    struct Node *head = a;
    del(&head, b);
    printf("%d %d\n", head->value, head->next->value);
    return 0;
}

تحديث الجارين

عند إزالة عقدة، أصلح دائمًا قيمة next للعقدة السابقة وقيمة prev للعقدة التالية.

تحقق من NULL عند كل طرف حتى لا تحاول إلغاء إشارة جار غير موجود.

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };
struct Node *make(int v){struct Node*n=malloc(sizeof*n);n->value=v;n->prev=NULL;n->next=NULL;return n;}

int main(void) {
    struct Node *a = make(1), *b = make(2);
    a->next = b; b->prev = a;
    a->next = NULL;
    free(b);
    printf("now only %d remains\n", a->value);
    free(a);
    return 0;
}

الاحتفاظ بمؤشر الذيل

تخزّن كثير من القوائم المرتبطة ثنائية الاتجاه أيضًا مؤشر tail إلى العقدة الأخيرة، مما يتيح الإضافة في النهاية والتكرار من الخلف بدءًا من النهاية بتعقيد O(1).

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *prev; struct Node *next; };
struct Node *make(int v){struct Node*n=malloc(sizeof*n);n->value=v;n->prev=NULL;n->next=NULL;return n;}

int main(void) {
    struct Node *head = make(1), *tail = head;
    struct Node *n = make(2);
    tail->next = n; n->prev = tail; tail = n;
    printf("tail = %d\n", tail->value);
    free(head); free(n);
    return 0;
}

المفاضلات

تستهلك القوائم المرتبطة ثنائية الاتجاه ذاكرة إضافية (مؤشرًا آخر لكل عقدة)، وتتطلب تحديث رابطين عند كل تغيير.

في المقابل، تحصل على تجوّل ثنائي الاتجاه وحذف عقدة معروفة بتعقيد O(1). اختر النوع الذي يلائم احتياجاتك.

#include <stdio.h>

int main(void) {
    printf("Singly: less memory, one-way\n");
    printf("Doubly: more memory, two-way + easy delete\n");
    return 0;
}

تحقق سريع

اختبر مدى فهمك للقوائم المرتبطة ثنائية الاتجاه.

مراجعة

تعلّمت القوائم المرتبطة ثنائية الاتجاه:

  • تحتوي كل عقدة على مؤشري prev وnext.
  • يتطلب الربط تحديث الاتجاهين.
  • يمكنك التجوّل إلى الأمام والخلف، وحذف عقدة معروفة بتعقيد O(1).
  • تتمثل التكلفة في الذاكرة الإضافية وزيادة تحديثات المؤشرات؛ ويتيح مؤشر tail الإضافة في النهاية بتعقيد O(1).

الأسئلة الشائعة

هل درس «القوائم المرتبطة ثنائية الاتجاه» مجاني؟

نعم — نص درس «القوائم المرتبطة ثنائية الاتجاه» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C Academy 4 دروس في المجموع.

ماذا ستتعلم في «القوائم المرتبطة ثنائية الاتجاه»؟

روابط في الاتجاهين تتمرن على C Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C Academy؟

لا تُشترط خبرة سابقة. C Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «القوائم المرتبطة ثنائية الاتجاه»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C Academy هذا؟

نعم. كل درس في C Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. القوائم المرتبطة أحادية الاتجاه
  2. الإدراج والحذف
  3. الاجتياز والبحث
  4. القوائم المرتبطة ثنائية الاتجاه
← العودة إلى C Academy