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

⚡ สรุปอย่างชาญฉลาด

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

  • 🧩 โครงสร้างของโหนด: แต่ละโหนดจะเก็บฟิลด์ข้อมูลหนึ่งฟิลด์และอีกหนึ่งฟิลด์ ถัดไป ตัวชี้ไปยังโหนดถัดไป; โหนดท้าย ถัดไป ตัวชี้เป็นค่าว่าง (NULL)
  • 📦 รายการเทียบกับอาร์เรย์: โครงสร้างข้อมูลแบบลิสต์เชื่อมโยงเดี่ยว (Singly Linked List) นิยมใช้เมื่อไม่ทราบจำนวนองค์ประกอบ ไม่จำเป็นต้องเข้าถึงข้อมูลแบบสุ่ม และการแทรกข้อมูลกลางลิสต์เป็นเรื่องปกติ
  • การแทรก: สามารถเพิ่มโหนดได้ที่ส่วนหัว ส่วนท้าย หลังโหนดที่ตรงกัน หรือก่อนโหนดที่ตรงกัน โดยใช้การเขียนทับตัวชี้ถัดไป (next-pointer rewrites)
  • การลบ: การลบส่วนหัว ส่วนท้าย หรือโหนดที่ค้นหา จะอัปเดตตัวชี้ไปยังโหนดข้างเคียงและปล่อยหน่วยความจำที่ถูกปล่อยคืนเพื่อป้องกันการรั่วไหลของหน่วยความจำ
  • 🔁 การเดินทาง: เนื่องจากไม่มีตัวชี้ไปยังรายการก่อนหน้า จึงรองรับเฉพาะการท่องไปข้างหน้าเท่านั้น ดังนั้นจึงไม่สามารถท่องย้อนกลับในรายการเชื่อมโยงเดี่ยวได้
  • ???? C++ และ Python Code: ตัวอย่างการใช้งานที่สมบูรณ์จะแสดงขั้นตอนการแทรก การลบ การค้นหา และการวนซ้ำ พร้อมเอาต์พุตที่สามารถเรียกใช้งานได้
  • 📊 ซับซ้อน: การแทรกหรือลบส่วนหัวคือ O(1); การค้นหาและการแทรกและการลบอื่นๆ คือ O(n); ความซับซ้อนของพื้นที่คือ O(n)

รายการที่เชื่อมโยงเพียงรายการเดียว

รายการเชื่อมโยงเดี่ยวคืออะไร?

รายการเชื่อมโยงเดี่ยว (Singly Linked List) เป็นโครงสร้างข้อมูลเชิงเส้นและทิศทางเดียว โดยข้อมูลจะถูกจัดเก็บไว้ในโหนด และแต่ละโหนดจะเชื่อมต่อกับโหนดถัดไปผ่านลิงก์ แต่ละโหนดประกอบด้วยฟิลด์ข้อมูลและลิงก์ไปยังโหนดถัดไป รายการเชื่อมโยงเดี่ยวสามารถเข้าถึงได้ในทิศทางเดียวเท่านั้น ในขณะที่รายการเชื่อมโยงหลายทาง (Dynamic List) สามารถเข้าถึงได้ในทิศทางเดียว รายการที่เชื่อมโยงเป็นสองเท่า สามารถสัญจรได้ทั้งสองทิศทาง

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

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

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

เหตุใดจึงควรใช้โครงสร้างข้อมูลแบบ Linked List แทน Array?

มีหลายสถานการณ์ที่การใช้ Linked List เหมาะสมกว่าการใช้ Intégrédient แถว:

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

Operaของรายการลิงค์เดี่ยว

โครงสร้างข้อมูลแบบรายการเชื่อมโยงเดี่ยว (Singly Linked List) เหมาะสำหรับการจัดสรรหน่วยความจำแบบไดนามิก รองรับการดำเนินการมาตรฐานของรายการเชื่อมโยง เช่น การแทรก การลบ การค้นหา การอัปเดต การรวมสองรายการ และการวนซ้ำ

บทความนี้จะกล่าวถึงการดำเนินการต่อไปนี้:

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

นี่คือตัวอย่างของโครงสร้างข้อมูลแบบลิสต์เชื่อมโยงที่มีสี่โหนด

ตัวอย่างรายการลิงค์เดี่ยว

ตัวอย่างรายการลิงค์เดี่ยว

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

