(1.1) ما هي Linked list
هي باختصار شديد عبارة عن قائمة من البيانات مرتبطة مع بعضها البعض وغير محدودة الحجم. أي انك لا تحتاج
للمعرفة ما عدد البيانات المراد إدخالها إلى القائمة.
(1.2) أهمية Linked list
لابد أنك استخدمت المصفوفات في العديد من برامجك وتجاربك, وكلما تعرف مصفوفة تحتاج للوضع حدود المصفوفة.
ولكن, ماذا لو احتجت أو احتاج مستخدم البرنامج أن يضيف عدد من البيانات يزيد عن حجم المصفوفة ؟!!. اظنك تعرف
ماذا سوف يحدث .
ممكن أن تحل المشكلة باستخدام المصفوفات باستعمال المؤشرات معها بنسخ المصفوفة الممتلئة إلى مصفوفة
جديدة ذات حجم أكبر, و لكن مشكلة هذه الطريقة هي أنك دائما تحقق من عدد العناصر هل تجاوزات حدود المصفوفة
لكي تزيد المساحة لو احتجت لذلك.
ولكن باستخدامك linked list سوف يزيل عنك هم هذه المشكلة وتدخل البيانات كما يحلو لك (لكن لا تنسى حجم
الذاكرة ) , لأنك لا تحتاج هل للمعرفة عدد البيانات المدخلة للقائمة فقط تقول للبيانات (حياكم الله القائمة
قائمتكم) وهذا يعني بتعبير آخر عن linked list كما يطلق عليها البعض على أنها مصفوفة دينامكية.
الان حمل المرفقات .. وهو عباره عن برنامج يستخدم single_linked_list لان الشرح التالي هو لهذا البرنامج
[2] Single Linked List
(2.1) الصيغة العامة للـSingle Linked List :
الصيغة العامة هي كالتالي :
كود:
struct My_List_Name{ // My Data : int, float, char, ... ect // This is the pointer to next node struct My_List_Name *next;};
الصيغة العامة هي عبارة عن struct أو calss (ولكن سوف نستخدم structer) يكون في احد عناصرها مؤشر للنفس
structer كما في السطر الخامس . ووظيفة هذا المؤشر هو الإشارة إلى العنصر التالي من القائمة
انظر هذا المثال :
struct my_list
{
int num;
// This is the pointer to the next node
struct my_list *next;
};
سوف نقوم بتطبيق دوال Single Linked List على هذه structer ولكن سوف نختصر الاسم باستخدام typedef :
كود:
typedef struct my_list node;
سوف نستخدم من بعد الآن node بدل من struct my_list في باقي الشرح.
إذا أردنا أن نستخدم linked list في مكان في البرنامج, فقط نقوم بتعريف مؤشر للتركيب كالتالي :
كود:
struct my_list_name *mypointer = new struct my_List_name;
مثال : هنا نعرف مؤشر التركيب my_list (بعد ما اختصرتا المسمى بـ node) :
كود:
node *mynodes = new node;
وهذا سوف نستخدمه للتعامل مع القائمة من خلال الدالة الرئيسية في البرنامج.
وهذا الشكل مثال على كيفية تخزين العناصر في الذاكرة و الربط بينها :

مثال عن كيفيه تخزين العناصر في الذاكرة
(2.2) بعض العمليات في Single linked list:
هنا سوف نتطرق إلى بعض العمليات المستخدمة في Single linked list ,وهي عبارة عن دوال. أهم نقطة هنا أن
تفهم هذه العمليات (الدوال) لكي تستطيع أن تعدلها بما يناسب مشاريعك.
1 ) إضافة عناصر إلى القائمة :-
عند إضافة عنصر , نتبع آلاتي :
أولا : نضع بيانات التركيب
ثنايا : إذا كان هناك عنصر آخر نشئ تركيب جديد في الذاكرة بواسطة كلمة new ونسند عنوان ذلك التركيب إلى
المؤشر next في العنصر الحالي.
ثالثا : إما إذا كان العنصر الحالي آخر عنصر في القائمة, نسند للمؤشر next قيمة NULL
وهذا مثال على ما سبق :
كود:
// Create List nodesvoid createlist(node *p){ int data; for( ; ; ) { cout<<"Enter Number or zero to stop: "; cin>>data; if(data == 0) { p->next = NULL; break; } else { p->num = data; p->next = new node; p = p->next; } }}
هذه دالة لا تعود بقيمة وتتطلب وسيط وهو مؤشر للتركيب my_list (طبعا وضعنا هنا node بعد ما اختصرتا المسمى
كما ذكر في الأعلى) . هنا يدخل المستخدم مجموعة من الأعداد إلى القائمة المرتبطة و يتم إنهاء العملية عند إدخال
صفر.
الآن لنبدأ الشرح :
كود:
int data;
هنا عرفنا متغير اسمه data من النوع integer .
كود:
for( ; ; )cout<<"Enter Number or zero to stop: ";cin>>data;
الآن ندخل إلى for loop لا نهائية لأننا لا نعرف كم من الأعداد سوف يدخلها المستخدم و من ثم قراءة الرقم المدخل.
كود:
if(data != 0)
هنا نتحقق من الرقم المدخل هل يساوي الصفر أم لا .
إذا كان العدد المدخل لا يساوي الصفر سوف نقوم بالتالي:
كود:
{p->num = data;p->next = new node;p = p->next;}p->num = data;
نسند العدد المدخل (data) للعضو num. لاحظ أننا استخدمنا المعامل السهم (->) بدلا من ( . ) لأننا نتعامل مع مؤشر
للتركيب وليس متغير أو كائن من التركيب .
كود:
p->next = new node;
وبعد ذلك , إنشاءنا تركيب جديد في الذاكرة (new node) و أسندنا عنوانه في الذاكرة إلى المؤشر next . الآن أصبح
لدينا عنصر مرتبط مع الأخر بواسطة المؤشر next .
الآن نريد أن نتقل إلى العنصر الجديد لكي يتم إضافة مدخلاته, نقوم بعملية التحرك هكذا :
كود:
p = p->next;
هنا يتحرك مؤشر التركيب إلى العنصر التالي وذلك بإسناد عنوان العنصر الجديد للمؤشر p الذي تم تخزينه في
المؤشر next .
لاحظ أن العنصر الجديد فارغ أي لا لم يدخله له أي بيانات أو عنوان عنصر آخر. الآن المؤشر p واقف في آخر عنصر تم
إنشاءه (العنصر الفارغ)
إما إذا كان العدد المدخل يساوي صفر أي يريد المستخدم التوقف عن إدخال البيانات سوف نقوم بالتالي :
كود:
{p->next = NULL; break;}
هنا نسند للمؤشر next التابع للعنصر الأخير (الفارغ) قيمة NULL أي انه الآن لا يحتوي على أي عنوان وهذا للدلالة
على أن هذا العنصر آخر عنصر في القائمة . سوف ترى فيما بعد أهميه هذه الخطوة. ومن ثم نخرج من for loop و
الدالة .
في الدالة الرئيسية لبرنامج :
كود:
void main(){ cout<<"Welcome you in Linked List lesson"; cout<<"\nPart 1: Single linked list:"; // defined pointer for structer my_list // that value is the address of first node node *mynodes = new node; // create nodes in mynodes list cout<<"\nCreate nodes in list"; createlist(mynodes); getchr();}
2 – عرض عناصر القائمة :-
الآن سوف نقوم للعرض عناصر القائمة , وهي أن نقوم المرور على كل عنصر و طباعته بياناته. تتم العملية كالتالي :
أولا : نتحقق من العنصر هل هو آخر عنصر في القائمة أما لا.
ثانيا : إذا كان العنصر ليس العنصر الأخير نعرض البيانات ونتحرك إلى العنصر الذي بعده.
ثالثا : إذا كان العنصر هو العنصر الأخير نتوقف .
مثال على العملية :
كود:
// Display all Listvoid display(node *p){ while(p->next != NULL) { cout<<p->num<<endl; p = p->next; }}
هنا لدينا دالة لا تعود بقيمة و تستقبل وسيط مؤشر للتركيب الذي عرفناه في البداية
كود:
while(p->next != NULL)
هنا ندخل إلى while loop وشرطها هو : هل المؤشر next للعنصر لا يساوي NULL, وهذا يعني على هذا العنصر
الأخير أما لا . إذا كان لا استمر , أما إذا كان نعم فأخرج عن loop.
كود:
cout<<p->num<<endl;
اظن ان هذا السطر مفهوم
كود:
p = p->next;
وهذا السطر كما ذكرنا في عملية إضافة العناصر هو التحرك من العنصر الحالي إلى العنصر التالي.
الآن في الدالة الرئيسية للبرنامج :
كود:
// Display nodescout<<"\nDisplay all nodes in list:\n";display(mynodes)
في الحقيقة في عملية العرض أو تعداد أو البحث و ... الخ , يجب علينا المرور على جميع عناصر القائمة , و أسلوب
المرور على جميع العناصر الذي سوف اتبعه سيكون هكذا :
كود:
while (p->next != NULL){ // do what you want // move to the next node p = p->next;}
3 – تعداد عدد عناصر القائمة :
مثل أي عملية تعداد آخرى , أي نعرف عداد ومن ثم يزيد بمقدار واحد. تتم العملية التعداد كالتالي :
أولا : نتحقق من العنصر هل هو آخر عنصر في القائمة أما لا.
ثانيا : إذا كان العنصر ليس العنصر الأخير نزيد العداد بمقدار واحد.
ثالثا : إذا كان العنصر هو العنصر الأخير نتوقف .
هنا نطبق عملية التعداد على التركيب my_list كالتالي :
كود:
// count nodesint countlist(node *p){ int count = 0; while(p->next !=NULL) { count++; p = p->next; } return count;}
هذه الدالة تعيد قيمة من النوع integer و تستقبل (كالعادة) وسيط من النوع مؤشر إلى التركيب my_list .
كود:
int count = 0;
هنا إنشاء العداد count وتم تهيئة للعملية التعداد باسناده القيمة صفر.
الآن نتبع الأسلوب المرور على جميع العناصر
كود:
while(p->next !=NULL) { count++; p = p->next; }
كما ترى بتطبيق الأسلوب السابق , يتم زيادة العداد count كلما كان شرط التكرار صحيحا .
الآن في الدالة الرئيسية للبرنامج :
كود:
// count the number of all nodesint nodescount = countlist(mynodes);cout<<"\nThe number of nodes in list is: "<<nodescount;
4 – بحث عن عنصر في القائمة :
في هذه العملية نقوم في البحث عن عنصر من عناصر القائمة و تمم العملية كالتالي :
أولا : نتحقق من العنصر هل هو آخر عنصر في القائمة أما لا.
ثانيا : إذا كان العنصر ليس العنصر الأخير نتحقق من بيانات العنصر و بيانات البحث
ثالثا : إذا كان بيانات العنصر مطابقة للبيانات البحث, نتوقف
رابع : إذا كان بيانات العنصر غير مطابقة للبيانات البحث, نتحرك للعنصر التالي.
خامسا : إذا كان العنصر هو العنصر الأخير نتوقف .
هنا نطبق عملية البحث على التركيب my_list :
كود:
// Serach for nodenode *serach(node *p){ int n; cout<<"Enter number to serach for it: "; cin>>n; while(p->next !=NULL ) { if(n == p->num) { return p; } else { p = p->next; } } return NULL;}
هذه الدالة من النوع مؤشر للتركيب my_list وتعيد عنوان في الذاكرة والوسيط معروف . الثلاث السطور الأولى و
شرط التكرار while أن شاء الله يكون مفهوم , الآن ندخل داخل التكرار :
كود:
if(n == p->num)
هنا جملة شرطية للتحقق هل القيمة المراد البحث عنها تساوي قيمة العضو num في العنصر الحالي .
كود:
return p;
إذا تحقق الشرط السابق , نقوم بارسال عنوان العنصر الذي حقق الشرط (لاحظ أن عمل الدالة لقد توقف أي انه لن
السطور الباقية إذا تحقق الشرط )
كود:
else{ p = p->next;}
إذا لم يتحقق الشرط سوف نتحرك إلى العنصر التالي.
كود:
return NULL;
إذا لم نجد العنصر (أي أننا خرجنا من التكرار) سوف نرسل القيمة NULL وذلك للدلالة على أن لم يتم إيجاد العنصر.
نضيف إلى دالة الرئيسية للبرنامج :
كود:
// Serach for node in listnode *serachnode; // to take the address from result of serach function.serachnode = serach(mynodes); // making serachif(serachnode != NULL) // check if find it or not.{ cout<<"\nWe find it in list";}else{ cout<<"\nCannot find it in list.";}
- إضافة في أول القائمة :-
الآن نريد إضافة عنصر جديد في أول القائمة أي أن يكون العنصر الأول. وهذه العملية كالتالي :
أولا : نقوم بإنشاء تركيب (عنصر) جديد و إضافة بياناته.
ثانيا : ثم نربط التركيب مع أول عنصر في القائمة
ثالثا : نسند للمؤشر القائمة عنوان التركيب الجديد.
الآن نطبق هذه العملية على تركيب my_list :
كود:
// Insert at beging of listnode *insertatbeging(node *p){ node *newnode = new node; cout<<"\nENter number"; cin>> newnode->num; newnode->next = p; return newnode;}
وضع الدالة هنا مثل دالة البحث السابقة . الثلاث الأسطر الأول أن شاء الله مفهومه, في السطر الرابع
كود:
newnode->next = p;
هنا أسندنا للمؤشر next للعنصر الجديد عنوان العنصر الأول في القائمة (لاحظ أن المؤشر mynodes في الدالة
الرئيسية للبرنامج لا يتحرك من مكانه و أنما عن طريقه نرسل عنوان أول عنصر للقائمة أي أن الوسيط p قيمته عنوان
العنصر الأول الآن)
كود:
return newnode;
ومن ثم نرسل عنوان العنصر الجديد أي أن mynodes سوف تتغير قيمته ليحمل عنوان العنصر الأول الجديد للقائمة.
الشكل التوضيحي للعملية : 
نضيف إلى الدالة الرئيسية في البرنامج التالي:
كود:
// Insert new node in beging of listmynodes = insertatbeging(mynodes);
إضافة عنصر في مكان محدد في القائمة :
لقد شاهدنا كيفية الإضافة في بداية القائمة , الآن سوف نرى كيفية إضافة عنصر جديد يتم تحديد مكانه من قبل
المستخدم ,أي إذا كانت لدينا هذه العناصر :
1 – 2 – 3 – 4
وردنا إضافة عنصر جديد ولنقل (5) في الموقع هو العنصر (3) سوف يتم إضافة (5) بعد (3) لكي تشكل هذه القائمة :
1 – 2 – 3 – 5 – 4
وتتم العملية كالتالي :
أولا : إدخال موقع للعنصر الجديد
ثانيا : التحقق من وجود الموقع في القائمة أما لا
ثالثا : إذا كان الموقع موجود , نشئ عنصر جديد ووضع بياناته
رابع : نربط الموقع مع العنصر الجديد و نربط العنصر الجديد مع العنصر الذي كان بعد الموقع.
نطبق هذه العلمية على تركيب my_list :
كود:
// inserat at localvoid insertatloc(node *p){ int n,loc; node *locadd, *newnode, *temp; cout<<"\nEnter local: "; cin>>loc; locadd = NULL; while(p->next !=NULL) { if(p->num == loc) { locadd = p; break; } p = p->next; } if(locadd == NULL) { cout<<"\nCannot find the local"; } else { cout<<"\nEnter new number: "; cin>>n; newnode = new node; newnode->num = n; temp = locadd->next; locadd->next = newnode; newnode->next = temp; }}
إذا نحن الآن نعرف وضع هذه الدالة لذا نتقل إلى داخلها:
كود:
int n,loc;
هنا عرفنا المتغيرين من النوع integer , المتغير n نستخدمه فيما بعد للقراءة الرقم الجديد, أما loc فهو للقراءة الموقع
الذي يريد إضافة العنصر الجديد
كود:
node *locadd, *newnode, *temp;
هنا عرفنا ثلاث مؤشرات للتركيب my_list , المؤشر locadd سوف يحتوي على عنوان الموقع , و newnode و temp ثم
تستخدم فيما بعد.
كود:
cout<<"\nEnter local: ";cin>>loc;locadd = NULL;
نقرا الموقع المرغوب ومن ثم نسند NULL للمؤشر locadd وذلك لتحقق من الموقع.
كود:
while(p->next !=NULL){if(p->num == loc) { locadd = p; break; } p = p->next;}
الآن ندخل في تكرار للمرور على جميع العناصر , نتحقق من الموقع المدخل هل هو موجود أما لا , إذا كان الموقع
صحيح نسند عنوان الموقع إلى المؤشر locadd ومن ثم نخرج من التكرار, إذا لم يتحقق الشرط نتحرك للعنصر التالي.
كود:
if(locadd == NULL){ cout<<"\nCannot find the local";}
هذه الجملة الشرطية تحقق في إذا تم إيجاد الموقع في القائمة أما لا , إذا لم يتم إيجاده سوف تكون قيمة locadd
كما هي قبل دخوله التكرار وهي NULL ونعرض للمستخدم بأن الموقع المدخل غير صحيح.
كود:
else{ cout<<"\nEnter new number: "; cin>>n; newnode = new node; newnode->num = n; temp = locadd->next; locadd->next = newnode; newnode->next = temp;}
إذا كان الموقع صحيحا سوف نشئ عنصر جديد ووضع بياناته كما هو الوضع في الأسطر الأربع أولى. وبعد ذلك نقوم
بعملية أبدالية في العناوين :
1 - نقوم بإسناد قيمة المؤشر next للموقع للمؤشر temp للحفاظ عليه مؤقتا.
2 - بعد ذلك نسند للمؤشر next للموقع بعنوان العنصر الجديد.
3 - وبعد ذلك نسند للمؤشر next للعنصر الجديد قيمة المؤشر temp والتي هي عنوان العنصر الذي كان التالي بعد
الموقع ليكون العنصر التالي بعد العنصر الجديد
وهذا الشكل يوضح العملية :
نضيف في الدالة الرئيسية للبرنامج :
كود:
// insert node in localinsertatloc(mynodes);
7 – حذف عنصر من القائمة :-
بعد ما تطرقنا من عمليات الإضافة, سوف نتكلم الآن عن كيفية حذف عنصر محدد من القائمة. والعملية كالتالي :
أولا : إدخال العنصر المراد حذفه.
ثانيا : التحقق من وجود العنصر في القائمة
ثالثا : إذا كان العنصر موجود, التحقق هل هو أول عنصر في القائمة أم لا.
رابعا : إذا كان العنصر هو الأول , نقوم بإسناد للمؤشر القائمة بعنوان العنصر الثاني في القائمة.
خامسا : إذا كان العنصر ليس هو الأول , نقوم ربط العنصر السابق له مع العنصر الذي بعده, ومن ثم حذفه
الآن نطبق العملية على التركيب my_list :
كود:
// remove nodenode *remove(node *p){ int n; node *temp, *s; s = p; cout<<"\nEnter to delete it: "; cin>>n; while(p->next != NULL) { if(p->num == n) { if(p == s) { return p->next; } else { temp->next = p->next; delete p; return s; } } temp = p; p = p->next; } return s;}
كما تلاحظ أن الدالة مثل دالة الإضافة في أول القائمة من ناحية النوع الدالة و الوسائط. في السطر الأول كالعادة تعريف
متغير للقراء الرقم من المستخدم. و السطر التالي قمنا بتعريف مؤشرين وهما s و temp.
كود:
s = p;
هنا أسندنا للمؤشر s قيمة المؤشر p والتي هي الآن عنوان أول عنصر في القائمة.
الآن بعد قراءة القيمة المراد حذفها من القائمة و الدخول إلى تكرار المرور على جميع العناصر
كود:
if(p->num == n)
هذه الجملة الشرطية للتحقق هل القيمة المدخلة هي قيمة العنصر الحالي.
كود:
if(p == s){ return p->next;}
إذا كان العنصر الحالي هو المراد حذفه, هذه الجملة الشرطية تتحقق هل هذا العنصر هو العنصر الأول من القائمة أم لا
(تذكر أننا وضعنا عنوان العنصر الأول للمؤشر s قبل قليل) . إذا كان صحيحا , نعيد عنوان العنصر الثاني للمؤشر القائمة
المستدعي للدالة.
كود:
else{ temp->next = p->next; delete p; return s;}
إذا كان العنصر ليس هو العنصر الأول, سوف نسند للمؤشر next التابع للعنصر السابق الذي يحمل عنوانه المؤشر
temp (سترى كيف تم اسندنا للمؤشر temp عنوان العنصر السابق للعنصر الحالي بعد قليل). بعد ذلك نحذف العنصر
الحالي بواسطة الكلمة المحجوزة delete التي تحذفه من الذاكر ه. وأخيرا نعيد عنوان العنصر الأول للمؤشر القائمة
المحدثة (أي بعد تم عملية الحذف).
كود:
temp = p;p = p->next;
إذا لم يكن العنصر الحالي هو العنصر الذي نريد حذفه ,نسند للمؤشر temp عنوان العنصر الحالي ليكون في الدورة
القادمة من التكرار العنصر السابق (كما رأيت سابقا). وبعد ذلك نتحرك إلى العنصر التالي.
كود:
return s;
إذا لم نجد العنصر في القائمة سوف نعيد عنوان العنصر الأول و كأن لم يحدث شيئا
وهذا الشكل يوضح العملية :
نضيف في الدالة الرئيسية في البرنامج :
كود:
//Delete node from listmynodes = remove(mynodes);
8 – ترتيب عناصر القائمة :
نستطيع أن نقوم بترتيب عناصر القائمة وفقا للشروط التي نريدها. وطريقة ترتيب تصاعدي للعناصر القائمة كما هي في
المصفوفات أي نقارن العنصر الحالي مع جميع عناصر القائمة .
تطبيق العملية على تركيب my_list بترتيب الأعداد تصاعديا :
كود:
// sort listvoid sort(node *p){ node *p1,*p2; int i,j,temp,n; n = countlist(p); for(i = 1; i<n; i++) { p1 = p; p2 = p->next; for(j = 1; j<=(n-i); j++) { if(p1->num > p2->num) { temp = p2->num; p2->num = p1->num; p1->num = temp; } p1 = p1->next; p2 = p2->next; } }}
لنبدأ بشرح الكود :
كود:
node *p1,*p2;
هنا عرفنا مؤشرين للتركيب my_list وهما p1 و p2 لنستخدمها في عملية الترتيب.
كود:
int i,j,temp,n;n = countlist(p);
هنا عرفنا بعض المتغيرات من النوع integer و السطر التالي اسندنا للمتغير n عدد العناصر الموجودة في القائمة وذلك
من طريق دالة countlist التي شرحنها سابقا
كود:
for(i = 1; i<n; i++)
هذه هي حلقة التكرار الأولى التي عدد مرات دورانها هو عدد العناصر الكلي - 1.
كود:
p1 = p;p2 = p->next;
هنا في كل دورة من حلقة التكرار الأولى سوف نسند للمؤشر p1 عنوان العنصر الأول للقائمة, و نسند للمؤشر p2
عنوان العنصر الثاني من القائمة.
كود:
for(j = 1; j<=(n-i); j++)
هذه هي الحلقة الثانية وعدد مرات تكرارها هو : عدد الكلي للعناصر – عدد الدورات التي تمت من الحلقة الأولى (ومع
الدورة الحالية أيضا).
ما معنى هذا ؟ انظر المثال :
لو كان لدينا قائمة عدد عناصرها 10 , ونحن الآن في الحلقة الأولى في العنصر الخامس , فكم سوف يكون عدد دورات
الحلقة الثانية ؟
10 – 5 = 5 دورات
كود:
if(p1->num > p2->num){ temp = p2->num; p2->num = p1->num; p1->num = temp;}
الآن بعد ما ندخل الحلقة الثانية , سوف نقارن العنصر الحالي (p1) مع العنصر التالي (p2) ونقوم بتنفيذ عملية التدبيل
بين القيم (أن شاء الله تكون فاهم كيف تقارن القيم )
كود:
p1 = p1->next;p2 = p2->next;
بعد المقارنة وتدبيل القيم نحرك العنصر p1 إلى العنصر التالي (الذي هو نفسه الآن p2) ومن بعد ذلك نحرك (p2) إلى
العنصر التالي.
نضيف في الدالة الرئيسية للبرنامج :
كود:
// sort nodes in listsort(mynodes);