รายการที่เชื่อมโยงทวีคูณ: C++, Python (ตัวอย่างโค้ด)

รายการที่เชื่อมโยงแบบทวีคูณคืออะไร?

ในรายการที่มีการเชื่อมโยงแบบทวีคูณ แต่ละโหนดจะมีลิงก์ไปยังโหนดก่อนหน้าและโหนดถัดไป แต่ละโหนดประกอบด้วยสามองค์ประกอบ: หนึ่งโหนดเก็บข้อมูล และอีกสององค์ประกอบคือตัวชี้ของโหนดถัดไปและก่อนหน้า พอยน์เตอร์ทั้งสองนี้ช่วยให้เราไปข้างหน้าหรือข้างหลังจากโหนดใดโหนดหนึ่ง

ต่อไปนี้เป็นโครงสร้างพื้นฐานของรายการที่เชื่อมโยงแบบทวีคูณ

โครงสร้างของรายการที่เชื่อมโยงแบบทวีคูณ
โครงสร้างของรายการที่มีการเชื่อมโยงแบบทวีคูณ

ทุกรายการที่เชื่อมโยงมีโหนดหัวและส่วนท้าย โหนด Head ไม่มีโหนด "ก่อนหน้า" (ตัวชี้ก่อนหน้า) และโหนดส่วนท้ายไม่มีโหนด "ถัดไป"

ต่อไปนี้เป็นคำศัพท์ที่สำคัญสำหรับรายการที่เชื่อมโยงแบบทวีคูณ:

  • ก่อนหน้า: แต่ละโหนดเชื่อมโยงกับโหนดก่อนหน้า มันถูกใช้เป็นตัวชี้หรือลิงค์
  • ถัดไป: แต่ละโหนดเชื่อมโยงกับโหนดถัดไป มันถูกใช้เป็นตัวชี้หรือลิงค์
  • วันที่: ใช้เพื่อเก็บข้อมูลในโหนด “ข้อมูล” สามารถเก็บข้อมูลอื่นๆ โครงสร้างข้อมูล ข้างในนั้น ตัวอย่างเช่น สตริง พจนานุกรม เซต แฮชแมป ฯลฯ สามารถจัดเก็บไว้ใน “ข้อมูล” ได้

นี่คือโครงสร้างพื้นฐานของโหนดเดียวในรายการที่เชื่อมโยงแบบทวีคูณ:

โครงสร้างของโหนดในรายการที่เชื่อมโยงแบบทวีคูณ

โครงสร้างของโหนดในรายการที่เชื่อมโยงแบบทวีคูณ

Operaของรายการเชื่อมโยงแบบทวีคูณ

การดำเนินการของรายการที่เชื่อมโยงคู่ได้แก่ การเพิ่มและการลบโหนด การแทรกและการลบโหนด และการเคลื่อนผ่านรายการที่เชื่อมโยงจากบนลงล่าง

นี่คือรายการการดำเนินการที่เราสามารถดำเนินการได้โดยใช้รายการเชื่อมโยงคู่:

  • แทรกด้านหน้า
  • การแทรกที่ส่วนท้ายหรือโหนดสุดท้าย
  • การแทรกหลังโหนด
  • การแทรกก่อนโหนด
  • ลบออกจากด้านหน้า
  • การลบออกจากส่วนท้าย
  • ค้นหาและลบโหนด
  • ข้ามหัวจรดท้าย
  • เคลื่อนหางไปทางศีรษะ

เราจะเห็นการใช้งานและรหัสเทียมสำหรับการดำเนินการทั้งหมดข้างต้น

การแทรกที่ด้านหน้าของรายการที่เชื่อมโยงแบบทวีคูณ

การแทรกไว้ข้างหน้าหมายความว่าเราจำเป็นต้องสร้างโหนดในรายการที่เชื่อมโยงและวางไว้ที่จุดเริ่มต้นของรายการที่เชื่อมโยง