นี่เป็นกระบวนการง่ายๆ โดยทั่วไปเรียกว่าการเพิ่มข้อมูลลงในรายการเชื่อมโยงเดี่ยว (Singly Linked List) คือการสร้างโหนดใหม่และวางไว้ที่หัวของรายการ

ในการดำเนินการนี้ ต้องปฏิบัติตามเงื่อนไขสำคัญสองประการดังนี้:

  1. หากรายการว่างเปล่า โหนดที่สร้างขึ้นใหม่จะกลายเป็นโหนดหัว และโหนดนั้นจะเป็นโหนดหัว ถัดไป ตัวชี้เป็นค่าว่าง (NULL)
  2. ถ้าลิสต์ไม่ว่างเปล่า โหนดใหม่จะกลายเป็นโหนดหัว และโหนดนั้นจะ... ถัดไป ตัวชี้ชี้ไปยังโหนดหัวก่อนหน้า

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

function insertAtHead(head, value):
  newNode = Node(value)
  if head is NULL:
    head = newNode
    return head
  else:
    newNode.next = head
    return newNode

การใส่ที่ศีรษะ

การใส่ที่ศีรษะ

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

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

ขั้นตอน 1) เดินทางไปจนถึง ถัดไป ตัวชี้ของโหนดปัจจุบันจะกลายเป็น NULL

ขั้นตอน 2) สร้างโหนดใหม่ด้วยค่าที่ระบุ

ขั้นตอน 3) กำหนดโหนดใหม่เป็นโหนดถัดไปของโหนดส่วนท้าย

รหัสเทียมสำหรับการแทรกข้อมูลที่ส่วนท้ายของลิสต์ที่มีสมาชิกเพียงตัวเดียว:

function insertAtEnd(head, value):
  newNode = Node(value)
  if head is NULL:
    head = newNode
    return head
  while head.next is not NULL:
    head = head.next
  head.next = newNode
  newNode.next = NULL

การใส่ที่ส่วนท้าย

การสอดเข้าที่ส่วนหาง

การแทรกหลังโหนดในรายการเชื่อมโยงเดี่ยว

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

ขั้นตอน 1) วนลูปไปเรื่อยๆ จนกว่าค่าของโหนดปัจจุบันจะเท่ากับสิ่งที่ต้องการค้นหา

ขั้นตอน 2) ตั้งค่าโหนดใหม่ ถัดไป ตัวชี้ไปยังโหนดปัจจุบัน ถัดไป ตัวชี้

ขั้นตอน 3) ชี้ไปยังโหนดปัจจุบัน ถัดไป ตัวชี้ไปยังโหนดใหม่

รหัสเทียม:

function insertAfter(head, value, searchItem):
  newNode = Node(value)
  while head.value != searchItem:
    head = head.next
  newNode.next = head.next
  head.next = newNode

การแทรกโหนดหลังโหนดในรายการที่เชื่อมโยงเดี่ยว

การแทรกโหนดหลังโหนดใน Singly Linked List

การแทรกก่อนโหนดในรายการเชื่อมโยงเดี่ยว

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

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

ขั้นตอน 2) สร้างโหนดใหม่และตั้งค่าโหนดนั้น ถัดไป ตัวชี้ไปยังโหนดปัจจุบัน ถัดไป.

ขั้นตอน 3) ชี้ไปยังโหนดปัจจุบัน ถัดไป ไปยังโหนดใหม่

function insertBefore(head, value, searchItem):
  newNode = Node(value)
  while head.next.value != searchItem:
    head = head.next
  newNode.next = head.next
  head.next = newNode

การแทรกโหนดก่อนโหนดในรายการที่เชื่อมโยงเดี่ยว

การแทรกโหนดก่อนโหนดในรายการที่เชื่อมโยงเดี่ยว

ลบส่วนหัวของรายการเชื่อมโยงเดี่ยว

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

ขั้นตอน 1) กำหนดให้โหนดถัดไปจากส่วนหัวเป็นส่วนหัวใหม่

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

ขั้นตอน 3) คืนโหนดส่วนหัวใหม่

function deleteHead(head):
  temp = head
  head = head.next
  free(temp)
  return head

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

การลบส่วนหัวของลิงค์ลิสต์

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

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

ขั้นตอน 1) เดินทางไปจนถึงก่อนถึงโหนดสุดท้าย บันทึกโหนดปัจจุบันไว้

ขั้นตอน 2) ปล่อยหน่วยความจำของโหนดถัดไป (โหนดท้าย)

