در این مقاله از بلاگ کنکور کامپیوتر، میخواهیم به توضیح لیست های پیوندی دوطرفه، نحوه درج و حذف در آن و کاربردهای آن بپردازیم. لیست پیوندی دو طرفه نوعی از لیست پیوندیلیست پیوندی چیست؟ آموزش لیست پیوندی ساده، دو طرفه و حلقویلیست پیوندی چیست؟ این صفحه عالی به آموزش لیست پیوندی ساده، دو طرفه و حلقوی با مثال پرداخته و پیاده سازی و عملیات مهم و کاربردهای لیست پیوندی را گفته است است که در آن، هر گره از 3 جزء تشکیل شده است:
- Prev*: آدرس گره قبلی
- Data: آیتم داده
- Next*: آدرس گره بعدی
در ادامه به بررسی جامع این نوع ساختمان دادهآموزش ساختمان داده و الگوریتمهر ساختمان داده یک نوع فرمت ذخیرهسازی و مدیریت دادهها در کامپیوتر است، که امکان دسترسی و اصلاح کارآمد آن دادهها را برای یکسری از الگوریتمها و کاربردها فراهم میکند، در این صفحه به بررسی و آموزش ساختمان داده و الگوریتم پرداخته شده است میپردازیم.
نمایش لیست پیوندی دوطرفه
بیایید ببینیم چگونه میتوانیم لیست پیوندی دو طرفه را در یک الگوریتمالگوریتم چیست به زبان ساده و با مثال های فراواندر این مقاله به زبان بسیار ساده و با مثال های متعدد توضیح داده شده که الگوریتم چیست و چه کاربردهایی دارد یا سورس کد (Source Code)سورس کد چیست؟ آیا سورس کد یا سورس برنامه قابلیت اجرا دارند؟این مقاله عالی به سورس کد یا سورس برنامه پرداخته؛ همچنین به بررسی اهداف سورس کد، نحوه ساخت سورس کد و اینکه آیا سورس کد ها قابلیت اجرا دارند پرداخته نشان دهیم. فرض کنید ما یک لیست پیوندی دو طرفه داریم:
نمایش لیست پیوندی دوطرفه در کد به شکل زیر است:
struct node {
int data;
struct node *next;
struct node *prev;
}
هر گره ساختار یک آیتم داده، یک اشاره گراشاره گر چیست — اشاره گرها در برنامه نویسیاین صفحه عالی توضیح داده اشاره گر چیست و نحوه تعریف اشاره گرها و همین طور اشاره گرها در برنامه نویسی را بررسی کرده سپس انواع اشاره گرها و کاربرد اشاره گرها را گفته به گره ساختار قبلی و یک اشاره گر به گره ساختار بعدی دارد. اکنون یک لیست ساده با پیوند دو طرفه با سه عنصر ایجاد میکنیم تا نحوه عملکرد آن را درک کنیم.
/* Initialize nodes */
struct node *head;
struct node *one = NULL;
struct node *two = NULL;
struct node *three = NULL;
/* Allocate memory */
one = malloc(sizeof(struct node));
two = malloc(sizeof(struct node));
three = malloc(sizeof(struct node));
/* Assign data values */
one->data = 1;
two->data = 2;
three->data = 3;
/* Connect nodes */
one->next = two;
one->prev = NULL;
two->next = three;
two->prev = one;
three->next = NULL;
three->prev = two;
/* Save address of first node in head */
head = one;
در کد بالا، one, two و three به ترتیب، گرههایی با دادههای 1، 2 و 3 هستند.
- برای گره یک: next آدرس دو را ذخیره می کند و null ،prev را ذخیره میکند. (قبل از آن گرهای وجود ندارد.)
- برای گره دو: next آدرس سه و prev آدرس یک را ذخیره میکند.
- برای گره سه: null ،next را ذخیره میکند (بعد از آن گرهای وجود ندارد) و prev، آدرس دو را ذخیره میکند.
توجه کنید که در مورد گره prev ،head به null و در مورد اشاره گر tail، نقطه بعدی به null است. در اینجا، یک گره head و سه گره tail است.
درج در لیست پیوندی دوطرفه
درج یک گره در یک لیست پیوندی دوطرفه، مشابه درج یک گره در یک لیست پیوندی ساده است، اما برای مدیریت اشارهگر به گره قبلی، کار اضافی لازم است. ما میتوانیم عناصر را در 3 موقعیت مختلف از یک لیست پیوندی دو طرفه درج کنیم:
- درج در ابتدا
- درج در بین گرهها
- درج در انتها
فرض کنید یک لیست دو طرفه با عناصر 1، 2 و 3 به شکل زیر داریم:
درج در ابتدا
بیایید یک گره با مقدار 6 در ابتدای لیست پیوندی دوطرفه که در بالا ایجاد کردیم، اضافه کنیم.
- یک گره جدید ایجاد کنید.
- برای گره جدید، حافظهحافظه در کامپیوتر، همه چیز در مورد حافظه در معماری کامپیوتردر این مقاله به بررسی کامل حافظه در کامپیوتر، انواع حافظه در کامپیوتر، کش، روشهای آدرس دهی کش، نگاشت آدرس و موارد دیگر میپردازیم تخصیص دهید.
- داده را به گره جدید اختصاص دهید.
- اشارهگرهای قبلی و بعدی گره جدید را تنظیم کنید.
- اشارهگر next گره جدید را به اولین گره از لیست پیوندی دوطرفه اشاره دهید.
- اشارهگر prev را به null اشاره دهید.
- گره جدید را به عنوان گره head قرار دهید.
- اشارهگر prev گره اول را به گره جدید اشاره دهید.
- head را به گره جدید ببرید.
در ادامه، کد درج در ابتدای لیست پیوندی دوطرفه آمده است:
// insert node at the front
void insertFront(struct Node** head, int data) {
// allocate memory for newNode
struct Node* newNode = new Node;
// assign data to newNode
newNode->data = data;
// point next of newNode to the first node of the doubly linked list
newNode->next = (*head);
// point prev to NULL
newNode->prev = NULL;
// point previous of the first node (now first node is the second node) to newNode
if ((*head) != NULL)
(*head)->prev = newNode;
// head points to newNode
(*head) = newNode;
}
درج در بین دو گره
بیایید یک گره با مقدار 6 بعد از گره با مقدار 1 در لیست پیوندی دوطرفه اضافه کنیم.
- یک گره جدید ایجاد کنید.
- برای گره جدید، حافظه تخصیص دهید.
- داده را به گره جدید اختصاص دهید.
- اشارهگر next گره جدید و گره قبلی را تنظیم کنید.
- مقدار اشارهگر next از گره قبلی را به اشارهگر next گره جدید اختصاص دهید.
- آدرس گره جدید را به اشارهگر next گره قبلی اختصاص دهید.
- اشارهگر prev گره جدید و گره بعدی را تنظیم کنید.
- مقدار اشارهگر prev گره بعدی را به اشارهگر prev گره جدید اختصاص دهید.
- آدرس گره جدید را به اشارهگر prev گره بعدی اختصاص دهید.
در نهایت لیست پیوندی دو طرفه بعد از این درج، به شکل زیر است:
کد عملیات درج در بین دو گره لیست پیوندی دوطرفه به شکل زیر است:
// insert a node after a specific node
void insertAfter(struct Node* prev_node, int data) {
// check if previous node is NULL
if (prev_node == NULL) {
cout “previous node cannot be NULL”;
return;
}
// allocate memory for newNode
struct Node* newNode = new Node;
// assign data to newNode
newNode->data = data;
// set next of newNode to next of prev node
newNode->next = prev_node->next;
// set next of prev node to newNode
prev_node->next = newNode;
// set prev of newNode to the previous node
newNode->prev = prev_node;
// set prev of newNode’s next to newNode
if (newNode->next != NULL)
newNode->next->prev = newNode;
}
درج در انتها
حال بیایید یک گره با مقدار 6 در انتهای لیست پیوندی دوطرفه اضافه کنیم.
- یک گره جدید ایجاد کنید.
- اشارهگر prev و next گره جدید و گره قبلی را تنظیم کنید.
اگر لیست پیوندی خالی است، گره جدید را به عنوان گره اصلی قرار دهید. در غیر این صورت، به انتهای لیست پیوندی دوطرفه پیمایش کنید و گره جدید را اضافه کنید.
در نهایت، لیست پیوندی دوطرفه بعد از این درج، به شکل زیر است:
کد عملیات درج در انتهای لیست پیوندی دوطرفه به شکل زیر است:
// insert a newNode at the end of the list
void insertEnd(struct Node** head, int data) {
// allocate memory for node
struct Node* newNode = new Node;
// assign data to newNode
newNode->data = data;
// assign NULL to next of newNode
newNode->next = NULL;
// store the head node temporarily (for later use)
struct Node* temp = *head;
// if the linked list is empty, make the newNode as head node
if (*head == NULL) {
newNode->prev = NULL;
*head = newNode;
return;
}
// if the linked list is not empty, traverse to the end of the linked list
while (temp->next != NULL)
temp = temp->next;
// now, the last node of the linked list is temp
// point the next of the last node (temp) to newNode.
temp->next = newNode;
// assign prev of newNode to temp
newNode->prev = temp;
}
آموزش درس ساختمان داده
قبل از مطالعه ادامه مقاله لازم به ذکر است که برای مشاهده و آموزش سایر مباحث مربوط به درس ساختمان داده و الگوریتم میتوانید از فیلمهای زیر استفاده کنید
فیلم ساختمان داده جلسه 1
فیلم ساختمان داده جلسه 1
فیلم ساختمان داده جلسه 2
فیلم ساختمان داده جلسه 3
فیلم ساختمان داده جلسه 4
فیلم ساختمان داده جلسه 5
فیلم ساختمان داده جلسه 6
فیلم ساختمان داده جلسه 7
فیلم ساختمان داده جلسه 8
حل تست ساختمان و الگوریتم جلسه 1
حل تست ساختمان و الگوریتم جلسه 2
حل تست ساختمان و الگوریتم جلسه 3
حل تست ساختمان و الگوریتم جلسه 4
انواع پیمایشهای درخت
نحوه ساخت درخت BST
آموزش درخت B-Tree
بررسی مرتبه ساخت هیپ
آموزش مرتب سازی سریع
آموزش شبکه شار
حل سوالات ساختمان ارشد کامپیوتر 99
حل ساختمان ارشد 95 بخش 1
حل ساختمان ارشد 95 بخش 2
حذف از یک لیست پیوندی دوطرفه
مشابه عملیات درج، ما میتوانیم یک گره را از 3 موقعیت مختلف از یک لیست پیوندی دوطرفه حذف کنیم. فرض کنید یک لیست دوطرفه با عناصر 1، 2 و 3 داریم.
حذف گره از اول لیست پیوندی دوطرفه
فرض کنید گرهای که میخواهیم حذفش کنیم، گره 1 باشد و آنرا del_node بنامیم. اگر گرهای که باید حذف شود در ابتدا باشد، اشارهگرهای head و گره 2 را دوباره مقداردهی میکنیم:
در نهایت لیست پیوندی، به صورت زیر خواهد بود:
کد عملیات حذف از ابتدای لیست پیوندی دوطرفه به صورت زیر است:
if (*head == del_node)
*head = del_node->next;
if (del_node->prev != NULL)
del_node->prev->next = del_node->next;
free(del);
حذف گره داخلی
فرض کنید گرهای که میخواهیم حذفش کنیم، گره 2 باشد و آنرا del_node بنامیم. اگر del_node یک گره داخلی (گره دوم) باشد، باید مقدار next و prev گرههای قبل و بعد از del_node را بازنشانی کنیم؛ برای گره قبل از del_node (یعنی اولین گره) مقدار next گره del_node را به next گره اول اختصاص دهید و برای گره بعد از del_node (یعنی گره سوم) مقدار prev گره del_node را به prev گره سوم اختصاص دهید.
لیست پیوندی دوطرفه پس از حذف عنصر میانی 2، به صورت زیر خواهد شد:
کد عملیات حذف گره میانی در لیست پیوندی دوطرفه به صورت زیر است:
if (del_node->next != NULL)
del_node->next->prev = del_node->prev;
if (del_node->prev != NULL)
del_node->prev->next = del_node->next;
حذف آخرین گره از لیست پیوندی دوطرفه
فرض کنید گرهای که میخواهیم حذفش کنیم، گره 3 باشد و آنرا del_node بنامیم؛ در این حالت، آخرین گره با مقدار 3 از لیست پیوندی دوطرفه را حذف میکنیم و در اینجا، میتوانیم به سادگی del_node را حذف کنیم و اشارهگر next گره قبل از del_node را به NULL تبدیل کنیم.
لیست پیوندی دوطرفه پس از حذف گره 3، به صورت زیر خواهد بود:
کد عملیات حذف از انتهای لیست پیوندی دوطرفه به صورت زیر است:
if (del_node->prev != NULL)
del_node->prev->next = del_node->next;
پیچیدگی زمانی و فضایی
پیچیدگی عملیات درج
- عملیاتهای درجی که نیاز به پیمایش ندارند، پیچیدگی زمانیپیچیدگی زمانی الگوریتم چیست؟ معرفی نماد های مجانبیاین صفحه عالی به معرفی پیچیدگی زمانی الگوریتم پرداخته، همچنین انواع نماد های مجانبی و پیچیدگی زمانی های برخی از الگوریتم های مرتب سازی و جستجو را توضیح داده (1)O دارند.
- عملیاتهای درجی که نیاز به پیمایش دارد، پیچیدگی زمانی (n)O دارد.
- پیچیدگی فضایی عملیات درج، (1)O است.
پیچیدگی عملیات حذف
- تمام عملیاتهای حذف با پیچیدگی زمانی (1)O اجرا میشوند.
- پیچیدگی فضایی عملیات حذف، برابر با (1)O است.
پیاده سازی لیست پیوندی دوطرفه
در ادامه، قطعه کد پیاده سازی لیست پیوندی دوطرفه در زبان پایتونزبان برنامه نویسی پایتون چیست؟ – نحوه شروع و دلایل محبوبیتزبان برنامه نویسی پایتون (Python) چیست؟ این مقاله عالی به بررسی دلایل محبوبیت پایتون، موارد استفاده از پایتون و نحوه شروع به برنامه نویسی پایتون پرداخته آمده است:
import gc
# node creation
class Node:
def __init__(self, data):
self.data = data
self.next = None
self.prev = None
class DoublyLinkedList:
def __init__(self):
self.head = None
# insert node at the front
def insert_front(self, data):
# allocate memory for newNode and assign data to newNode
new_node = Node(data)
# make newNode as a head
new_node.next = self.head
# assign null to prev (prev is already none in the constructore)
# previous of head (now head is the second node) is newNode
if self.head is not None:
self.head.prev = new_node
# head points to newNode
self.head = new_node
# insert a node after a specific node
def insert_after(self, prev_node, data):
# check if previous node is null
if prev_node is None:
print(“previous node cannot be null”)
return
# allocate memory for newNode and assign data to newNode
new_node = Node(data)
# set next of newNode to next of prev node
new_node.next = prev_node.next
# set next of prev node to newNode
prev_node.next = new_node
# set prev of newNode to the previous node
new_node.prev = prev_node
# set prev of newNode’s next to newNode
if new_node.next:
new_node.next.prev = new_node
# insert a newNode at the end of the list
def insert_end(self, data):
# allocate memory for newNode and assign data to newNode
new_node = Node(data)
# assign null to next of newNode (already done in constructor)
# if the linked list is empty, make the newNode as head node
if self.head is None:
self.head = new_node
return
# store the head node temporarily (for later use)
temp = self.head
# if the linked list is not empty, traverse to the end of the linked list
while temp.next:
temp = temp.next
# now, the last node of the linked list is temp
# assign next of the last node (temp) to newNode
temp.next = new_node
# assign prev of newNode to temp
new_node.prev = temp
return
# delete a node from the doubly linked list
def deleteNode(self, dele):
# if head or del is null, deletion is not possible
if self.head is None or dele is None:
return
# if del_node is the head node, point the head pointer to the next of del_node
if self.head == dele:
self.head = dele.next
# if del_node is not at the last node, point the prev of node next to del_node to the previous of del_node
if dele.next is not None:
dele.next.prev = dele.prev
# if del_node is not the first node, point the next of the previous node to the next node of del_node
if dele.prev is not None:
dele.prev.next = dele.next
# free the memory of del_node
gc.collect()
# print the doubly linked list
def display_list(self, node):
while node:
print(node.data, end=”->”)
last = node
node = node.next
# initialize an empty node
d_linked_list = DoublyLinkedList()
d_linked_list.insert_end(5)
d_linked_list.insert_front(1)
d_linked_list.insert_front(6)
d_linked_list.insert_end(9)
# insert 11 after head
d_linked_list.insert_after(d_linked_list.head, 11)
# insert 15 after the seond node
d_linked_list.insert_after(d_linked_list.head.next, 15)
d_linked_list.display_list(d_linked_list.head)
# delete the last node
d_linked_list.deleteNode(d_linked_list.head.next.next.next.next.next)
print()
d_linked_list.display_list(d_linked_list.head)
کاربردهای لیست پیوندی دوطرفه
- برای سیستمهای ناوبری که در آن ناوبری به جلو و عقب مورد نیاز است.
- مرورگرهای وب برای پیمایش به عقب و جلو در صفحات وب، از لیست پیوندی دوطرفه استفاده میکنند.
- LRU (کمترین استفاده اخیر)/MRU (اخیراً استفاده شده) حافظه نهان با استفاده از لیستهای پیوندی دوطرفه ساخته میشود.
- توسط برنامههای مختلف برای حفظ عملکردهای Undo و Redo استفاده میشود.
- در سیستم عاملسیستم عامل چیست به زبان ساده، چرا باید از OS استفاده کنیم؟این مقاله عالی به معرفی سیستم عامل (Operating System|OS) به زبان ساده پرداخته، همچنین بررسی کرده که چرا باید از سیستم عامل استفاده کنیم ، لیستهای پیوندی دوطرفه توسط زمانبندی رشتهرشته یا String چیست ⚡️ نحوه کار با استرینگ در برنامه نویسیاین مقاله به معرفی رشته (String) یا استرینگ در برنامه نویسی، رشته در پایتون، رشته در C++ و همین طور الگوریتمهای معروف مربوط به رشته ها در برنامه نویسی پرداخته برای پیگیری فرآیندهایی که در آن زمان اجرا میشوند، نگهداری میشود.
جمع بندی
لیست پیوندی دو طرفه (Doubly Linked List)، نوع خاصی از لیست پیوندی است که در آن، هر گره دارای اشارهگر به گره قبلی و همچنین گره بعدی در لیست پیوندی است. در این مقاله با این نوع لیست پیوندی آشنا شدیم و همینطور به بررسی و پیادهسازی عملیاتهای اساسی آن پرداختیم؛ همچنین با کاربردها و پیچیدگیهای زمانی و فضایی و پیادهسازی آن آشنا شدیم.
لیست پیوندی دوطرفه چیست؟
لیست پیوندی دوطرفه نوع پیچیدهتری از لیست پیوندی است که حاوی اشارهگر به گره بعدی و همچنین گره قبلی در لیست پیوندی است. این ویژگی مشکل پیمایش معکوس را که در لیست پیوندهای ساده امکانپذیر نبود، حل میکند.
لیست پیوندی دوطرفه برای چه مواردی استفاده می شود؟
به طور کلی، لیست پیوندی برای سیستمهای ناوبری که در آن ناوبری به جلو و عقب مورد نیاز است، مرورگرهای وب برای پیمایش به عقب و جلو در صفحات وب، LRU (کمترین استفاده اخیر)/MRU (اخیراً استفاده شده) حافظه نهان، توسط برنامههای مختلف برای حفظ عملکردهای لغو و مجدد و در سیستمعاملها، برای پیگیری فرآیندهایی که در آن زمان اجرا میشوند، مورد استفاده قرار میگیرد.
مزایای لیست پیوندی دوطرفه چیست؟
مزایای لیست پیوندی دوطرفه بدین شرح است: امکان پیمایش در هر دو جهت جلو و عقب را به دلیل نشانگرهای بعدی و قبلی میدهد، برخلاف لیست پیوندی ساده که امکان پیمایش را تنها در یک جهت میدهد و حذف عناصر در مقایسه با لیست پیوندی ساده، سادهتر است.
معایب لیست پیوندی دوطرفه چیست؟
مزایای لیست پیوندی دوطرفه بدین شرح است: برای ذخیره نشانگرهای اضافی به حافظه بیشتری نیاز دارد، این پیادهسازی پیچیدهتر از لیستهای تک پیوندی است و آنها برای حفظ یکپارچگی لیست، به عملیات اضافی نیاز داشتند.
آرایه بهتر است یا لیست پیوندی دوطرفه؟
در آرایهآموزش آرایه در ساختمان داده به زبان ساده و از 0 تا 100در این مقاله موارد زیر بررسی شده است : 1- آرایه چیست 2- انواع اندیس گذاری در آرایه 3- انواع آرایه 4- محاسبه آدرس در آرایه 5- محاسبه شماره در آرایه 6- آرایه در برنامه نویسی 7- مزایای استفاده از آرایه انجام هر عملیاتی مانند درج، حذف و غیره، زمان بیشتری میبرد. لیست پیوندی هنگام انجام هر عملیاتی مانند درج، حذف و غیره زمان کمتری میبرد. دسترسی به هر عنصر در یک آرایه سریعتر است؛ زیرا عنصر موجود در یک آرایه میتواند مستقیماً از طریق اندیس قابل دسترسی باشد.