ตัวอย่างเช่น มีโหนดที่กำหนดเป็น “15” เราจำเป็นต้องเพิ่มสิ่งนี้ลงในโหนดหลัก

นี่คือเงื่อนไขสำคัญสองประการขณะทำการดำเนินการนี้:

  1. โหนดใหม่จะเป็นโหนดหลักหากไม่มีโหนดในรายการที่เชื่อมโยงแบบทวีคูณ
  2. หากมีโหนดหลักอยู่แล้ว โหนดก่อนหน้าจะถูกแทนที่ด้วยโหนดใหม่

นี่คือรหัสเทียมสำหรับการดำเนินการนี้:

function insertAtFront (ListHead, value):
  newNode = Node()
  newNode.value = value
  ListHead.prev = NewNode
  NewNode.next = ListHead
  newNode.prev = NULL
  return ListHead
การแทรกในโหนดด้านหน้า
การแทรกในโหนดด้านหน้า

การแทรกที่ส่วนท้ายของรายการเชื่อมโยงทวีคูณ

“การแทรกที่ส่วนท้าย” หมายความว่าเราจะสร้างโหนดในรายการที่เชื่อมโยงและวางไว้ที่ส่วนท้าย

ในการดำเนินการนี้ เราสามารถใช้สองวิธี:

  • วิธีฮิต: เริ่มต้นการสำรวจจากส่วนหัวของรายการที่เชื่อมโยงแบบทวีคูณจนกระทั่ง "ถัดไป" กลายเป็นโมฆะ จากนั้นเชื่อมโยงโหนดใหม่กับ "ถัดไป"
  • วิธีฮิต: ใช้โหนดสุดท้ายของรายการเชื่อมโยงทวีคูณ จากนั้นโหนด “ถัดไป” ของโหนดสุดท้ายจะเชื่อมโยงกับโหนดใหม่ ตอนนี้โหนดใหม่จะเป็นโหนดส่วนท้าย

นี่คือรหัสหลอกสำหรับการแทรกที่โหนดส่วนท้าย:

function insertAtTail(ListHead, value):
  newNode = Node()
  newNode.value = value
  newNode.next = NULL
  while ListHead.next is not NULL:
	then ListHead = ListHead.next
  newNode.prev = ListHead
  ListHead.next = newNode
  return ListHead
การแทรกที่ส่วนท้ายของรายการที่เชื่อมโยง

การแทรกที่ส่วนท้ายของรายการที่เชื่อมโยง

การแทรกหลังโหนด

สมมติว่าเรามีรายการที่เชื่อมโยงคู่กันดังต่อไปนี้:

การแทรกหลังจากโหนด

เราต้องการแทรกโหนดที่กำหนดซึ่งจะเชื่อมโยงหลังโหนดซึ่งมีค่าเป็น "12"

ต่อไปนี้เป็นขั้นตอนสำหรับสิ่งนี้:

ขั้นตอน 1) สำรวจจากหัวไปยังโหนดสุดท้าย ตรวจสอบว่าโหนดใดมีค่าเป็น "12"

ขั้นตอน 2) สร้างโหนดใหม่และกำหนดให้เป็นตัวชี้ถัดไปของโหนด “12” โหนด "ถัดไป" ของโหนดใหม่จะเป็น 15

นี่คือรหัสหลอกสำหรับการแทรกโหนดหลังโหนดในรายการที่เชื่อมโยงแบบทวีคูณ:

function insertAfter(ListHead, searchItem, value):
  List = ListHead
  NewNode = Node()
  NewNode.value = value
  while List.value is not equal searchItem
	then List = ListHead.next
  List = List.next
  NewNode.next = List.next
  NewNode.prev = List
  List.next = NewNode
การแทรกหลังจากโหนด

การแทรกหลังโหนด

การแทรกก่อนโหนด