ขั้นตอน 3) ตั้งค่าโหนดถัดไปของโหนดปัจจุบันเป็น NULL

function deleteTail(head):
  while head.next.next is not NULL:
    head = head.next
  free(head.next)
  head.next = NULL

การลบส่วนท้ายของ Singly Linked List

การลบส่วนท้ายของ Singly Linked List

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

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

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

ขั้นตอน 2) หากพบการจับคู่ ให้บันทึกตัวชี้ไปยังโหนดปัจจุบัน

ขั้นตอน 3) การขอ ถัดไป โหนดก่อนหน้าจะกลายเป็นโหนดถัดไปของโหนดปัจจุบัน

ขั้นตอน 4) ลบโหนดปัจจุบันและเพิ่มพื้นที่ว่างในหน่วยความจำของโหนดนั้น

function searchAndDelete(head, searchItem):
  while head.next.next is not NULL and head.next.value != searchItem:
    head = head.next
  temp = head.next
  head.next = head.next.next
  free(temp)

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

ค้นหาและลบโหนดออกจาก Singly Linked List

ท่องไปในรายการเชื่อมโยงเดี่ยว

โครงสร้างข้อมูลแบบรายการเชื่อมโยงเดี่ยว (Singly Linked List) รองรับการท่องไปได้เฉพาะจากหัวไปท้ายเท่านั้น ไม่มีตัวชี้ไปยังโหนดก่อนหน้า ดังนั้นจึงไม่สามารถท่องย้อนกลับได้ แต่ละโหนดจะถูกเยี่ยมชมทีละโหนด โดยพิมพ์ค่าของโหนดนั้นไปเรื่อยๆ จนกว่าจะถึงค่า NULL

ขั้นตอน 1) วนลูปผ่านแต่ละโหนดจนกว่าจะพบค่า NULL

ขั้นตอน 2) พิมพ์ค่าของโหนดปัจจุบัน

function traverse(head):
  while head is not NULL:
    print head.value
    head = head.next

ตัวอย่างรายการลิงค์เดี่ยวใน C++

#include<iostream>
using namespace std;
struct Node{
  int data;
  struct Node *next;
};
void insertAtHead(Node* &head, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  if(head != NULL){
    newNode->next = head;
  }
  head = newNode;
  cout<<"Added "<<newNode->data<<" at the front"<<endl;
}
void insertEnd(Node* &head, int value){
  if(head == NULL){
    insertAtHead(head, value);
    return;
  }
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *temp = head;
  while(temp->next != NULL){
    temp = temp->next;
  }
  temp->next = newNode;
  cout<<"Added "<<newNode->data<<" at the end"<<endl;
}
void searchAndDelete(Node **headPtr, int searchItem){
  Node *temp = NULL;
  if((*headPtr)->data == searchItem){
    temp = *headPtr;
    *headPtr = (*headPtr)->next;
    free(temp);
  } else {
    Node *currentNode = *headPtr;
    while(currentNode->next != NULL){
      if(currentNode->next->data == searchItem){
        temp = currentNode->next;
        currentNode->next = currentNode->next->next;
        free(temp);
        break;
      } else {
        currentNode = currentNode->next;
      }
    }
  }
  cout<<"Deleted Node\t"<<searchItem<<endl;
}
void insertAfter(Node* &headPtr, int searchItem, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *head = headPtr;
  while(head->next != NULL && head->data != searchItem){
    head = head->next;
  }
  newNode->next = head->next;
  head->next = newNode;
  cout<<"Inserted "<<value<<" after node\t"<<searchItem<<endl;
}
void insertBefore(Node* &headPtr, int searchItem, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *head = headPtr;
  while(head->next != NULL && head->next->data != searchItem){
    head = head->next;
  }
  newNode->next = head->next;
  head->next = newNode;
  cout<<"Inserted "<<value<<" before node\t"<<searchItem<<endl;
}
void traverse(Node *headPointer){
  Node* tempNode = headPointer;
  cout<<"Traversal from head:\t";
  while(tempNode != NULL){
    cout<<tempNode->data;
    if(tempNode->next)
      cout<<" --> ";
    tempNode = tempNode->next;
  }
  cout<<endl;
}
int main(){
  Node *head = NULL;
  insertAtHead(head, 5);
  insertAtHead(head, 6);
  insertAtHead(head, 7);
  insertEnd(head, 9);
  traverse(head);
  searchAndDelete(&head, 6);
  traverse(head);
  insertAfter(head, 7, 10);
  insertBefore(head, 9, 11);
  traverse(head);
}

