next pointer เดินไปข้างหน้าทางเดียว
Singly list มี pointer เดียว (next) — คุณเดินจาก head → tail ได้ แต่วิ่งกลับจาก tail → head ไม่ได้ ถ้าต้องการเดินย้อนกลับบ่อย ๆ ให้พิจารณาใช้ doubly linked list
Linked List
เรียนรู้ singly linked list ตั้งแต่วิธีเดินอ่านข้อมูล การเข้าถึงตาม index และการค้นหาแบบทีละโหนด
Singly Linked List คือ linked list แบบที่ node แต่ละตัวมี pointer ทิศทางเดียวคือ **next** — ชี้ไป node ถัดไปเท่านั้น ไม่สามารถเดินย้อนกลับได้ เปรียบเหมือนถนนเดินรถทางเดียว: ไปข้างหน้าได้อย่างเดียว ห้ามกลับรถ
สร้าง 3 nodes แล้วเชื่อมด้วย next pointer — head ชี้ไป node1
type SinglyNode<T> = {
value: T;
next: SinglyNode<T> | null;
};
// ตัวอย่างการสร้าง singly linked list ด้วย object literal
const node3 = { value: 30, next: null };
const node2 = { value: 20, next: node3 };
const node1 = { value: 10, next: node2 };
const head = node1;
// Traverse: 10 → 20 → 30 → nullการ traverse (เดินสำรวจ) ใน linked list คือการเริ่มจาก head แล้วเดินตาม next pointer ทีละ node จนกว่าจะพบ null — ไม่สามารถกระโดดข้ามหรือเข้าถึง index โดยตรงได้
ใช้ while loop เดินตาม next จนเจอ null
function printList(head) {
let current = head;
while (current !== null) {
console.log(current.value);
current = current.next;
}
}
const head = { value: 10, next: { value: 20, next: { value: 30, next: null } } };
printList(head);
// 10
// 20
// 30ใน array การเข้าถึง index ที่ i ใช้เวลา O(1) ด้วยการคำนวณตำแหน่งจาก base address — แต่ใน linked list เราไม่มีทางรู้ว่า node ตัวที่ i อยู่ไหนนอกจากเดินจาก head ผ่าน next ทีละ node i ก้าว
ต้องไล่ next ทีละ node — O(n) ไม่ใช่ O(1) เหมือน array
function getAt(head, index) {
let current = head;
let count = 0;
while (current !== null) {
if (count === index) return current.value;
count += 1;
current = current.next;
}
return undefined; // index เกินขนาด list
}
const head = { value: 10, next: { value: 20, next: { value: 30, next: null } } };
console.log(getAt(head, 0)); // 10
console.log(getAt(head, 2)); // 30
console.log(getAt(head, 5)); // undefined| Index ที่ต้องการ | จำนวนก้าวที่ต้องเดิน | Array (เวลา) | Singly Linked List (เวลา) |
|---|---|---|---|
| 0 | 0 | O(1) | O(1) |
| 1 | 1 | O(1) | O(1) |
| 10 | 10 | O(1) | O(n) |
| n/2 | n/2 | O(1) | O(n) |
| n-1 | n-1 | O(1) | O(n) |
การค้นหาค่า (search) ใน linked list ไม่สามารถใช้ binary search ได้ (เพราะเข้าถึงตรงกลางไม่ได้ใน O(1)) — ต้องใช้ linear search โดยเดินทีละ node จาก head และเทียบค่า
linear search — เดินทีละ node เทียบค่า ใช้เวลา O(n)
function find(head, target) {
let current = head;
let index = 0;
while (current !== null) {
if (current.value === target) return index;
current = current.next;
index += 1;
}
return -1; // ไม่พบ
}
const head = { value: 10, next: { value: 20, next: { value: 30, next: null } } };
console.log(find(head, 20)); // 1
console.log(find(head, 99)); // -1ถ้า linked list เรียงแล้ว (sorted) เรายังต้องใช้ linear search อยู่ — ข้อดีคือพอเจอค่าที่มากกว่า target แล้วจะหยุดค้นได้เลย (early exit) แต่ก็ยัง O(n) อยู่ดี ต่างจาก sorted array ที่ binary search ทำได้ O(log n)
การเขียนเป็น class ช่วยให้จัดการ head/tail ได้เป็นระเบียบ — มาดูตัวอย่าง SinglyList ที่มี method prepend, print, และ find
ตัวอย่าง class SinglyList สำหรับจัดการ singly linked list
class SinglyList {
constructor() {
this.head = null;
this.tail = null;
}
prepend(value) {
const node = { value, next: this.head };
this.head = node;
if (!this.tail) this.tail = node;
}
print() {
let current = this.head;
const values = [];
while (current !== null) {
values.push(current.value);
current = current.next;
}
console.log(values.join(" → "));
}
find(target) {
let current = this.head;
let index = 0;
while (current !== null) {
if (current.value === target) return index;
current = current.next;
index += 1;
}
return -1;
}
}
const list = new SinglyList();
list.prepend(30);
list.prepend(20);
list.prepend(10);
list.print(); // 10 → 20 → 30
console.log(list.find(20)); // 1prepend อาจทำให้ลำดับกลับด้าน
ถ้าเรา prepend 10, 20, 30 ตามลำดับ — ใน list จะเรียงเป็น 30 → 20 → 10 เพราะเราเสียบไว้หน้า head เสมอ ถ้าต้องการเรียงตามลำดับที่เพิ่ม ให้ใช้ append แทน
ลงมือเขียนฟังก์ชันที่ทำงานกับ singly linked list — เริ่มจาก sum การหาผลรวมทั้งหมด
| Operation | Singly Linked List | Array | รายละเอียด |
|---|---|---|---|
| Access by Index | O(n) | O(1) | Linked list ต้องเดินจาก head ทีละ node |
| Search | O(n) | O(n) / O(log n) หากเรียงแล้ว | ทำ linear search ทั้งคู่ แต่ array binary search ได้ถ้าเรียง |
| Prepend (เพิ่มหัว) | O(1) | O(n) | Linked list แค่สร้าง node ใหม่ชี้ head เดิม |
| Append (เพิ่มท้าย) | O(1) มี tail, O(n) ไม่มี | O(1) amortized | มี tail = เร็ว, ไม่มี = ต้องเดินทั้ง list |
| Insert at Index | O(n) | O(n) | ต้องเดินหาตำแหน่งก่อน + O(1) แทรก |
| Delete Head | O(1) | O(n) | Linked list ขยับ head, Array shift ทั้งก้อน |
| Delete Tail | O(n) | O(1) | Singly ต้องเดินหา node ก่อน tail เพื่อตั้ง next = null |
หัวใจสำคัญของ singly linked list ที่ต้องจำให้ขึ้นใจ
Singly list มี pointer เดียว (next) — คุณเดินจาก head → tail ได้ แต่วิ่งกลับจาก tail → head ไม่ได้ ถ้าต้องการเดินย้อนกลับบ่อย ๆ ให้พิจารณาใช้ doubly linked list
ทุก operation เริ่มจาก head — ถ้าทำ head หาย (override, assign ผิด) เท่ากับคุณทำ list ทั้งก้อนหายไป แม้ node อื่นจะยังอยู่ใน memory
นี่คือ trade-off สำคัญของ linked list — เก่งเรื่องแก้โครงสร้าง (insert/delete) แต่อ่อนเรื่องเข้าถึงตำแหน่งสุ่ม (random access)
การเก็บ reference tail ไว้ด้วย (นอกเหนือจาก head) เป็น optimization ที่สำคัญ — ทำให้ append เร็วขึ้นมากโดยเสียหน่วยความจำเพิ่มเพียง 1 reference
เวลา insert node ใหม่ ต้องชี้ next ของ node ใหม่ไปยัง node ถัดไปก่อน แล้วค่อยเปลี่ยน next ของ node ปัจจุบัน — ถ้าทำกลับกัน node ถัดไปจะหายทันที
ทดสอบความเข้าใจก่อนไปเรียนรู้ doubly linked list