การดำเนินการนี้จะคล้ายกับการ "แทรกหลังโหนด" มากกว่า เราต้องค้นหาค่าของโหนดที่ต้องการเพื่อดำเนินการนี้ จากนั้นเราจะสร้างโหนดใหม่และแทรกก่อนโหนดที่ค้นหา

สมมติว่าเราต้องการแทรกโหนดที่กำหนด “15” ก่อนโหนด “12” ต่อไปนี้เป็นขั้นตอนในการทำ:

ขั้นตอน 1) สำรวจรายการที่เชื่อมโยงจากโหนดหลักไปยังโหนดท้าย

ขั้นตอน 2) ตรวจสอบว่าตัวชี้ถัดไปของโหนดปัจจุบันมีค่าเป็น "12" หรือไม่

ขั้นตอน 3) แทรกโหนดใหม่เป็นโหนด "ถัดไป" ของโหนดปัจจุบัน

นี่คือรหัสหลอกสำหรับการแทรกโหนดก่อนโหนดในรายการที่เชื่อมโยงแบบทวีคูณ:

function insertBefore(ListHead, searchItem, value):
  List = ListHead
  NewNode = Node()
  NewNode.value = value
  while List.next.value is not equal searchItem
	then List = ListHead.next
  NewNode.next = List.next
  NewNode.prev = List
  List.next = NewNode
การแทรกโหนดก่อนโหนด

การแทรกโหนดก่อนโหนด

ลบส่วนหัวของรายการที่เชื่อมโยงแบบทวีคูณ

โหนดหลักในรายการที่เชื่อมโยงแบบทวีคูณที่ไม่มีโหนดก่อนหน้า ดังนั้นตัวชี้ถัดไปจะเป็นโหนดหลักใหม่หากเราต้องการลบโหนดหลัก นอกจากนี้เรายังต้องเพิ่มหน่วยความจำที่โหนดครอบครองขณะลบโหนด

ต่อไปนี้เป็นขั้นตอนในการลบโหนดหลัก:

ขั้นตอน 1) กำหนดตัวแปรให้กับโหนดส่วนหัวปัจจุบัน

ขั้นตอน 2) เยี่ยมชมโหนด "ถัดไป" ของโหนดหลักปัจจุบันและสร้างโหนด "ก่อนหน้า" (ตัวชี้ก่อนหน้า) เป็น ''NULL" ซึ่งหมายความว่าเรากำลังตัดการเชื่อมต่อโหนดที่สองจากโหนดแรก

ขั้นตอน 3) ปลดปล่อยหน่วยความจำที่ถูกครอบครองโดยโหนดหลักก่อนหน้า

นี่คือรหัสหลอกสำหรับการลบส่วนหัวออกจากรายการที่เชื่อมโยงแบบทวีคูณ:

function deleteHead(ListHead):
  PrevHead = ListHead
  ListHead = ListHead.next
  ListHead.prev = NULL
  PrevHead.next = NULL
  free memory(PrevHead)
  return ListHead
การลบโหนดหลัก

กำลังลบโหนดส่วนหัว

จำเป็นต้องลบหน่วยความจำที่จัดสรรไว้หลังจากดำเนินการลบใดๆ มิฉะนั้น หน่วยความจำสำหรับบล็อกที่ถูกลบจะยังคงถูกใช้ไปตลอดระยะเวลาที่โปรแกรมทำงาน แอปพลิเคชันอื่นไม่สามารถใช้เซกเมนต์หน่วยความจำนั้นได้

ลบส่วนท้ายของรายการที่เชื่อมโยงแบบทวีคูณ

การดำเนินการนี้คล้ายกับการลบส่วนหัว ในกรณีนี้ แทนที่จะลบส่วนหัว เราต้องลบส่วนท้าย เมื่อต้องการระบุโหนดเป็นโหนดส่วนท้าย เราควรตรวจสอบว่าตัวชี้ถัดไปหรือโหนดถัดไปเป็นค่าว่างหรือไม่ หลังจากลบส่วนท้ายแล้ว เราต้องปลดปล่อยหน่วยความจำ