เอาท์พุต

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

ตัวอย่างรายการลิงค์เดี่ยวใน Python

class Node:
  def __init__(self, data=None, next=None):
    self.data = data
    self.next = next
class SinglyLinkedList:
  def __init__(self):
    self.head = None
  def insertAtHead(self, value):
    newNode = Node(data=value)
    if self.head is not None:
      newNode.next = self.head
    self.head = newNode
    print(f'Added {newNode.data} at the front.')
  def insertAtEnd(self, value):
    if self.head is None:
      self.insertAtHead(value)
      return
    newNode = Node(value)
    temp = self.head
    while temp.next is not None:
      temp = temp.next
    temp.next = newNode
    print(f'Added {newNode.data} at the end.')
  def searchAndDelete(self, searchItem):
    if self.head is None:
      return
    if self.head.data == searchItem:
      self.head = self.head.next
      print(f'Deleted node\t{searchItem}')
      return
    currentNode = self.head
    while currentNode.next is not None:
      if currentNode.next.data == searchItem:
        currentNode.next = currentNode.next.next
        print(f'Deleted node\t{searchItem}')
        return
      currentNode = currentNode.next
  def insertAfter(self, searchItem, value):
    newNode = Node(data=value)
    temp = self.head
    while temp.next is not None and temp.data != searchItem:
      temp = temp.next
    newNode.next = temp.next
    temp.next = newNode
    print(f'Inserted {value} after node\t{searchItem}')
  def insertBefore(self, searchItem, value):
    newNode = Node(data=value)
    temp = self.head
    while temp.next is not None and temp.next.data != searchItem:
      temp = temp.next
    newNode.next = temp.next
    temp.next = newNode
    print(f'Inserted {value} before node\t{searchItem}')
  def traverse(self):
    temp = self.head
    print("Traversing from head:\t", end="")
    while temp:
      print("{}\t".format(temp.data), end="")
      temp = temp.next
    print()
singlyLinkedList = SinglyLinkedList()
singlyLinkedList.insertAtHead(5)
singlyLinkedList.insertAtHead(6)
singlyLinkedList.insertAtHead(7)
singlyLinkedList.insertAtEnd(9)
singlyLinkedList.traverse()
singlyLinkedList.searchAndDelete(6)
singlyLinkedList.traverse()
singlyLinkedList.insertAfter(7, 10)
singlyLinkedList.insertBefore(9, 11)
singlyLinkedList.traverse()

เอาท์พุต

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

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

ความซับซ้อนมีสองประเภท ได้แก่ ความซับซ้อนเชิงเวลาและความซับซ้อนเชิงพื้นที่ สำหรับโครงสร้างข้อมูลแบบรายการเชื่อมโยงเดี่ยว (Singly Linked List) ความซับซ้อนเชิงเวลาในกรณีที่เลวร้ายที่สุดและกรณีเฉลี่ยจะเท่ากัน

ความซับซ้อนของเวลาในกรณีที่ดีที่สุด:

  • การแทรกที่ส่วนหัวสามารถทำได้ในเวลา O(1) ไม่จำเป็นต้องวนลูปภายในลิสต์
  • การค้นหาและการลบสามารถทำได้ใน O(1) หากองค์ประกอบเป้าหมายอยู่ที่โหนดหัว

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

  • การแทรกข้อมูลลงในลิสต์แบบเชื่อมโยงใช้เวลา O(n) โดยที่ n คือจำนวนองค์ประกอบทั้งหมด
  • การค้นหาและการลบอาจใช้เวลา O(n) เช่นกัน เนื่องจากองค์ประกอบเป้าหมายสามารถอยู่ที่ใดก็ได้จนถึงโหนดสุดท้าย

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

โครงสร้างข้อมูลแบบลิสต์เดี่ยว (Singly Linked List) จะจัดสรรหน่วยความจำแบบไดนามิก เพื่อจัดเก็บข้อมูล n องค์ประกอบต่างๆ ที่จัดสรร n หน่วยความจำ ดังนั้นความซับซ้อนของพื้นที่จึงเป็น O(n)

การประยุกต์ใช้รายการเชื่อมโยงเดี่ยว

