บทที่ 4...

20
1 บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ (Linked List) Linked list เป็นโครงสร้าง (เมื่อเปรียบเทียบกับอาร์เรย์ ) ที่ใช้หน่วยความจาได้อย่างดีเยี่ยมมีความ ยืดหยุ่นสูง การใช้เนื้อที่ในการจัดเก็บข้อมูลไม่จาเป็นต้องจองจานวนที่ต้องการใช้สูงสุด แต่จะจัดสรรเนื้อที่ตามทีใช้จริง การเข้าหาข้อมูลจะเข้าแบบเป็นลาดับขั้น (Sequential Access) คือจากข้อมูลหนึ่ง ไปยังข้อมูลหนึ่ง ตามลาดับก่อนหลัง เชน่ ถ้าต้องการไปยังข้อมูลที่อยู่ในลาดับที่สามจากข้อมูล ตาแหน่งปัจจุบันจะต้องผ่าน ข้อมูลก่อนหน้าสองตัว โดยทั่วไป Linked list จะมีส่วนประกอบหลักอยู่สองส่วนคือ 1). ส่วนเก็บข้อมูล และ 2). ส่วนเชื่อมต่อ หัวข้อเรื่องในบทนี- กระบวนการของการจัดการกับลิงค์ลิสต์ ทั้ง 3 กระบวนการคือ 1). การนาข้อมูลเข้า 2). การลบข้อมูล และ 3).การค้นหาข้อมูล - โดยเฉพาะเมื่อ ทากับโครงสร้างต่อไปนี1. ลิสต์ทางเดียว (Singly linked list) 2. ลิสต์สองทาง (Doubly linked list) 3. ลิสต์วนกลับ (Circular linked list) 1.1 ลิสต์ทางเดียว (Singly linked list) Singly linked list เป็นโครงสร้างที่ใช้ node มาเชื่อมต่อกันให้เป็นลิสต์ โดยในแต่ละ node จะมีส่วนทีเป็นข้อมูล (data) และส่วนที่เป็นตัวเชื่อมไปยัง node ถัดไป (link) รูปที1.1 แสดงลิสต์ทางเดียวที่มีข้อมูลเป็น integer รูปที่ 1.1 Singly linked list การออกแบบ node จะใช้ส่วนที่ชื่อ data เป็นที่เก็บข้อมูลและส่วนที่ชื่อ next เป็นตัวเชื่อม สาหรับ Singly linked list รูปที1.2 แสดงส่วนประกอบของ node และ node สุดท้ายของลิสต์ รูปที1.2 โครงสร้างของ node [data และ link] และวิธีแสดง node สุดท้ายของ Linked list ใน diagram

Transcript of บทที่ 4...

Page 1: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

1

บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ (Linked List)

Linked list เป็นโครงสร้าง (เมื่อเปรียบเทียบกับอาร์เรย์) ที่ใช้หน่วยความจ าได้อย่างดีเยี่ยมมีความ

ยืดหยุ่นสูง การใช้เนื้อท่ีในการจัดเก็บข้อมูลไม่จ าเป็นต้องจองจ านวนที่ต้องการใช้สูงสุด แต่จะจัดสรรเนื้อที่ตามที่ใช้จริง การเข้าหาข้อมูลจะเข้าแบบเป็นล าดับขั้น (Sequential Access) คือจากข้อมูลหนึ่ง ไปยังข้อมูลหนึ่ง ตามล าดับก่อนหลัง เชน่ ถ้าต้องการไปยังข้อมูลที่อยู่ในล าดับที่สามจากข้อมูล ณ ต าแหน่งปัจจุบันจะต้องผ่านข้อมูลก่อนหน้าสองตัว โดยทั่วไป Linked list จะมีส่วนประกอบหลักอยู่สองส่วนคือ 1). ส่วนเก็บข้อมูล และ 2). ส่วนเชื่อมต่อ

หัวข้อเรื่องในบทนี้ - กระบวนการของการจัดการกับลิงค์ลิสต์ ทั้ง 3 กระบวนการคือ 1). การน าข้อมูลเข้า 2). การลบข้อมูล และ 3).การค้นหาข้อมูล - โดยเฉพาะเมื่อ ท ากับโครงสร้างต่อไปนี้ 1. ลิสต์ทางเดียว (Singly linked list) 2. ลิสต์สองทาง (Doubly linked list) 3. ลิสต์วนกลับ (Circular linked list)

1.1 ลิสต์ทางเดียว (Singly linked list) Singly linked list เป็นโครงสร้างที่ใช้ node มาเชื่อมต่อกันให้เป็นลิสต์ โดยในแต่ละ node จะมีส่วนที่

เป็นข้อมูล (data) และส่วนที่เป็นตัวเชื่อมไปยัง node ถัดไป (link) รูปที่ 1.1 แสดงลิสต์ทางเดียวที่มีข้อมูลเป็น integer

รูปที่ 1.1 Singly linked list

การออกแบบ node จะใช้ส่วนที่ชื่อ data เป็นที่เก็บข้อมูลและส่วนที่ชื่อ next เป็นตัวเชื่อม ส าหรับ

Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ node และ node สุดท้ายของลิสต์