การดำเนินการนี้เรียกอีกอย่างว่าการ “ลบจากด้านหลัง”

ต่อไปนี้เป็นขั้นตอนในการดำเนินการนี้:

ขั้นตอน 1) เคลื่อนที่ไปจนถึงโหนดท้ายของรายการที่เชื่อมโยงแบบทวีคูณ

ขั้นตอน 2) กำหนดตัวแปรหรือตัวชี้ให้กับโหนดส่วนท้าย

ขั้นตอน 3) ทำให้โหนด "ถัดไป" เป็น NULL และเพิ่มหน่วยความจำของโหนดส่วนท้าย

นี่คือรหัสหลอกสำหรับการลบโหนดท้าย:

function DeleteTail( ListHead ):
  head = ListHead
  while ListHead.next is not NULL:
	ListHead = ListHead.next
  Tail = ListHead.next
  ListHead.next = NULL
  free memory( Tail )
  return head

ลบส่วนท้ายของการเชื่อมโยงทวีคูณ

ค้นหาและลบโหนดออกจากรายการเชื่อมโยงแบบทวีคูณ

การดำเนินการนี้ช่วยให้เราค้นหาข้อมูลโหนดเฉพาะและลบโหนดนั้นได้ เราจำเป็นต้องดำเนินการค้นหาเชิงเส้นเนื่องจากรายการลิงก์เป็นโครงสร้างข้อมูลเชิงเส้น หลังจากลบแล้ว เรายังต้องล้างหน่วยความจำด้วย

ต่อไปนี้เป็นขั้นตอนในการค้นหาและลบโหนดในรายการลิงก์คู่:

ขั้นตอน 1) สำรวจรายการที่เชื่อมโยงจากส่วนหัวจนกว่าค่าของโหนดจะเท่ากับรายการค้นหา

ขั้นตอน 2) กำหนดตัวแปร “deleteNode” ให้กับโหนดที่ตรงกัน

ขั้นตอน 3) กำหนดโหนดก่อนหน้าของ "deleteNode" ให้กับโหนดถัดไป

ขั้นตอน 4) ปลดปล่อยหน่วยความจำของ "deleteNode"

นี่คือซูโดโค้ดสำหรับการค้นหาและลบโหนดจากรายการที่ลิงก์:

function SearchAndDelete(ListHead, searchItem):
  head = ListHead
  while ListHead.next.value not equals searchItem:
	head = head.next
  deleteNode = head.next
  head.next = head.next.next
  head.next.prev = head
  deleteNode.next, deleteNode.next = NULL
  free memory(deleteNode)
  return ListHead
ค้นหาและลบ Operaการ

การค้นหาและการลบการดำเนินการ

สำรวจรายการที่เชื่อมโยงแบบทวีคูณจากไปข้างหน้า

เพื่อสำรวจจากโหนดหลักและวนซ้ำโหนดถัดไปจนกว่าเราจะพบ "NULL" ในขณะที่สำรวจแต่ละโหนด เราสามารถพิมพ์ค่าของโหนดได้ ต่อไปนี้เป็นขั้นตอนในการเคลื่อนที่จากด้านหน้า (ทิศทางไปข้างหน้า):

ขั้นตอน 1) กำหนดตัวชี้หรือตัวแปรให้กับโหนดส่วนหัวปัจจุบัน

ขั้นตอน 2) วนซ้ำไปยังโหนดถัดไปของส่วนหัวจนกระทั่งได้รับ "NULL"

ขั้นตอน 3) พิมพ์ข้อมูลของโหนดในการวนซ้ำแต่ละครั้ง

ขั้นตอน 4) กลับโหนดหัว

นี่คือรหัสหลอกสำหรับการสำรวจรายการที่มีการเชื่อมโยงแบบทวีคูณจากด้านหน้า:

function traverseFromFront(ListHead):
  head = ListHead
  while head not equals to NULL:
	print head.data
	head = head.next
  return ListHead

ที่นี่ การส่งคืนไม่ใช่ข้อบังคับ แต่การส่งคืนโหนดหลักหลังการดำเนินการถือเป็นแนวทางปฏิบัติที่ดี

สำรวจรายการที่เชื่อมโยงแบบทวีคูณจากด้านหลัง

การดำเนินการนี้เป็นแบบย้อนกลับของการเคลื่อนที่จากด้านหน้า วิธีการนี้เหมือนกัน แต่มีความแตกต่างเล็กน้อย เราต้องเคลื่อนที่ไปยังโหนดสุดท้าย จากนั้นจึงเคลื่อนที่จากโหนดสุดท้ายไปยังโหนดด้านหน้า

ต่อไปนี้เป็นขั้นตอนในการสำรวจรายการที่เชื่อมโยงแบบทวีคูณจากด้านหลัง:

ขั้นตอน 1) เคลื่อนตัวไปเรื่อย ๆ จนกระทั่งถึงส่วนท้าย

ขั้นตอน 2) จากโหนดส่วนท้าย เราจะเคลื่อนที่ไปจนกว่าเราจะได้โหนดก่อนหน้าเป็น "NULL" “ก่อนหน้า” (ตัวชี้ก่อนหน้า) จะเป็นโมฆะสำหรับโหนดหลัก

ขั้นตอน 3) ในการวนซ้ำแต่ละครั้ง เราจะพิมพ์ข้อมูลโหนด

นี่คือรหัสหลอกสำหรับการสำรวจจากด้านหลัง:

function traverseFromBack(ListHead):
  head = ListHead
  while head not equals NULL:
	head = head.next
  tail = head
  while tail not equal to NULL:
	print tail.value
	tail = tail.prev
  return ListHead

ความแตกต่างระหว่างรายการเชื่อมโยงแบบเดี่ยวและแบบทวีคูณ

ข้อแตกต่างที่สำคัญระหว่างรายการ Singly และ Doublely Linked คือจำนวนลิงก์

ความแตกต่างระหว่างรายการเชื่อมโยงแบบเดี่ยวและแบบทวีคูณ

ต่อไปนี้เป็นข้อแตกต่างระหว่างโหนดของรายการที่เชื่อมโยงแบบเดี่ยวและโครงสร้างโหนดของรายการที่เชื่อมโยงแบบทวีคูณ:

สนาม รายการที่เชื่อมโยงเพียงรายการเดียว รายการที่เชื่อมโยงเป็นสองเท่า
โครงสร้าง รายการที่เชื่อมโยงเพียงรายการเดียว มีหนึ่งช่องข้อมูลและหนึ่งลิงค์ไปยังโหนดถัดไป รายการที่เชื่อมโยงแบบทวีคูณมีช่องข้อมูลหนึ่งช่องและลิงก์สองรายการ หนึ่งอันสำหรับโหนดก่อนหน้าและอีกอันสำหรับโหนดถัดไป
การข้ามผ่าน มันสามารถเคลื่อนที่ได้ตั้งแต่หัวจรดท้ายเท่านั้น สามารถเคลื่อนที่ได้ทั้งเดินหน้าและถอยหลัง
หน่วยความจำ ใช้หน่วยความจำน้อยลง ใช้หน่วยความจำมากกว่ารายการที่เชื่อมโยงเพียงรายการเดียว
การเข้าถึง รายการที่เชื่อมโยงแบบเดี่ยวมีประสิทธิภาพน้อยกว่าเนื่องจากใช้ลิงก์เดียวไปยังโหนดถัดไป ไม่มีลิงก์สำหรับโหนดก่อนหน้า รายการที่เชื่อมโยงแบบทวีคูณมีประสิทธิภาพมากกว่ารายการที่เชื่อมโยงแบบเดี่ยว

รายการที่เชื่อมโยงทวีคูณใน C++

#include<iostream>
using namespace std;
  struct node{
  int data;
  struct node *next;
  struct node *prev;
  };
void insertFront(node* &listHead, int value){
  node* newNode = new node();
  newNode->data = value;
  newNode->prev = NULL;
  newNode->next = NULL;
  if(listHead!=NULL)
  {
	listHead->prev = newNode;
  	newNode->next = listHead;
  }
  
  listHead = newNode;
  cout<<"Added "<<value<<" at the front"<<endl;
  }
void insertEnd(node * &listHead, int value){
  if(listHead==NULL)
  {
  	insertFront(listHead,value);
  }
  node* newNode = new node();
  newNode->data = value;
  newNode->prev = NULL;
  newNode->next = NULL;
  node *head = listHead;
  while(head->next!=NULL){
  head = head->next;
  }
  head->next = newNode;
  newNode->prev = head;
  cout<<"Added "<<value<<" at the end"<<endl;
  }
void insertAfter(node * &listHead, int searchValue,int value){
  node* newNode = new node();
  newNode->data = value;
  newNode->prev = NULL;	
  newNode->next = NULL;
  node *head = listHead;	
  while(head->next!=NULL &&  head->data!=searchValue){	
  head = head->next;	
  }	
  newNode->next = head->next;	
  head->next = newNode;	
  newNode->prev = head;	
  if(newNode->next !=NULL){	
  newNode->next->prev = newNode;	
  }	
  cout<<"Inserted "<<value<<" after node "<<searchValue<<endl;
  }
void insertBefore(node * &listHead, int searchValue,int value){
  node* newNode = new node();
  newNode->data = value;
  newNode->prev = NULL;	
  newNode->next = NULL;	
  node *head = listHead;	
  while(head->next!=NULL &&  head->next->data!=searchValue){	
  head = head->next;	
  }	
  newNode->next = head->next;	
  head->next = newNode;	
  newNode->prev = head;	
  if(newNode->next !=NULL){	
  newNode->next->prev = newNode;	
  }	
  cout<<"Inserted "<<value<<" before node "<<searchValue<<endl;	
  }
void traverseFromFront(node *listHead){
  node* head = new node();
  head = listHead;
  cout<<"Traversal from head:\t";	
  while(head!=NULL){	
  cout<<head->data<<"\t ";	
  head = head->next;	
  }	
  cout<<endl;
  }	
void traverseFromEnd(node *listHead){
  node* head = new node();
  head = listHead;	
  cout<<"Traversal from head:\t";	
  while(head->next!=NULL){	
  head = head->next;	
  }	
  node *tail = head;	
  while(tail!=NULL){	
  cout<<tail->data<<"\t";	
  tail = tail->prev;	
  }	
  cout<<endl;	
  }
void searchAndDelete(node **listHead, int searchItem){
  node* head = new node();
  head = (*listHead);
  while(head->next!=NULL &&  head->data!=searchItem){
  head = head->next;
  }
  if(*listHead == NULL || head == NULL) return;
  if((*listHead)->data == head->data){
  	*listHead = head->next;
  }
  if(head->next != NULL){
  	head->next->prev = head->prev;
  }
  
  if(head->prev != NULL){
  	head->prev->next = head->next;
  }
  free(head);
  cout<<"Deleted Node\t"<<searchItem<<endl;
  return;
  }
int main(){
  node *head = NULL;
  insertFront(head,5);
  insertFront(head,6);
  insertFront(head,7);
  insertEnd(head,9);
  insertEnd(head,10);
  insertAfter(head,5,11);
  insertBefore(head,5,20);
  traverseFromFront(head);
  traverseFromEnd(head);
  searchAndDelete(&head,7);
  traverseFromFront(head);
  traverseFromEnd(head);
}

เอาท์พุต

Added 5 at the front
Added 6 at the front
Added 7 at the front
Aded 9 at the end
Added 10 at the end
Inserted 11 after node 5
Inserted 20 before node 5
Traversal from head:    7        6       20      5       11      9       10
Traversal from head:    10      9       11      5       20      6       7
Deleted Node    7
Traversal from head:    6        20      5       11      9       10
Traversal from head:    10      9       11      5       20      6

รายการที่เชื่อมโยงทวีคูณใน Python

class node:
  def __init__(self,data = None, prev=None, next = None):
    self.data = data
    self.next = next
    self.prev = prev
class DoublyLinkedList:
  def __init__(self):
    self.head = None

  def insertFront(self, val):
    newNode = node(data=val)
    newNode.next = self.head
    if self.head is not None:
      self.head.prev = newNode
    self.head = newNode
    print("Added {} at the front".format(val))

  def insertEnd(self,val):
    newNode = node(data=val)
    if self.head is None:
      self.insertFront(val)
    
    temp = self.head
    while temp.next is not None:
      temp = temp.next
    temp.next = newNode
    newNode.prev = temp
    print("Added {} at the end".format(val))

  def traverseFromFront(self):
    temp = self.head
    print("Traversing from head:\t",end="")
    while temp:
      print("{}\t".format(temp.data),end="")
      temp = temp.next
    print()

  def traverseFromEnd(self):
    temp = self.head
    print("Traversing from Tail:\t",end="")
    while temp.next is not None:
      temp = temp.next
    tail = temp
    while tail is not None:
      print("{}\t".format(tail.data),end="")
      tail = tail.prev
    print()
  
  def insertAfter(self,searchItem, value):
    newNode = node(data=value)

    temp = self.head
    while temp.next is not None and temp.data is not searchItem:
      temp = temp.next
    
    newNode.next = temp.next
    temp.next = newNode
    newNode.prev = temp

    if newNode.next is not None:
      newNode.next.prev = newNode
    print("Inserted {} after node {}".format(value,searchItem))

  def insertBefore(self,searchItem, value):
    newNode = node(data=value)

    temp = self.head
    while temp.next is not None and temp.next.data is not searchItem:
      temp = temp.next
    
    newNode.next = temp.next
    temp.next = newNode
    newNode.prev = temp

    if newNode.next is not None:
      newNode.next.prev = newNode
    print("Inserted {} before node {}".format(value,searchItem))

  def searchAndDelete(self,searchItem):
    temp = self.head

    while temp.next is not None and temp.data is not searchItem:
      temp = temp.next
    
    if self.head is None or temp is None:
      return

    if self.head.data is temp.data:
      self.head = temp.next

    if temp.next is not None:
      temp.next.prev = temp.prev
    
    if temp.prev is not None:
      temp.prev.next = temp.next
    
    print("Deleted Node\t{}".format(searchItem))
    return

doublyLinkedList = DoublyLinkedList()
doublyLinkedList.insertFront(5)
doublyLinkedList.insertFront(6)
doublyLinkedList.insertFront(7)
doublyLinkedList.insertEnd(9)
doublyLinkedList.insertEnd(10)
doublyLinkedList.insertAfter(5, 11)
doublyLinkedList.insertBefore(5, 20)
doublyLinkedList.traverseFromFront()
doublyLinkedList.traverseFromEnd()
doublyLinkedList.searchAndDelete(7)
doublyLinkedList.traverseFromFront()
doublyLinkedList.traverseFromEnd()

เอาท์พุต

Added 5 at the front
Added 6 at the front
Added 7 at the front
Added 9 at the end
Added 10 at the end
Inserted 11 after node 5
Inserted 20 before node 5
Traversing from head:   7       6       20      5       11      9       10
Traversing from Tail:   10      9       11      5       20      6       7
Deleted Node    7
Traversing from head:   6       20      5       11      9       10
Traversing from Tail:   10      9       11      5       20      6

ความซับซ้อนของรายการที่เชื่อมโยงแบบคู่

โดยทั่วไปความซับซ้อนของเวลาจะแบ่งออกเป็น 3 ประเภท

พวกเขาจะ:

  1. เคสที่ดีที่สุด
  2. กรณีเฉลี่ย
  3. กรณีที่เลวร้ายที่สุด

ความซับซ้อนของเวลาในกรณีที่ดีที่สุดสำหรับรายการเชื่อมโยงแบบคู่:

  1. การแทรกในส่วนหัวหรือส่วนท้ายจะมีค่าใช้จ่าย O(1) เนื่องจากเราไม่จำเป็นต้องเข้าไปภายในรายการที่เชื่อมโยง ตัวชี้ส่วนหัวและส่วนท้ายสามารถให้เราเข้าถึงโหนดส่วนหัวและส่วนท้ายด้วยความซับซ้อนของเวลา O(1)
  2. การลบที่หัวหรือส่วนท้ายจะมีค่าใช้จ่าย O(1)
  3. การค้นหาโหนดจะมีความซับซ้อนของเวลาเท่ากับ O(1) เนื่องจากโหนดเป้าหมายสามารถเป็นโหนดหัวได้

ความซับซ้อนของเวลาในกรณีเฉลี่ยของรายการเชื่อมโยงแบบคู่:

  1. การแทรกที่หัวหรือหางจะมีความซับซ้อนของเวลาเท่ากับต้นทุน O(1)
  2. การลบที่หัวหรือหางจะมีความซับซ้อนของเวลาและมีต้นทุน O(1)
  3. การค้นหาโหนดจะมีความซับซ้อนของเวลาเท่ากับ O(n) เนื่องจากรายการค้นหาสามารถอยู่ที่ใดก็ได้ระหว่างรายการที่เชื่อมโยง ในที่นี้ "n" คือโหนดทั้งหมดที่มีอยู่ในรายการที่เชื่อมโยง

ความซับซ้อนของเวลาในกรณีเลวร้ายที่สุดของรายการที่เชื่อมโยงแบบคู่จะเหมือนกับกรณีโดยเฉลี่ย

ความซับซ้อนของหน่วยความจำของรายการเชื่อมโยงแบบคู่

ความซับซ้อนของหน่วยความจำคือ O(n) โดยที่ "n" คือจำนวนโหนดทั้งหมด ขณะใช้งานรายการลิงก์ เราต้องปลดปล่อยหน่วยความจำที่ใช้ มิฉะนั้น หากรายการลิงก์มีขนาดใหญ่ขึ้น หน่วยความจำอาจรั่วไหลได้

สรุป

  • รายการที่เชื่อมโยงคือโครงสร้างข้อมูลเชิงเส้นประเภทหนึ่ง ซึ่งเป็นการรวบรวมข้อมูลที่แสดงในลักษณะเส้นตรง
  • รายการที่เชื่อมโยงแบบทวีคูณคือประเภทของรายการที่เชื่อมโยงโดยที่โหนดมีการเชื่อมโยงกับทั้งโหนดก่อนหน้าและถัดไป
  • รายการที่เชื่อมโยงแบบคู่จะมีการดำเนินการทั้งหมด เช่น การเพิ่มโหนด การลบโหนด การแทรกโหนดหลังหรือก่อนโหนดอื่น และการเคลื่อนผ่านรายการที่เชื่อมโยงจากหัวถึงหาง
  • รายการที่เชื่อมโยงแบบทวีคูณมีช่องข้อมูลหนึ่งช่องและลิงก์สองรายการ หนึ่งอันสำหรับโหนดก่อนหน้าและอีกอันสำหรับโหนดถัดไป
  • ความซับซ้อนของรายการเชื่อมโยงคู่ กรณีที่ดีที่สุด กรณีเฉลี่ย
  • และกรณีที่เลวร้ายที่สุด