โครงสร้างข้อมูลแบบลิสต์เดี่ยว (Singly Linked Lists) ปรากฏในหลายๆ ที่ซึ่งการท่องไปในทิศทางเดียวเท่านั้นและหน่วยความจำแบบไดนามิกมีประโยชน์:

  • สแต็กและคิว: ระบบจัดเก็บข้อมูลพื้นฐานสำหรับสแต็ก LIFO และคิว FIFO ที่สร้างขึ้นจากโหนด
  • การเชื่อมโยงตารางแฮช: การชนกันของข้อมูลจะได้รับการแก้ไขโดยการเชื่อมโยงรายการต่างๆ เข้ากับรายการเชื่อมโยงแบบเดี่ยว (Singly Linked List) ในแต่ละบัคเก็ต
  • รายการที่อยู่ติดกัน: กราฟแบบเบาบางใช้รายการเชื่อมโยงแบบเดี่ยว (Singly Linked List) ของเพื่อนบ้านสำหรับแต่ละจุดยอด
  • ตารางสัญลักษณ์: คอมไพเลอร์และอินเตอร์พรีเตอร์จะเชื่อมโยงตัวระบุต่างๆ เข้าด้วยกันเป็นรายการเชื่อมโยงเดี่ยว (Singly Linked List) ต่อขอบเขตหนึ่งๆ
  • ตัวจัดสรรหน่วยความจำ: ผู้จัดสรรรายการอิสระ tracมีบล็อกว่าง k บล็อกในรูปแบบรายการเชื่อมโยงเดี่ยว (Singly Linked List)

คำถามที่พบบ่อย

โครงสร้างข้อมูลแบบ Singly Linked List เชื่อมโยงตัวอย่างการฝึกฝน ชุดข้อมูลขนาดเล็ก และบล็อกหน่วยความจำว่างภายในเฟรมเวิร์ก AI ทำให้เกิดคิวแบบไดนามิกสำหรับข้อมูลขาเข้าแบบสตรีมมิ่ง และไปป์ไลน์ข้อมูลแบบไร้การล็อกที่สามารถปรับขนาดได้ตามความต้องการของโมเดล

ใช่แล้ว GitHub Copilot และ GPT สามารถสร้าง Singlely Linked List แบบเต็มรูปแบบในภาษา C ได้ C++, Java, Pythonหรือ Javaสคริปต์ รวมถึงการแทรก การลบ การย้อนกลับ การตรวจจับวงจร และการทดสอบหน่วย

โครงสร้างข้อมูลแบบรายการเชื่อมโยงเดี่ยว (Singly Linked List) มีตัวชี้ไปยังโหนดถัดไปเพียงตัวเดียว และเคลื่อนที่ไปข้างหน้าได้เท่านั้น ส่วนโครงสร้างข้อมูลแบบรายการเชื่อมโยงคู่ (Doubly Linked List) มีทั้งตัวชี้ไปยังโหนดถัดไปและโหนดก่อนหน้า และเคลื่อนที่ได้ทั้งสองทิศทาง แต่ใช้หน่วยความจำต่อโหนดมากกว่า

การใช้งานทั่วไป ได้แก่ การใช้งานสแต็กและคิว การเชื่อมโยงตารางแฮช รายการความสัมพันธ์สำหรับกราฟแบบเบาบาง ตารางสัญลักษณ์ในคอมไพเลอร์ ตัวจัดสรรหน่วยความจำแบบรายการว่าง และประวัติการยกเลิกในโปรแกรมแก้ไขข้อความขนาดเล็ก

การแทรกหรือลบที่ส่วนหัวใช้เวลา O(1) การแทรกที่ส่วนท้าย การค้นหา การแทรกที่ตำแหน่ง และการลบโหนดเฉพาะ ล้วนใช้เวลา O(n) เนื่องจากต้องมีการสำรวจจากส่วนหัว

Linked List สามารถขยายและหดตัวได้ในระหว่างการทำงาน แทรกหรือลบข้อมูลได้ในเวลา O(1) เมื่อทราบตำแหน่งแล้ว และไม่จำเป็นต้องใช้หน่วยความจำที่ต่อเนื่องกัน ส่วน Array นั้นให้การเข้าถึงแบบสุ่มในเวลา O(1) และมีประสิทธิภาพในการใช้แคชที่ดีกว่า

ไล่ดูรายการโดยใช้ตัวชี้สามตัว ได้แก่ prev, curr และ next ในแต่ละขั้นตอน ให้บันทึก curr.next ไว้ ชี้ curr.next ไปที่ prev และเลื่อน prev และ curr ไปข้างหน้า คืนค่า prev เป็นหัวรายการใหม่

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

สรุปโพสต์นี้ด้วย: