การแทรกและการลบโหนดที่จุดเริ่มต้นของ singly-linked list ทำได้มีประสิทธิภาพมากที่ O(1) แต่การแทรกหรือลบตรงกลางหรือท้ายลิสต์ต้องไล่ลำดับจนถึงจุดนั้น ทำให้มีความซับซ้อนเวลา O(n)
การออกแบบของ singly-linked list ทำให้เหมาะอย่างยิ่งเมื่อปฏิบัติการส่วนใหญ่เกิดขึ้นที่จุดเริ่มต้นของลิสต์
Doubly-linked list
Doubly-linked list
ข้อเสียอย่างหนึ่งของ singly-linked list คือสามารถไล่ลำดับได้เพียงทิศทางเดียว และไม่สามารถย้อนกลับไปยังโหนดก่อนหน้าเมื่อจำเป็น ข้อจำกัดนี้ทำให้การดำเนินการที่ต้องการการเดินทางแบบสองทิศทางทำได้ยาก
Doubly-linked list แก้ปัญหานี้ด้วยการเพิ่มพอยน์เตอร์อีกหนึ่งตัวในแต่ละโหนด ทำให้ไล่ลำดับได้ทั้งไปข้างหน้าและย้อนกลับ แต่ละโหนดใน doubly-linked list จะมีสามองค์ประกอบ ได้แก่ ข้อมูล พอยน์เตอร์ไปยังโหนดถัดไป และพอยน์เตอร์ไปยังโหนดก่อนหน้า
Circular linked list
Circular linked list
Circular linked list เป็นรูปแบบเฉพาะของ linked list ที่โหนดสุดท้ายชี้กลับไปยังโหนดแรก ทำให้เกิดโครงสร้างแบบวงกลม หมายความว่าไม่เหมือนกับ singly และ doubly linked list ที่เราเห็นก่อนหน้า circular linked list จะไม่มีจุดสิ้นสุด แต่จะวนกลับไปเรื่อยๆ
ความเป็นวัฏจักรของ circular linked list ทำให้เหมาะกับสถานการณ์ที่ต้องวนซ้ำอย่างต่อเนื่อง เช่น เกมกระดานที่วนจากผู้เล่นคนสุดท้ายกลับไปยังคนแรก หรือในอัลกอริทึมคอมพิวเตอร์อย่างการจัดตารางแบบ round-robin
สรุปความซับซ้อนเวลา
เป็นประโยชน์ที่จะเห็นภาพรวมว่า linked list เปรียบเทียบกับลิสต์ของ Python อย่างไรแบบคร่าวๆ:
การดำเนินการ
Singly Linked List
Array/Python List
เข้าถึงด้วยดัชนี
O(n)
O(1)
ค้นหาด้วยค่า
O(n)
O(n)
แทรกที่จุดเริ่มต้น
O(1)
O(n)
แทรกที่จุดสิ้นสุด
O(n)
O(1) แบบแอมอร์ไทซ์
แทรกตรงกลาง
O(n)
O(n)
ลบที่จุดเริ่มต้น
O(1)
O(n)
ลบที่จุดสิ้นสุด
O(n)
O(1) แบบแอมอร์ไทซ์
สรุปสำคัญ: linked list เด่นเรื่องการแทรกและลบที่หัวลิสต์ (O(1)) แต่เสียเปรียบในอย่างอื่น หากไม่ได้เพิ่มหรือลบองค์ประกอบที่จุดเริ่มต้นบ่อย ลิสต์ปกติของ Python มักเป็นตัวเลือกที่ดีกว่า
definsertAtBeginning(self, new_data):
new_node = Node(new_data)# Create a new node
new_node.next= self.head # Next for new node becomes the current head
self.head = new_node # Head now points to the new nodeขับเคลื่อนโดย
เราจะเติม linked list นี้ด้วยชุดคำ เพื่อทำความเข้าใจการทำงานของการแทรกมากขึ้น ก่อนอื่นมาสร้างเมธอดเพื่อไล่ลำดับและพิมพ์เนื้อหาของลิสต์:
defprintList(self):
temp = self.head # Start from the head of the listwhile temp:print(temp.data,end=' ')# Print the data in the current node
temp = temp.next# Move to the next nodeprint()# Ensures the output is followed by a new lineขับเคลื่อนโดย
เมธอดนี้จะพิมพ์เนื้อหาของ linked list มาลองใช้เมธอดที่กำหนดไว้เพื่อเติมลิสต์ด้วยคำชุดต่อไปนี้: “the quick brown fox”
if __name__ =='__main__':# Create a new LinkedList instance
llist = LinkedList()# Insert each letter at the beginning using the method we created
llist.insertAtBeginning('fox')
llist.insertAtBeginning('brown')
llist.insertAtBeginning('quick')
llist.insertAtBeginning('the')# Now 'the' is the head of the list, followed by 'quick', then 'brown' and 'fox'# Print the list
llist.printList()ขับเคลื่อนโดย
if __name__ =='__main__':
llist = LinkedList()# Insert words at the beginning
llist.insertAtBeginning('fox')
llist.insertAtBeginning('brown')
llist.insertAtBeginning('quick')
llist.insertAtBeginning('the')# Insert a word at the end
llist.insertAtEnd('jumps')# Print the list
llist.printList()ขับเคลื่อนโดย
การลบโหนดแรกของ linked list ทำได้ง่าย เพราะเพียงชี้หัวลิสต์ไปยังโหนดที่สอง โหนดแรกก็จะไม่เป็นส่วนหนึ่งของลิสต์อีกต่อไป เพื่อทำสิ่งนี้ ให้เพิ่มเมธอดต่อไปนี้ในคลาส LinkedList:
defdeleteFromBeginning(self):if self.head isNone:return"The list is empty"# If the list is empty, return this string
self.head = self.head.next# Otherwise, remove the head by making the next node the new headขับเคลื่อนโดย
ลบโหนดจากจุดสิ้นสุดของ linked list
ในการลบโหนดสุดท้ายของ linked list ต้องไล่ลำดับเพื่อหาโหนดรองสุดท้าย และเปลี่ยนพอยน์เตอร์ถัดไปของมันเป็น None โหนดสุดท้ายจึงจะไม่เป็นส่วนหนึ่งของลิสต์ คัดลอกเมธอดต่อไปนี้ไปยังคลาส LinkedList เพื่อทำสิ่งนี้:
defdeleteFromEnd(self):if self.head isNone:return"The list is empty"if self.head.nextisNone:
self.head =None# If there's only one node, remove the head by making it Nonereturn
temp = self.head
while temp.next.next:# Otherwise, go to the second-last node
temp = temp.next
temp.next=None# Remove the last node by setting the next pointer of the second-last node to Noneขับเคลื่อนโดย
เมธอดนี้จะตรวจสอบก่อนว่า linked list ว่างหรือไม่ หากว่างจะส่งข้อความแจ้งเตือนกลับไป หากมีเพียงโหนดเดียว โหนดนั้นจะถูกลบ สำหรับลิสต์ที่มีหลายโหนด เมธอดจะหาโหนดรองสุดท้ายและอัปเดตตัวอ้างอิงโหนดถัดไปของมันให้เป็น None
if __name__ =='__main__':
llist = LinkedList()# Insert words at the beginning
llist.insertAtBeginning('fox')
llist.insertAtBeginning('brown')
llist.insertAtBeginning('quick')
llist.insertAtBeginning('the')# Insert a word at the end
llist.insertAtEnd('jumps')# Print the list before deletionprint("List before deletion:")
llist.printList()# Deleting nodes from the beginning and end
llist.deleteFromBeginning()
llist.deleteFromEnd()# Print the list after deletionprint("List after deletion:")
llist.printList()ขับเคลื่อนโดย
โค้ดด้านบนจะพิมพ์ลิสต์ก่อนและหลังการลบ เพื่อแสดงการทำงานของการแทรกและการลบใน linked list โดยคาดว่าจะเห็นผลลัพธ์ดังนี้:
List before deletion:
the quick brown fox jumps
List after deletion:
quick brown foxขับเคลื่อนโดย
ค้นหา linked list เพื่อหาค่าที่ต้องการ
กระบวนการสุดท้ายที่เราจะเรียนรู้ในบทนี้คือการค้นหาค่าเฉพาะใน linked list วิธีการคือเริ่มจากหัวลิสต์และไล่ผ่านแต่ละโหนด ตรวจสอบว่าข้อมูลในโหนดตรงกับค่าที่ค้นหาหรือไม่ ต่อไปนี้คือตัวอย่างการใช้งานจริง:
defsearch(self, value):
current = self.head # Start with the head of the list
position =0# Counter to keep track of the positionwhile current:# Traverse the listif current.data == value:# Compare the list's data to the search valuereturnf"Value '{value}' found at position {position}"# Print the value if a match is found
current = current.next
position +=1returnf"Value '{value}' not found in the list"ขับเคลื่อนโดย
เพื่อค้นหาค่าเฉพาะใน linked list ที่เราสร้างไว้ ให้อัปเดตฟังก์ชันหลักเพื่อรวมเมธอด search ที่เพิ่งสร้าง:
if __name__ =='__main__':
llist = LinkedList()# Insert words at the beginning
llist.insertAtBeginning('fox')
llist.insertAtBeginning('brown')
llist.insertAtBeginning('quick')
llist.insertAtBeginning('the')# Insert a word at the end
llist.insertAtEnd('jumps')# Print the list before deletionprint("List before deletion:")
llist.printList()# Deleting nodes from beginning and end
llist.deleteFromBeginning()
llist.deleteFromEnd()# Print the list after deletionprint("List after deletion:")
llist.printList()# Search for 'quick' and 'lazy' in the listprint(llist.search('quick'))# Expected to findprint(llist.search('lazy'))# Expected not to findขับเคลื่อนโดย
โค้ดข้างต้นจะแสดงผลดังนี้:
List before deletion:
the quick brown fox jumps
List after deletion:
quick brown fox
Value 'quick' found at position 0
Value 'lazy'not found in the listขับเคลื่อนโดย
คำว่า “quick” ถูกพบใน linked list เพราะอยู่ที่ตำแหน่งแรกของลิสต์ ส่วนคำว่า “lazy” ไม่ได้เป็นส่วนหนึ่งของลิสต์ จึงไม่ถูกพบ
บทส่งท้าย
หากอ่านมาถึงตรงนี้ ขอแสดงความยินดี ขณะนี้มีความเข้าใจที่มั่นคงเกี่ยวกับหลักการพื้นฐานของ linked list แล้ว ทั้งโครงสร้าง ประเภท วิธีเพิ่มและลบองค์ประกอบ ตลอดจนการไล่ลำดับ
แต่การเรียนรู้ยังไม่จบเพียงเท่านี้ Linked list เป็นเพียงจุดเริ่มต้นของโลกโครงสร้างข้อมูลและอัลกอริทึม ต่อไปนี้คือแนวทางที่อาจช่วยต่อยอดความเข้าใจให้ลึกซึ้งยิ่งขึ้น:
สร้างโปรเจกต์ของตนเอง
ลงมือปฏิบัติจริงด้วยการผสาน linked list เข้ากับโปรเจกต์ด้านการเขียนโค้ดหรือวิทยาการข้อมูล Linked list ถูกใช้สร้างระบบไฟล์ สร้าง hash table ไปจนถึงระบบนำทาง GPS และเกมกระดาน หากต้องการเริ่มโปรเจกต์ของตนเอง ลองดู โปรเจกต์วิทยาการข้อมูลแบบมีแนะแนวของเราฟรี ที่สอนการแก้ปัญหาโลกจริงด้วย Python, R และ SQL
เรียนรู้โครงสร้างข้อมูลและอัลกอริทึม
การเรียนรู้โครงสร้างข้อมูลอื่นๆ เช่น ต้นไม้ (tree), สแต็ก (stack), และคิว (queue) เป็นการต่อยอดตามธรรมชาติจากความเข้าใจเรื่อง linked list โครงสร้างเหล่านี้ต่อยอดหลักการของ linked list และช่วยแก้ปัญหาการคำนวณได้อย่างมีประสิทธิภาพยิ่งขึ้น ตัวอย่างเช่น ต้นไม้และ binary search tree ขยายแนวคิดของ linked list ไปเป็นโครงสร้างลำดับชั้น ทำให้โหนดหนึ่งเชื่อมต่อกับองค์ประกอบหลายตัวได้
ในบทเรียนนี้ เราได้ใช้งาน singly-linked list ครอบคลุมการแทรก ลบ และไล่ลำดับ
สามารถต่อยอดความรู้ด้วยการเรียนรู้การใช้งาน doubly และ circular linked list ได้ อีกทั้ง skip list ก็เป็นส่วนขยายของ linked list ที่ช่วยให้ค้นหาได้เร็วขึ้นด้วยการเข้าถึงองค์ประกอบได้ฉับไวกว่า
Linked list คือโครงสร้างข้อมูลที่มีบทบาทสำคัญต่อการจัดระเบียบและการจัดการข้อมูล โดยประกอบด้วยโหนด (node) หลายตัวที่ถูกเก็บไว้กระจายกันในหน่วยความจำ ช่วยให้จัดสรรหน่วยความจำได้อย่างมีประสิทธิภาพ แต่ละโหนดของ linked list จะมีองค์ประกอบหลักสองส่วน ได้แก่ ส่วนข้อมูล และตัวอ้างอิงไปยังโหนดถัดไปในลำดับ
หากแนวคิดนี้ดูซับซ้อนในตอนแรก ไม่ต้องกังวล
เราจะอธิบายให้เห็นภาพตั้งแต่พื้นฐานว่า linked list คืออะไร ทำไมจึงใช้ และมีข้อได้เปรียบเฉพาะตัวอย่างไร
ทำไมต้องใช้ Linked List?
Linked list ถูกสร้างขึ้นเพื่อแก้ข้อจำกัดต่างๆ ของการเก็บข้อมูลในลิสต์และอาร์เรย์แบบปกติ ดังรายละเอียดต่อไปนี้:
แทรกและลบได้สะดวก
ในการใช้ลิสต์ทั่วไป การแทรกหรือลบองค์ประกอบ ณ ตำแหน่งใดๆ ที่ไม่ใช่ท้ายรายการ จำเป็นต้องเลื่อนรายการถัดไปทั้งหมดไปยังตำแหน่งใหม่ กระบวนการนี้มีค่าความซับซ้อนเวลา O(n) และอาจทำให้ประสิทธิภาพลดลงอย่างมากเมื่อขนาดลิสต์เติบโต หากยังไม่คุ้นเคยกับหลักการทำงานหรือการใช้งานลิสต์ สามารถอ่าน บทเรียนเกี่ยวกับลิสต์ใน Python ของเราได้
ในทางกลับกัน linked list ทำงานต่างออกไป โดยเก็บองค์ประกอบไว้ในตำแหน่งหน่วยความจำที่ไม่ต่อเนื่องกัน และเชื่อมโยงกันผ่านพอยน์เตอร์ไปยังโหนดถัดไป โครงสร้างนี้ช่วยให้ linked list สามารถเพิ่มหรือลบองค์ประกอบ ณ ตำแหน่งใดๆ ได้ เพียงแค่ปรับการเชื่อมโยงให้รวมโหนดใหม่หรือข้ามโหนดที่ถูกลบ
เมื่อมีตัวอ้างอิงตรงไปยังโหนดที่ตำแหน่งแทรกหรือลบแล้ว การดำเนินการจะเป็น O(1) อย่างไรก็ตาม การค้นหาตำแหน่งนั้นยังคงต้องไล่ลำดับ O(n) ดังนั้นประโยชน์ O(1) จะใช้ได้ก็ต่อเมื่อมีพอยน์เตอร์ไปยังโหนดที่เกี่ยวข้องอยู่แล้ว (เช่น เมื่อทำงานที่หัวลิสต์)
ขนาดยืดหยุ่น
ลิสต์ใน Python เป็น dynamic array ซึ่งหมายความว่าสามารถปรับขนาดได้ยืดหยุ่น
อย่างไรก็ตาม กระบวนการนี้เกี่ยวข้องกับการดำเนินการที่ซับซ้อนหลายอย่าง รวมถึงการจัดสรรอาร์เรย์ใหม่ไปยังบล็อกหน่วยความจำที่ใหญ่ขึ้น การย้ายบล็อกเช่นนี้ไม่มีประสิทธิภาพเพราะต้องคัดลอกองค์ประกอบไปยังบล็อกใหม่ และอาจต้องสำรองพื้นที่มากกว่าที่จำเป็นทันที
ในทางตรงข้าม linked list สามารถขยายและหดตัวได้แบบไดนามิกโดยไม่ต้องจัดสรรหรือปรับขนาดใหม่ ทำให้เหมาะกับงานที่ต้องการความยืดหยุ่นสูง
ใช้หน่วยความจำคุ้มค่า
ลิสต์จะจัดสรรหน่วยความจำสำหรับองค์ประกอบทั้งหมดในบล็อกที่ต่อเนื่องกัน หากลิสต์ต้องเติบโตเกินขนาดตั้งต้น จะต้องจัดสรรบล็อกหน่วยความจำที่ใหญ่ขึ้นแบบต่อเนื่อง และคัดลอกองค์ประกอบเดิมทั้งหมดไปยังบล็อกใหม่นี้ กระบวนการนี้ใช้เวลามากและไม่มีประสิทธิภาพ โดยเฉพาะกับลิสต์ขนาดใหญ่ อีกทั้งหากประเมินขนาดตั้งต้นมากเกินไป หน่วยความจำที่ไม่ได้ใช้งานก็ถูกสูญเปล่า
ตรงกันข้าม linked list จะจัดสรรหน่วยความจำให้แต่ละองค์ประกอบแยกจากกัน ส่งผลให้ใช้หน่วยความจำได้ดีกว่า เพราะสามารถจัดสรรให้กับองค์ประกอบใหม่เมื่อมีการเพิ่มเข้ามา
ควรใช้ Linked List เมื่อไร?
แม้ linked list จะมีข้อดีเหนือกว่าลิสต์และอาร์เรย์ทั่วไป เช่น ขนาดยืดหยุ่นและใช้หน่วยความจำคุ้มค่า แต่ก็มีข้อจำกัดเช่นกัน เนื่องจากต้องเก็บพอยน์เตอร์ของแต่ละองค์ประกอบเพื่ออ้างอิงโหนดถัดไป ทำให้ใช้หน่วยความจำต่อองค์ประกอบมากขึ้น นอกจากนี้ โครงสร้างข้อมูลนี้ไม่รองรับการเข้าถึงข้อมูลแบบสุ่ม ต้องไล่ลำดับทีละโหนดจากจุดเริ่มต้นเพื่อเข้าถึงองค์ประกอบ ส่งผลให้เวลาค้นหาเป็น O(n)
การเลือกใช้ระหว่าง linked list หรืออาร์เรย์จึงขึ้นอยู่กับความต้องการของแอปพลิเคชัน โดย linked list เหมาะที่สุดเมื่อ:
ประเภทของ linked list
Linked list มีอยู่สามประเภท โดยแต่ละแบบมีข้อดีเฉพาะสำหรับสถานการณ์ที่ต่างกัน ได้แก่:
Singly-linked list
Singly-linked list
Singly-linked list เป็นรูปแบบที่ง่ายที่สุด โดยแต่ละโหนดเก็บข้อมูลและตัวอ้างอิงไปยังโหนดถัดไปในลำดับ สามารถไล่ลำดับได้เพียงทิศทางเดียว คือจากหัว (โหนดแรก) ไปยังหาง (โหนดสุดท้าย)
แต่ละโหนดใน singly-linked list มักประกอบด้วยสองส่วน:
เนื่องจากโครงสร้างนี้ไล่ลำดับได้เพียงทิศทางเดียว การเข้าถึงองค์ประกอบตามค่า หรือดัชนีจำเป็นต้องเริ่มจากหัวลิสต์และเลื่อนไปทีละโหนดจนกว่าจะพบโหนดที่ต้องการ การดำเนินการนี้มีค่าความซับซ้อนเวลา O(n) ทำให้ไม่มีประสิทธิภาพนักสำหรับลิสต์ขนาดใหญ่
การแทรกและการลบโหนดที่จุดเริ่มต้นของ singly-linked list ทำได้มีประสิทธิภาพมากที่ O(1) แต่การแทรกหรือลบตรงกลางหรือท้ายลิสต์ต้องไล่ลำดับจนถึงจุดนั้น ทำให้มีความซับซ้อนเวลา O(n)
การออกแบบของ singly-linked list ทำให้เหมาะอย่างยิ่งเมื่อปฏิบัติการส่วนใหญ่เกิดขึ้นที่จุดเริ่มต้นของลิสต์
Doubly-linked list
Doubly-linked list
ข้อเสียอย่างหนึ่งของ singly-linked list คือสามารถไล่ลำดับได้เพียงทิศทางเดียว และไม่สามารถย้อนกลับไปยังโหนดก่อนหน้าเมื่อจำเป็น ข้อจำกัดนี้ทำให้การดำเนินการที่ต้องการการเดินทางแบบสองทิศทางทำได้ยาก
Doubly-linked list แก้ปัญหานี้ด้วยการเพิ่มพอยน์เตอร์อีกหนึ่งตัวในแต่ละโหนด ทำให้ไล่ลำดับได้ทั้งไปข้างหน้าและย้อนกลับ แต่ละโหนดใน doubly-linked list จะมีสามองค์ประกอบ ได้แก่ ข้อมูล พอยน์เตอร์ไปยังโหนดถัดไป และพอยน์เตอร์ไปยังโหนดก่อนหน้า
Circular linked list
Circular linked list
Circular linked list เป็นรูปแบบเฉพาะของ linked list ที่โหนดสุดท้ายชี้กลับไปยังโหนดแรก ทำให้เกิดโครงสร้างแบบวงกลม หมายความว่าไม่เหมือนกับ singly และ doubly linked list ที่เราเห็นก่อนหน้า circular linked list จะไม่มีจุดสิ้นสุด แต่จะวนกลับไปเรื่อยๆ
ความเป็นวัฏจักรของ circular linked list ทำให้เหมาะกับสถานการณ์ที่ต้องวนซ้ำอย่างต่อเนื่อง เช่น เกมกระดานที่วนจากผู้เล่นคนสุดท้ายกลับไปยังคนแรก หรือในอัลกอริทึมคอมพิวเตอร์อย่างการจัดตารางแบบ round-robin
สรุปความซับซ้อนเวลา
เป็นประโยชน์ที่จะเห็นภาพรวมว่า linked list เปรียบเทียบกับลิสต์ของ Python อย่างไรแบบคร่าวๆ:
สรุปสำคัญ: linked list เด่นเรื่องการแทรกและลบที่หัวลิสต์ (O(1)) แต่เสียเปรียบในอย่างอื่น หากไม่ได้เพิ่มหรือลบองค์ประกอบที่จุดเริ่มต้นบ่อย ลิสต์ปกติของ Python มักเป็นตัวเลือกที่ดีกว่า
วิธีสร้าง Linked List ใน Python
เมื่อเข้าใจแล้วว่า linked list คืออะไร ทำไมจึงใช้ และมีรูปแบบใดบ้าง ต่อไปเราจะลงมือใช้งานโครงสร้างข้อมูลนี้ใน Python โน้ตบุ๊กสำหรับบทเรียนนี้มีให้ใน DataLab workbook นี้ หากสร้างสำเนาไว้ จะสามารถแก้ไขและรันโค้ดได้ เหมาะอย่างยิ่งหากพบปัญหาในการรันโค้ดด้วยตนเอง
การเริ่มต้นโหนด
อย่างที่ได้เรียนไปแล้ว โหนดคือองค์ประกอบใน linked list ที่เก็บข้อมูลและตัวอ้างอิงไปยังโหนดถัดไป ต่อไปนี้คือวิธีการกำหนดโหนดใน Python:
โค้ดข้างต้นเป็นการเริ่มต้นโหนดโดยทำสองอย่างหลักๆ คือ กำหนดค่าให้แอตทริบิวต์ “data” เพื่อแทนข้อมูลจริงที่โหนดจะเก็บ และแอตทริบิวต์ “next” ใช้แทนที่อยู่ของโหนดถัดไป ซึ่งขณะนี้ตั้งค่าเป็น None เพื่อบ่งชี้ว่ายังไม่เชื่อมกับโหนดอื่น เมื่อมีการเพิ่มโหนดใหม่ใน linked list แอตทริบิวต์นี้จะถูกอัปเดตให้ชี้ไปยังโหนดถัดไป
การสร้างคลาส linked list
ต่อไปเราจะสร้างคลาส linked list เพื่อรวบรวมการดำเนินการต่างๆ สำหรับจัดการโหนด เช่น การแทรกและการลบ เริ่มจากการเริ่มต้น linked list ก่อน:
ด้วยการตั้งค่า
self.headเป็น None เรากำหนดให้ linked list ว่างในตอนเริ่มต้น และยังไม่มีโหนดใดให้ชี้ไป ตอนนี้จะเริ่มใส่โหนดใหม่เพื่อเติมข้อมูลในลิสต์แทรกโหนดใหม่ที่จุดเริ่มต้นของ linked list
ภายในคลาส
LinkedListเราจะเพิ่มเมธอดเพื่อสร้างโหนดใหม่และวางไว้ที่จุดเริ่มต้นของลิสต์:ทุกครั้งที่เรียกเมธอดนี้ จะมีการสร้างโหนดใหม่พร้อมข้อมูลที่ระบุ พอยน์เตอร์ถัดไปของโหนดใหม่นี้จะชี้ไปยังหัวลิสต์ปัจจุบัน ทำให้โหนดนี้อยู่หน้าสุดของโหนดที่มีอยู่ สุดท้ายจะตั้งให้โหนดที่สร้างใหม่เป็นหัวของลิสต์
เราจะเติม linked list นี้ด้วยชุดคำ เพื่อทำความเข้าใจการทำงานของการแทรกมากขึ้น ก่อนอื่นมาสร้างเมธอดเพื่อไล่ลำดับและพิมพ์เนื้อหาของลิสต์:
เมธอดนี้จะพิมพ์เนื้อหาของ linked list มาลองใช้เมธอดที่กำหนดไว้เพื่อเติมลิสต์ด้วยคำชุดต่อไปนี้: “the quick brown fox”
โค้ดข้างต้นควรแสดงผลลัพธ์ดังนี้:
"the quick brown fox"แทรกโหนดใหม่ที่จุดสิ้นสุดของ linked list
ต่อไปจะสร้างเมธอดชื่อ
insertAtEndภายในคลาสLinkedListเพื่อสร้างโหนดใหม่ที่ท้ายลิสต์ หากลิสต์ว่าง โหนดใหม่จะกลายเป็นหัวของลิสต์ มิฉะนั้นจะถูกต่อท้ายโหนดสุดท้ายของลิสต์ มาลองดูการทำงานจริง:เมธอดนี้เริ่มจากการสร้างโหนดใหม่ จากนั้นตรวจสอบว่าลิสต์ว่างหรือไม่ หากว่างจะกำหนดให้โหนดใหม่นั้นเป็นหัวลิสต์ มิฉะนั้นจะไล่ลำดับเพื่อหาโหนดสุดท้าย และตั้งพอยน์เตอร์ของโหนดนั้นให้ชี้ไปยังโหนดใหม่
ตอนนี้ให้นำเมธอดนี้ไปรวมในคลาส
LinkedListและใช้เพิ่มคำที่ท้ายลิสต์ โดยแก้ไขฟังก์ชันหลักให้เป็นดังนี้:สังเกตว่าเราเรียกใช้เมธอด
insertAtEndเพื่อพิมพ์คำว่า “jumps” ที่ท้ายลิสต์ โค้ดข้างต้นควรแสดงผลดังนี้:"the quick brown fox jumps"ลบโหนดจากจุดเริ่มต้นของ linked list
การลบโหนดแรกของ linked list ทำได้ง่าย เพราะเพียงชี้หัวลิสต์ไปยังโหนดที่สอง โหนดแรกก็จะไม่เป็นส่วนหนึ่งของลิสต์อีกต่อไป เพื่อทำสิ่งนี้ ให้เพิ่มเมธอดต่อไปนี้ในคลาส
LinkedList:ลบโหนดจากจุดสิ้นสุดของ linked list
ในการลบโหนดสุดท้ายของ linked list ต้องไล่ลำดับเพื่อหาโหนดรองสุดท้าย และเปลี่ยนพอยน์เตอร์ถัดไปของมันเป็น None โหนดสุดท้ายจึงจะไม่เป็นส่วนหนึ่งของลิสต์ คัดลอกเมธอดต่อไปนี้ไปยังคลาส
LinkedListเพื่อทำสิ่งนี้:เมธอดนี้จะตรวจสอบก่อนว่า linked list ว่างหรือไม่ หากว่างจะส่งข้อความแจ้งเตือนกลับไป หากมีเพียงโหนดเดียว โหนดนั้นจะถูกลบ สำหรับลิสต์ที่มีหลายโหนด เมธอดจะหาโหนดรองสุดท้ายและอัปเดตตัวอ้างอิงโหนดถัดไปของมันให้เป็น None
คราวนี้มาอัปเดตฟังก์ชันหลักเพื่อลบองค์ประกอบจากจุดเริ่มต้นและจุดสิ้นสุดของ linked list:
โค้ดด้านบนจะพิมพ์ลิสต์ก่อนและหลังการลบ เพื่อแสดงการทำงานของการแทรกและการลบใน linked list โดยคาดว่าจะเห็นผลลัพธ์ดังนี้:
ค้นหา linked list เพื่อหาค่าที่ต้องการ
กระบวนการสุดท้ายที่เราจะเรียนรู้ในบทนี้คือการค้นหาค่าเฉพาะใน linked list วิธีการคือเริ่มจากหัวลิสต์และไล่ผ่านแต่ละโหนด ตรวจสอบว่าข้อมูลในโหนดตรงกับค่าที่ค้นหาหรือไม่ ต่อไปนี้คือตัวอย่างการใช้งานจริง:
เพื่อค้นหาค่าเฉพาะใน linked list ที่เราสร้างไว้ ให้อัปเดตฟังก์ชันหลักเพื่อรวมเมธอด search ที่เพิ่งสร้าง:
โค้ดข้างต้นจะแสดงผลดังนี้:
คำว่า “quick” ถูกพบใน linked list เพราะอยู่ที่ตำแหน่งแรกของลิสต์ ส่วนคำว่า “lazy” ไม่ได้เป็นส่วนหนึ่งของลิสต์ จึงไม่ถูกพบ
บทส่งท้าย
หากอ่านมาถึงตรงนี้ ขอแสดงความยินดี ขณะนี้มีความเข้าใจที่มั่นคงเกี่ยวกับหลักการพื้นฐานของ linked list แล้ว ทั้งโครงสร้าง ประเภท วิธีเพิ่มและลบองค์ประกอบ ตลอดจนการไล่ลำดับ
แต่การเรียนรู้ยังไม่จบเพียงเท่านี้ Linked list เป็นเพียงจุดเริ่มต้นของโลกโครงสร้างข้อมูลและอัลกอริทึม ต่อไปนี้คือแนวทางที่อาจช่วยต่อยอดความเข้าใจให้ลึกซึ้งยิ่งขึ้น:
สร้างโปรเจกต์ของตนเอง
ลงมือปฏิบัติจริงด้วยการผสาน linked list เข้ากับโปรเจกต์ด้านการเขียนโค้ดหรือวิทยาการข้อมูล Linked list ถูกใช้สร้างระบบไฟล์ สร้าง hash table ไปจนถึงระบบนำทาง GPS และเกมกระดาน หากต้องการเริ่มโปรเจกต์ของตนเอง ลองดู โปรเจกต์วิทยาการข้อมูลแบบมีแนะแนวของเราฟรี ที่สอนการแก้ปัญหาโลกจริงด้วย Python, R และ SQL
เรียนรู้โครงสร้างข้อมูลและอัลกอริทึม
การเรียนรู้โครงสร้างข้อมูลอื่นๆ เช่น ต้นไม้ (tree), สแต็ก (stack), และคิว (queue) เป็นการต่อยอดตามธรรมชาติจากความเข้าใจเรื่อง linked list โครงสร้างเหล่านี้ต่อยอดหลักการของ linked list และช่วยแก้ปัญหาการคำนวณได้อย่างมีประสิทธิภาพยิ่งขึ้น ตัวอย่างเช่น ต้นไม้และ binary search tree ขยายแนวคิดของ linked list ไปเป็นโครงสร้างลำดับชั้น ทำให้โหนดหนึ่งเชื่อมต่อกับองค์ประกอบหลายตัวได้
หากแนวคิดเหล่านี้ยังดูไม่คุ้น ไม่ต้องกังวล Datacamp มีคอร์สทั้งคอร์สเกี่ยวกับ โครงสร้างข้อมูลและอัลกอริทึม ใน Python ที่จะพาคุณเรียนรู้เชิงลึก เริ่มจากสแต็ก ต้นไม้ แฮชเทเบิล คิว และกราฟ จากนั้นจะทำความเข้าใจอัลกอริทึมการค้นหาและการเรียงลำดับ ช่วยให้เขียนโปรแกรมและแก้ปัญหาได้มีประสิทธิภาพมากขึ้น
สำรวจแนวคิดขั้นสูงของ linked list
ในบทเรียนนี้ เราได้ใช้งาน singly-linked list ครอบคลุมการแทรก ลบ และไล่ลำดับ
สามารถต่อยอดความรู้ด้วยการเรียนรู้การใช้งาน doubly และ circular linked list ได้ อีกทั้ง skip list ก็เป็นส่วนขยายของ linked list ที่ช่วยให้ค้นหาได้เร็วขึ้นด้วยการเข้าถึงองค์ประกอบได้ฉับไวกว่า
การเรียนรู้โครงสร้างข้อมูลขั้นสูงเหล่านี้จะยกระดับทักษะทางเทคนิคและเพิ่มขีดความสามารถในการเขียนโปรแกรม เตรียมพร้อมสำหรับความท้าทายที่ซับซ้อนยิ่งขึ้นในสาขาอย่างวิทยาการข้อมูล การพัฒนาซอฟต์แวร์ และวิศวกรรมแมชชีนเลิร์นนิง
หากต้องการบทนำที่เป็นมิตรกับผู้เริ่มต้นก่อนเข้าสู่หัวข้อขั้นสูง ลองสำรวจเส้นทางทักษะ Python Programming ของเรา ซึ่งมีคอร์สต่อเนื่องที่จะสอนพื้นฐานของภาษา