รูปที่ 1.2 โครงสร้างของ node [data และ link] และวิธีแสดง node สุดท้ายของ Linked list ใน diagram

Page 2: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

2

เราจะใช้ node พิเศษสอง node ส าหรับเป็นตัวชี้ไปยัง node ทีอ่ยู่ด้านหน้าและ node ที่อยู่ท้ายสุดในลิสต์ คือ head และ tail ซึ่งการก าหนดในลักษณะนี้จะท าให้การเข้าหาข้อมูลทั้งด้านหน้าและด้านหลัง (node แรกสุดของทั้งสองทางดังที่แสดงในรูปที่ 1.1) ท าได้ในเวลาที่เท่ากันส าหรับ node ที่ tail ชี้อยู่และเป็น node สุดท้ายของลิสต์เราจะก าหนดให้ชี้ไปยัง null node (หรือ nil) ซึ่งเราใช้สัญลักษณ์ Ø แทน และทุกครั้งเมื่อมี การสร้างลิสต์เราจะก าหนดให้ head และ tail ชี้ไปที่ null node เสมอเพ่ือเป็นการก าหนดถึงลิสต์ที่ยังไม่มีข้อมูล (empty list)

1.1.1 การน าข้อมูลเข้า (Insertion)

ในการน าข้อมูลเข้าสู่ linked list นั้น เรามักจะน าเข้าทางด้านหลัง (node ที่ tail ชี้อยู่) แต่การน าเข้าทางด้านหน้าหรือ ณ ต าแหน่งอืน่ ๆ ใน linked list ก็สามารถที่ได้เช่นกัน ทั้งนี้การน าเข้าข้อมูลเราจะต้องค านึงถึง กรณีหลักสองกรณีคือ

head ชี้ไปที ่null ซ่ึงหมายถึง linked list ทีไ่ม่มีข้อมูลอยู่เลย หรือ linked list มีข้อมูลอยูซ่ึ่งการน าเข้าก็คือการเชื่อม node ใหม่เข้าสู่ลิสต์พร้อมกับปรับ head หรือ tail ให้ชี้

ไปยังnode ใหม่ทีเ่พ่ิงน าเข้านี้ การน าข้อมูลเข้าทางด้านหลังของ linked list เริ่มด้วยการสร้าง node ใหม่ส าหรับข้อมูลที่ต้องน าเข้า

พร้อมกับตรวจสอบ ว่าลิสต์มีข้อมูลหรือไม่ ถ้าลิสต์ไม่มีข้อมูลเลยก็ย้าย head และ tail ไปที่ node ใหม่นี้ แต่ถ้าลิสต์มขี้อมูลอยู่ให้ย้ายตัวเชื่อม

จาก node tail ไปยัง node ใหม่สร้างขึน้ พร้อมกับย้าย tail มาชี้ที่ node ใหม่นี ้

รูปที่ 1.3 การน าข้อมูลเข้าเมื่อลิสต์ไม่มีข้อมูล

รูปที่ 1.4 การน าข้อมูลเข้าทางด้านหลังของลิสต์

Page 3: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

3

อัลกอริทึมการน าเข้าข้อมูลในลิสต์

01: Algorithm insertAtTail(item) 02: Begin 03: newNode new Node(value) 04: If head = null 05: tail head newNode 06: Else 07: tail.next newNode 08: tail newNode 09: End If 10: End insertAtTail

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

รูปที่ 1.5 การน าข้อมูลเข้าทางด้านหน้าของลิสต์

อัลกอริทึมการน าเข้าข้อมูลในลิสต์ 01: Algorithm insertAtHead(item) 02: Begin 03: newNode new Node(value) 04: If head = null 05: tail head newNode 06: Else 07: newNode.next head 08: head newNode 09: End If 10: End insertAtHead

Page 4: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

4

1.1.2 การลบข้อมูล (Deletion) การลบข้อมูลออกจาก Linked list ทางด้านหน้านั้น ท าได้ง่ายกว่าทางด้านหลัง เราเพียงแต่ย้าย

head ให้ชี้ไปยัง node ถัดไปก็จบ แต่การลบออกจากทางด้านหลังนั้น เราต้องค้นหา node ทีอ่ยู่ก่อน tail ให้เจอเพ่ือที่จะย้ายตัวเชื่อมจาก node นี้ไปยัง null ซ่ึงก็หมายถึงการเดินเข้าหาทุก node ทีมี่อยู่ในลิสต์ (ใช้เวลามากถ้าจ านวนของ node มีมาก) รูปที่ 1.6 แสดงการลบ node ออกทางด้านหน้าของลิสต์

รูปที่ 1.6 แสดงการลบจากทางด้านหน้าของลิสต์

อัลกอริทึมของการลบจากลิสต์ 01: Algorithm removeAtHead() 02: Begin 03: //case 1: empty list 04: If head = null 05: Return false 06: End If 07: //case 2: list has at least one node 08: head head.next 09: Return true 10: End removeAtHead

ส าหรับการลบ node ออกทางด้านหลังของลิสต์เราต้องเดินเข้าหาลิสต์ทีละ node เพ่ือหา node ก่อนหน้า tail ให้เจอ เพ่ือที่จะได้ก าหนดให้ tail ชี้ไปที่ node นี้ (ดูรูปที่ 1.7)

รูปที่ 1.7 การลบจากทางด้านหลังของลิสต์

Page 5: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

5

อัลกอริทึมของการลบทางด้านหลังจากลิสต์ 01: Algorithm removeAtTail() 02: Begin 03: //case 1: empty list 04: If head = null 05: Return false 06: End If 07: //case 2: list has at least one node 08: beforeTail head 09: While beforeTail != tail do 10: beforeTail beforeTail.next 11: End while 12: beforeTail.next null 13: tail beforeTail 14: Return true 15: End removeAtTail

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

1.1.3 การน าข้อมูลเข้าหรือดึงออกหลัง node ทีก่ าหนดให้ กระบวนการทั้งสองนีต้้องหา node ทีก่ าหนดให้ให้เจอ ซ่ึงอหาเจอแล้วขัน้ตอนต่อไปก็ง่ายขึ้น เรา

เพียงแต่ย้ายตัวเชื่อมให้อยู่ในต าแหน่งที่เหมาะสม (ถ้าไม่เจอก็ไม่ต้องลบ) รูปที่ 1.8 แสดงการลบ node ที่อยู่ถัดไปจาก node ทีก่ าหนดให้ และรูปที ่1.8 แสดงการน า node เข้าหลัง node ทีก่ าหนดให้

รูปที่ 1.8 การลบจากทางด้านหลังของ node ที่ก าหนด

Page 6: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

6

01: Algorithm removeAfter(key) 02: Begin 03: cur search(key) 04: If cur = null 05: Return false 06: End If 07: forward cur.next.next 08: cur.next forward 09: Return true 10: End removeAfter

อัลกอริทึมส าหรับการค้นหาก็ออกแบบง่าย ๆ ด้วยการเดินเข้าหา node ทุกตัวที่อยู่ในลิสต์พร้อมกับ ท าการเปรียบข้อมูลของ node กับข้อมูลที่ต้องการค้นหา (key) ถ้าข้อมูลที่ต้องการค้นหามีอยู่ในลิสต์เราก็ส่ง node ทีข้่อมูลกลับ แต่ถ้าข้อมูลไม่มีอยู่ในลิสต์ค่าที่เราส่งกลับคือ null ผู้อ่านคงจะสังเกตเห็นว่าการค้นหาข้อมูลทีไม่มีอยู่ในลิสต์นั้น เราต้องใช้เวลาค้นหาไปจนหมด node ที่มีอยู่ในลิสต์ ซึ่งเป็นการใช้เวลามากถ้าจ านวน node มีมาก

01: Algorithm search(key) 02: Begin 03: cur head 04: While cur != null && cur.data != key do 05: cur cur.next 06: End While 07: Return cur 08: End search

รูปที่ 1.9 การน า node เข้าทางด้านหลังของ node ที่ก าหนด

Page 7: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

7

01: Algorithm insertAfter(key) 02: Begin 03: cur search(key) 04: If cur = null 05: Error: ‘Given doesn’t exist’ 06: Else 07: newNode new Node(value) 08: newNode.setNext(cur.getNext()) 09: cur.setNext(newNode) 10: End If 11: End insertAfter

1.1.4 การน าข้อมูลเข้าหรือออกก่อนหน้า node ที่ก าหนดให้ การน าเข้าหรือลบออกจาก node ที่ก าหนดให้ก็คล้ายกับการกระท าหลัง node ที่ก าหนดให้

ต่างกันตรงที่เราต้องจ า node ทีอ่ยู่ก่อนหน้า node ที่ได้ถูกก าหนดไว้เพ่ือการโยกย้ายตัวเชื่อม รูปที่ 1.10 แสดงการน า node เข้าส่วนรูปที่ 1.11 แสดงการลบ node ออก

รูปที่ 1.10 การน า node เข้าทางด้านหน้าของ node ที่ก าหนด

รูปที่ 1.10 แสดงการน าเข้าด้านหน้าของ node ทีก่ าหนดให้เฉพาะกรณีทั่วไป แต่กรณีที่ต้องน าเข้าก่อน

node ที่ head ชี้อยู่ต้องตรวจสอบเพิ่มเติมว่า cur ชี้อยู่ทีเ่ดียวกันกับ head หรือไม่เพ่ือให้การเชื่อมต่อจาก head ไปยัง node ใหม่เป็นไปได้ (บรรทัดที่ 12 – 14 ในอัลกอริทึม)

01: Algorithm insertBefore(key, item) 02: Begin 03: before cur head 04: While cur != null && cur.data != key do 05: before cur 06: cur cur.next 07: End while

Page 8: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

8

08: If cur = null 09: Error: Key doesn’t exist 10: Else 11: temp new Node(item) 12: If cur = head 13: head = temp 14: Else 15: before.next temp 16: End If 17: End If 18: End insertBefore

รูปที่ 1.10 การลบ node ที่อยู่ก่อนหน้าของ node ที่ก าหนดให้

รูปที่ 1.10 แสดงการลบ node กอ่ นหน้า node ที่มีข้อมูลที่ก าหนดให้ในกรณีที่ node นี้ไม่ได้อยู่ถัดจาก head ทันท ีขัน้ตอนทีเ่ราท า คือเดินเข้าหาลิสต์ด้วยตัวชี้สองตัว ตัวหนึ่ง อยู่ก่อนหน้า node ที่มี key อยู่อีก node หนึ่งอยู่ก่อนหน้าสอง node (cur และ before) เมื่อหา node ทั้งสองเจอแล้วเราก็ย้ายตัวเชื่อมจาก before ไปยัง node ที่ก าหนดให้ส าหรับกรณีที่ key อยู่ถัดจาก head ทันที สิ่งที่เราต้องท าก็คือย้ายตัวเชื่อมของ head ไปยัง node ทีอ่ยู่ด้านหลังของ node ก่อนหน้า key

01: Algorithm removeBefore(key) 02: Begin 03: //case 1: empty list 04: If head = null 05: Return false 06: End If 07: before cur head 08: While cur.next != null && cur.next.data != key do 09: before cur

Page 9: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

9

10: cur cur.next 11: End While 12: //case 2: key is not in the list 13: If cur.next = null 14: Return false 15: End If 16: //case 3: key is after head node 17: If cur = head 18: head head.next 19: Return true 20: End If 21: //case 4: other cases 22: before.next cur.next 23: Return true 24: End removeBefore 1.2 ลิสต์สองทาง (Doubly linked list)

ลิสต์สองทางเป็นลิสต์ที่ปรับปรุงมาจากลิสต์ทางเดียวเพ่ือตอบสนองการเดินเข้าหาลิสต์ในรูปแบบที่เป็นไปได้ทั้งสองทาง คือทางด้านหน้าและด้านหลัง การท าในลักษณะนี้ท าให้กระบวนการต่าง ๆ ทีเ่ราต้องท ากับลิสต์ท าได้ไวขึน้ เช่นการลบ node ก่อนหน้า tail ทีเ่ราท าในลิสต์ทางเดียว ใน Doubly linked list เราไม่ต้องเดินเข้าหาลิสต์จากทางด้านหน้าอีกต่อไปเราสามารถลบ node ดังกล่าวออกทางด้านหลังได้ทันที แต่เราต้องออกแบบให้ node ของเรามีตัวเชื่อมอีกหนึ่งตัวส าหรับเชื่อม node ก่อนหน้า รูปที่ 1.11 แสดง Doubly linked list

รูปที่ 1.11 Doubly linked list

1.2.1 การน าข้อมูลเข้า (Insertion)

การน าข้อมูลเข้าสู่ Doubly linked list ทัง้สองทางท าในลักษณะเดียวกันคือ สร้าง node ใหม่ส าหรับข้อมูลที่ต้องการ น าเข้าย้ายตัวเชื่อมต่างๆ ไปยังต าแหน่งที่เหมาะสม การน าเข้าในกรณีท่ีไม่มีข้อมูลอยู่ในลิสต์นั้น สิ่งทีเ่ราต้องท าคือย้าย tail มาชี้ที่ node ใหม่แต่ถ้ามีข้อมูลอยู่เราต้องย้ายตัวเชื่อมกลับของ head มาท่ี node ใหม่นี้ ขัน้ตอนทีเ่หลือของทั้งสอง กรณีคือการเชื่อมตัวชี้ของ node ใหม่ไปที่ head พร้อมกับการย้าย head ไปที่ node ใหม่ รูปที่ 1.12 แสดงการน าเข้าในกรณีที่ลิสต์มีข้อมูลอยู่

Page 10: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

10

รูปที่ 1.12 การน าข้อมูลเข้าด้านหน้าของ Doubly linked list

01: Algorithm insertAtHead(value) 02: Begin 03: newNode new Node(value) 04: If head = null 05: tail newNode 06: Else 07: head.previous newNode 08: End If 09: newNode.next head 10: head newNode 11: End insertAfter

ส าหรับการน าข้อมูลเข้าทางด้านหลังนั้น เราก็ท าคล้ายกับทางด้านหน้าในกรณีท่ีลิสต์ไม่มีข้อมูลอยู่เราก็ก าหนดให้ head ชี้ไปที่ node ใหม ่แต่ถ้ามีข้อมูลเราต้องย้ายตัวเชื่อมไปของ tail ไปที่ node ใหม่พร้อมกับย้ายตัวเชื่อมกลับของ node ใหม ่ไปที่ tail และข้ันตอนสุดท้ายของทั้งสองกรณีคือการย้าย tail ไปชี้ที่ node ใหม่ (ดูรูปที่ 1.13)

รูปที่ 1.13 การน าข้อมูลเข้าด้านหน้าของ Doubly linked list

Page 11: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

11

01: Algorithm insertAtHead(value) 02: Begin 03: newNode new Node(value) 04: If head = null 05: tail newNode 06: Else 07: head.previous newNode 08: End If 09: newNode.next head 10: head newNode 11: End insertAfter

ส าหรับการน าข้อมูลเข้าทางด้านหลังนั้น เราก็ท าคล้ายกับทางด้านหน้าในกรณีท่ีลิสต์ไม่มีข้อมูลอยู่เราก็ก าหนดให้ head ชี้ไปที ่node ใหม ่แต่ถ้ามีข้อมูลเราต้องย้ายตัวเชื่อมไปของ tail ไปที ่node ใหม่พร้อมกับย้ายตัวเชื่อมกลับของ node ใหม่ไปที่ tail และข้ันตอนสุดท้ายของทั้งสองกรณีคือการย้าย tail ไปชี้ที่ node ใหม่ (ดูรูปที่ 1.14)

รูปที่ 1.14 การน าข้อมูลเข้าด้านหลังของ Doubly linked list

01: Algorithm insertAtTail(value) 02: Begin 03: newNode new Node(value) 04: If head = null 05: head newNode 06: Else 07: tail.nextnewNode 08: newNode.previous tail 09: End If 10: tail newNode 11: End insertAtTail

Page 12: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

12

1.2.2 การลบข้อมูล (Deletion) การลบข้อมูลออกจากลิสต์ทางด้านหน้าเราต้องค านึงถึง 1). กรณีท่ีลิสต์ไม่มีข้อมูล 2). กรณีท่ีลิสต์

มีข้อมูลเพียงหนึ่งตัว และ 3). กรณีท่ีลิสต์มีข้อมูลมากกว่าหนึ่งตัว กรณีท่ี 1). และ 2). นั้นง่ายมากเราเพียงแค่ก าหนดให้ head และ/หรือ tail มีค่าเป็น null ส่วนกรณีท่ี 3). เราย้ายตัวเชื่อมกลับของ node ทีอ่ยู่ถัดจาก head (ถ้ามี) ไปที่ null และย้าย head ไปยัง node นี้หลังจากนัน้ ดูรูปที่ 1.15

รูปที่ 1.15 การลบข้อมูลออกทางด้านหน้าของ Doubly linked list

01: Algorithm removeAtHead() 02: Begin 03: //case 1: empty list 04: If head = null 05: Return null 06: End If 07: //case 2: list with one node 08: If head.next = null 09: head tail null 10: //case 3: list with more than one node 11: Else 12: head.next.previous null 13: head head.next 14: End If 15: End removeAtHead ส าหรับการลบข้อมูลจากด้านหลังของลิสต์ก็คล้ายกับทางด้านหน้าต่างกันเพียงแค่การย้ายตัวเชื่อมของ tail แทนการย้ายตัวเชื่อมของ head ดูรูปที่ 1.16

Page 13: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

13

รูปที่ 1.16 การลบข้อมูลออกทางด้านหลังของ Doubly linked list

01: Algorithm removeAtTail() 02: Begin 03: //case 1: empty list 04: If head = null 05: Return null 06: End If 07: //case 2: list with one node 08: If head.next = null 09: head tail null 10: //case 3: list with more than one node 11: Else 12: tail.previous.next null 13: tail tail.previous 14: End If 15: End removeAtTail

1.2.3 การน าข้อมูลเข้าแบบจัดเรียง (In-order Insertion) การน าข้อมูลเข้าแบบที่ต้องค านึงถึงต าแหน่งของข้อมูลก่อนหลัง เช่นจากน้อยไปหามากจ าเป็นต้องหาต าแหน่งที่เหมาะสมในลิสต์ให้กับข้อมูล ซ่ึงโดยทั่วไปก็มีอยู่ 4 กรณีคือ 1). ลิสต์ไม่มีข้อมูล 2). น าเข้าด้านหน้า 3). น าเข้าด้านหลัง และ 4).น าเข้าระหว่าง node ซ่ึงทุกกรณีดังกล่าว (ยกเว้นกรณีที่ 1) เราต้องเดินเข้าหาลิสต์เพ่ือเปรียบเทียบข้อมูลในการหาต าแหน่งที่ถูกต้อง 01: Algorithm insertInorder(value) 02: Begin 03: //case 1: empty list 04: If head = null 05: insertAtHead(value) 06: Return 07: End If 08: //search for proper position 09: cur head

Page 14: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

14

10: While cur != null do 11: If value > cur.data 12: cur cur.next 13: Else 14: Break 15: End While 16: newNode = new Node(value) 17: //case 2: insert before head node 18: If cur = head 19: head newNode 20: head.next cur 21: cur.previous newNode 22: //case 3: insert at tail 23: Else If cur = null 24: tail.next newNode 25: newNode.previous tail 26: tail newNode 27: //case 4: insert between nodes 28: Else 29: newNode.next cur 30: cur.previous.next newNode 31: newNode.previous cur.previous 32: cur.previous newNode 33: End If 34: End insertInorder 1.3 ลิสต์แบบวนกลับ (Circular linked list) ลิสต์แบบวนกลับหมายถึงลิสต์ที่เราก าหนดให้ตัวเชื่อมไปของ node สุดท้ายชี้กลับไปที่ node แรกของลิสต์ซ่ึงข้อดีของการท าแบบนี้คือ การเข้าหาnode ด้านหน้าและด้านหลังใช้เวลาเท่ากัน ดูรูปที่ 1.17

รูปที่ 1.17 Circular linked list

Page 15: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

15

Singly linked list โดยทั่วไปจะมี node พิเศษทีเ่รียกว่า head ชี้อยู่ที่ node แรกสุดของลิสต์ แต่ส าหรับลิสต์แบบวนกลับเราจะใช้ tail4 แทน head ทั้งนี้ก็เพราะว่าการมี head อยู่ก็ไม่ช่วยให้การเข้าหาลิสต์ดีขึ้น ซึ่งตรงกันข้ามกับการมี tail เราสามารถเข้าหาทั้ง head และ tail ได้ในเวลาทีเ่ท่ากัน

1.3.1 การน าข้อมูลเข้า (Insertion) เนื่องจากว่าเราใช้ตัวชี้ tail เป็นตัวก าหนดการเข้าหาลิสต์ ดังนั้น การน าข้อมูลเข้า (ทัง้ ด้านหน้าและด้านหลัง) จึงต้องที่ผ่านทาง tail เช่นเดียวกับการน าเข้าโดยทั่วไป (น าเข้าทางด้านหน้าของลิสต์) เราต้องค านึงถึงกรณีที่ลิสต์มีและไม่มีข้อมูลอยู่ ในกรณีทีล่ิสต์ไม่มีข้อมูลเราก็เพียงแค่ก าหนดให้ tail ชี้ไปที่ node ใหม่ทีเ่ราสร้างขึน้ และตัวชี้ของ tail ชี้ไปที่ตัวเอง (บรรทัดที ่07 ในอัลกอริทึม) ส าหรับกรณีที่ลิสต์มีข้อมูลอยู่เราต้องก าหนดให้ตัวชี้ของ node ใหม่ชี้ไปยัง node ที ่tail ชี้ (ซึ่งอาจเป็นตัวมันเองถ้าลิสต์มีเพียงข้อมูลเดียว) หลังจากนั้นก็ก าหนดให้ตัวชี้ของ tail ชี้ไปยัง node ใหม่นี้ 01: Algorithm insert(value) 02: Begin 03: newNode new Node(value) 04: //case 1: empty list 05: If tail = null 06: tail newNode 07: tail.next tail 08: Else 09: //case 2: list has at least one node 10: newNode.next tail.next 11: tail.next newNode 12: End If 13: End insert

รูปที่ 1.18 การน าข้อมูลเข้าเม่ือลิสต์ไม่มีข้อมูล (ซ้าย) และเมือ่ลิสต์มีข้อมูลอยู่อย่างน้อยหนึ่งตัว (ขวา)

เรารู้ว่าลิสต์แบบวนกลับนี้จะต้องก าหนดให้ tail ชี้ไปที่ node ทีอ่ยู่ด้านหลังสุดเสมอ ดังนั้น การน าข้อมูลเข้าทางด้านหลังก็เป็นเพียงแค่การน าข้อมูลเข้าตามปกติทางด้านหน้าแล้วก็ย้ายตัว tail เองไปชี้ที่ node ใหม่ทีน่ าเข้านี้ดูรูปที่ 1.19

Page 16: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

16

01: Algorithm insertAtTail(value) 02: Begin 03: insert(value) 04: tail tail.next 05: End insertAtTail

รูปที่ 1.19 การน าข้อมูลเข้าทางด้านหลังของลิสต์

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

ในต าแหน่งทีเ่หมาะสม ในการลบด้านหน้านั้น สิ่งทีเ่ราต้องค านึงหลัก ๆ คือ 1). กรณทีีล่ิสต์มีเพียงแค่หนึ่ง node และ 2). กรณีทีม่ีมากกว่าหนึ่ง node ถ้าลิสต์มีแค่ node เดียวเราก็ก าหนดให้ tail มีค่าเป็น null ก็จบแต่ถ้ามีมากกว่าหนึ่ง node เราต้องก าหนดให้ตัวชี้ของ tail ชี้ไปที่ node ทีอ่ยู่ห่างออกไปจาก tail สอง node (เริ่มนับจากตัว tail เอง) 01: Algorithm remove() 02: Begin 03: //case 1: empty list 04: If tail = null 05: Return null 06: //case 2: list with one node 07: Else if tail = tail.next 08: tail null 09: //other case 10: Else 11: tail.next tail.next.next 12: End If 13: End remove

Page 17: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

17

รูปที่1.19 การลบข้อมูลออกทางด้านหน้าของลิสต์

การลบข้อมูลออกจากทางด้านหลังของลิสต์ดูเผิน ๆ แล้วก็น่าจะง่ายเพราะเรามี tail ชี้ที่ node สุดท้ายแต่ในความเป็นจริงแล้วเราต้องหา node ก่อนหน้า node ที่ tail ชี้อยู่ให้เจอเพ่ือที่เราจะได้ย้ายตัวชี้ให้อยู่ใน ต าแหน่งที่เหมาะสม อัลกอริทึมส าหรับการน าเข้าแบบนี้เริ่มที่การก าหนดให้มีตัวชี้ (cur ในอัลกอริทึม) ชี้ไปยัง node สุดท้าย ท าการเดินเข้าหาลิสต์จนกว่าจะมาถึง node ก่อนหน้า tail (next ของตัวชี้นี้ ชี้ไปยัง tail) ท าการย้ายตัวเชื่อมของ node นี้ให้ไปชี้ที่ next ของ tail ชี้อยู่ หลังจากนั้นก็ย้าย tail ไปที่ node ก่อนหน้า tail ส าหรับกรณีทีล่ิสต์ม ีnode เพียง node เดียวการเดินเข้าหาลิสต์จะไม่เกิดขึ้น (cur.next = tail) เราก็ก าหนดให้ tail มีค่าเป็น null

รูปที่ 1.20 การลบข้อมูลออกทางด้านหลังของลิสต์ 01: Algorithm removeAtTail() 02: Begin 03: //case 1: empty list 04: If tail = null 05: Return null 06: End If 07: //search for the node before tail 08: cur tail 09: While cur.next != tail do

Page 18: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

18

10: cur cur.next 11: End While 12: //case 2: only one node 13: If cur = tail 14: tail null 15: //case 3: more than one node 16: Else 17: cur.next tail.next 18: tail cur 19: End If 20: End removeAtTail สรุปโครงสร้างข้อมูลแบบลิงค์ลิสต์

ความแตกต่างของข้อมูลอาร์เรย์ กับข้อมูลประเภทนี้ที่เห็นได้ชัดเจนคือ ในอาร์เรย์การค านวณหาต าแหน่งของข้อมูลสามารถที่จะค านวณหาต าแหน่งได้โดยตรง มีลักษณะโครงสร้างแบบเชิงเส้น (linearly ordered) มีข้อ จ ากัดหลายๆอย่าง คือ

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

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

ลิงค์ลิสต์เดี่ยว รูปแบบการเก็บข้อมูลโดยการใช้โครงสร้างแบบ Linked -List อาจมีการเชื่อมต่อกันเป็นเส้นตรง(linear)

หรือไม่เรียงเป็นเส้นตรง ติดต่อกันไป (nonlinear) ในจ านวนที่ไม่แน่นอน เรียกสมาชิก ของลิงค์ลิสต์ว่า "โหนด" (node) แต่ละโหนดไม่จ าเป็นต้องมีสมาชิกในต าแหน่งที่ประชิดกัน โครงสร้างของแต่ละโหนดจะประกอบด้วย 2 ส่วน ส่วนแรกบรรจุสารสนเทศของสมาชิก (INFO) ส่วนที่สองบรรจุต าแหน่งของโหนดถัดไป หรือ โหนดที่ตามมาหลัง (LINK) โหนดสุดท้ายของลิงค์ลิส จะไม่มีโหนดตามหลัง หรือลิงค์ไม่มีการเก็บต าแหน่งของโหนดใด ๆ แต่จะเก็บ ค่า NULL แทน (แทนด้วยเครื่องหมาย / ) ซึ่ง เป็นสัญญาณว่าสิ้นสุดลิงค์ลิสต์ ลิงค์ลิสต์เดี่ยวจึงหมายถึง ลิงค์ลิสต์ที ่แต่ละโหนดมีลิงค์ตัวเดียว ส่วนลิงค์ลิสต์ใดที่ไม่มีโหนดเลยเรียกว่า ลิสค์ว่าง (empty)

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

จาก โหนดแรกไปเรื่อย ๆ จนกว่าจะพบโหนดที่ต้องการ

Page 19: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

19

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

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

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

(availability list ) หรือ หน่วยเก็บรวม (storage pool) เมื่อต้องการน าโหนดมาแทรกในลิงค์ลิสต์ ก็จะมีพอยเตอร์ 1 ตัว ในการที่จะชี้ไปยังสมาชิกตัวแรกของลิสต์พร้อมใช้งาน แล้วน าหนดอิสระมาจากลิงค์ลิสต์พร้อมใช้งาน และเชื่อมกับลิสต์ในต าแหน่งที่ต้องการในขณะเดียวกัน ถ้ามีการลบโหนดออกจากลิสต์ เราก็จะต้องมีการคืนโหนดไปยังลิสต์พร้อมใช้งาน เพ่ือให้สามารถจะน ามาใช้ได้ในภายหลัง

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

NULL ของโหนดสุดท้ายของลิงค์ลิสต์ด้วยต าแหน่ง ที่อยู่ของโหนดแรก ลิงค์ลิสต์ในลักษณะนี้เราเรียกว่า ลิงค์ลิสต์วงกลม (circular linking linear list ) หรือ ลิสต์วงกลม ( circular list)

ลิงค์ลิสต์วงกลม มีประโยชน์กว่าลิงค์ลิสต์ธรรมดามาก กล่าวคือ 1. ในการเข้าถึงข้อมูล ของโหนดลิงค์ลิสต์วงกลมโหนดทุกโหนด สามารถเข้าถึงได้จากโหนดใดๆ ที่

ก าหนดให้ โดยผ่านทางสายโซ่ (ลิงค)์ ของลิสต์ 2. ในการลบโหนด ในการค้นหาโหนดที่มาก่อนโหนดใด ๆ สามารถเริ่มต้นค้นหาในโหนดนั้นได้เลย ข้อเสียของลิงค์ลิสต์วงกลม 1. ในการประมวลผลหากไม่ระมัดระวัง อาจท าให้การประมวลผลวนรอบซ้ าไม่รู้จบ (infinite loop) จึง

ต้อง รู้จุดสิ้นสุดของการท างาน โดยเราจะแทนจุดสุดท้ายของลิงค์ด้วยโหนดพิเศษ ที่ง่ายต่อการจ าแนกโหนดในลิสต์วงกลม ซึ่งเราเรียกโหนดพิเศษนี้ว่า " โหนดหัว" (head node) หรือ "หัวลิสต์" (head list ) ของลิสต์วงกลม เทคนิคนี้จะท าให้ ลิสต์ไม่สามรถเป็นลิสต์ว่างได้

ลิงค์ลสิต์คู่ ในลิงค์ลิสต์ประเภทนี้จะมีโหนดซึ่งประกอบด้วยลิงค์ลิสต์ 2 ส่วน เพ่ือแสดงโหนดที่มาก่อน และโหนดที่มา

ทีหลังโหนดที่มาก่อนเราเรียกว่า ลิงค์ซ้าย (left link) ซึ่งแทนด้วยพอยเตอร์ LLINK และลิงค์ที่แทนโหนดที่มาหลังเรา เรียกว่า ลิงค์ขวา(right link) ซึ่งแทนด้วยพอยเตอร์ RLINK ซึ่งลิงค์ลิสต์ที่ประกอบด้วย คุณสมบัติดังกล่าวเราเรียกว่า "ลิงค์ลิสต์เชิงเส้นคู่ "(double linked linear list) หรือลิงค์ลิสต์คู่ (double linked list) ในแต่ละทิศทาง ไม่ว่าขวาสุด หรือซ้ายสุด จะมีค่า NULL เพ่ือแสดงว่าสิ้นสุดลิสต์ในแต่ละทิศทาง

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

และก าหนดให้ลิงค์ซ้ายและลิงค์ขวาของโหนดใหม่มีค่า NULL blockquote> 2.เมื่อแทรกโหนดใหม่ลงในกึ่งกลางของลิงค์ลิสต์ ลิงค์ลิสต์ก่อนการแทรกและหลังการแทรก ล าดับการ

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

Page 20: บทที่ 4 โครงสร้างข้อมูลแบบลิงค์ลิสต์ ( Linked List · Singly linked list รูปที่ 1.2 แสดงส่วนประกอบของ

20

3. เมื่อมีการแทรกโหนดใหม่ทางด้านซ้ายของโหนดซ้ายสุดของลิสต์ จะท าให้พอยเตอร์ L มีค่าเปลี่ยนไป การลบข้อมูลอกจากลิงค์ลิสต์คู่ ในการลบโหนดประเภทนี้ แตกต่างจากการลบโหนดในลิงค์ลิสเดี่ยวคือไม่จ าเป็นต้องมีการค้นหาโหนด

ที่มาก่อนโหนดที่จะลบ เพียงก าหนดต าแหน่งของโหนดที่จะลบก็สามารถทราบต าแหน่งของโหนดที่มาก่อน และโหนดที่มาทีหลังโหนดนั้น ถ้าลิงค์ลิสต์มีโหนดเพียงโหนดเดียว การลบโหนดออกจากลิงค์ลิสต์จะท าให้ได้ลิงค์ลิสต์ว่าง คือพอยเตอร์ซ้ายสุด และขวาสุดจะถูกก าหนดให้มีค่าเป็น NULL หาก พิจารณาขั้นตอนการลบข้อมูลและแทรกข้อมูลในลิงค์ลิสต์ แบบลิงค์ลิสต์คู่ สามารถท าให้ง่ายขึ้นได้ คือในกรณีที่ลิงค์ลิสต์ว่าง สามารถก าหนดให้ลิงค์ลิสต์ไม่เคยว่างได้ โดยการก าหนดโหนดพิเศษ ขึ้นมา 1 โหนด ซึ่งจะเป็นโหนดที่มีอยู่ในลิงค์ลิสต์ตลอดเวลา จึงท าให้ลิงค์ลิสต์ว่างมีเพียง 1 โหนด ที่เป็นโหนดพิเศษเท่านั้น ซึ่งเรียกว่าโหนดหัว (Head node) ของลิงค์ลิสต์ และนอกจกนั้นยังสามารถสร้างลิงค์ลิสคู่เป็นลิงค์ลิสต์วงกลมได้ ขั้นตอนวิธีในการแทรกและลบโหนดตามหลังโหนดที่ก าหนดให้ใดๆ จะลดล าดับขั้นตอนให้น้อยลง

**************************************