# [資演] 6- Linked List (III): Circular Linked List 上的操作 ###### tags: `資演` ## 創建一個 circular linked list 前面已經說過,circular linked list 只是把 singly linked list 的 tail 連到 head 上。我們首先創建一個只有一個節點的circular linked list: ![](https://hackmd.io/_uploads/rym91LCTF.png) 和前面的singly linked list類似地,首先我們建立一個`CircularLinkedList`物件,這個物件含有兩個屬性,`head`和`tail`。 接著,我們建立一個節點,並將這個節點插入到前面建立的`CircularLinkedList`物件中,也就是將`head`和`tail`都設定為這個節點。因為現在創建的是circular linked list,所以這個節點的next必須指到自己。 具體流程如下: ![](https://hackmd.io/_uploads/ByaAJIRat.png) 注意到在建立物件之前,我們必須要先有相應的類別(class),才能創建那個類別的實例(instance)。建立類別的程式碼也和singly linked list類似: ``` class Node: def __init__(self, value=None): self.value = value self.next = None class CircularLinkedList: def __init__(self): self.head = None self.tail = None ``` 如此我們便可以在CircularLinkedList類別實作create方法: ``` class CircularLinkedList: ... def create(self, nodeValue): node = Node(nodeValue) node.next = node self.head = node self.tail = node ``` ## Circular linked list 的插入操作 和singly linked list的狀況類似,對於一個非空的circular linked list,我們想插入一個節點,同樣可以分為以下三種情形: 1. 在 head 之前插入,所插入節點成為新的 head 2. 在中間插入,插入的節點成為中間的節點 3. 在 tail 之後插入,所插入節點成為新的 tail 注意對於circular linked list並沒有真正的頭或尾,在這邊給定兩個屬性`head`和`tail`只是為了方便起見。有些實作只會使用`head`或`tail`的其中一個。 circular linked list上的插入和singly linked list類似,差別只在於要注意circular linked list的的`tail`必須接到`head`上,也就是,`tail`的`next`必須是`head`。 由以上邏輯我們可以畫出插入操作的流程圖: ![](https://hackmd.io/_uploads/SkYq7u0aY.png) 其程式碼如下: ``` class CircularLinkedList: ... def insert(self, nodeValue, location): if self.head is None: return "The head reference is None" else: newNode = Node(value) if location == 0: newNode.next = self.head self.head = newNode self.tail.next = newNode else: tempNode = self.head index = 0 while index < location - 1: tempNode = tempNode.next index += 1 nextNode = tempNode.next tempNode.next = newNode newNode.next = nextNode ``` ## 巡訪 circular linked list Circular linked list 的巡訪和singly linked list的完全相同,故在此不再重複介紹。 ## 在 circular linked list 裡面尋找一個元素 Circular linked list 的search和singly linked list的完全相同,故在此亦不再重複介紹。 ## Circular linked list 的刪除操作 對於 circular linked list 的刪除操作,我們一樣可以分為三種情形來看: 1. 刪除第一個節點,即head 2. 刪除中間節點 3. 刪除最後一個節點,即tail 我們同樣可以採用和singly linked list類似的思路,只是要注意circular linked list的的`tail`必須接到`head`上。 ![](https://hackmd.io/_uploads/SkR-nYATt.png) 其程式碼實作如下: ``` class CircularLinkedList: ... def delete(self, location): if self.head is None: print("The list is empty.") else: if location == 0: if self.head == self.tail: self.head.next = None self.head = None self.tail = None else: self.head = self.head.next self.tail.next = self.head else: tempNode = self.head index = 0 while index < location - 1: tempNode = tempNode.next index += 1 nextNode = tempNode.next tempNode.next = nextNode.next ``` ## 例題 * [探測Linked List裡面有沒有cycle](https://leetcode.com/problems/linked-list-cycle/) 給定一個linked list,我們想寫一個函式,探測這個linked list有沒有cycle(循環)在裡面。如果有cycle,回傳`True`,否則回傳`False`。 ![](https://hackmd.io/_uploads/Skl8nRe0Y.png) ![](https://hackmd.io/_uploads/Bk3LhReAt.png) ::: spoiler 提示 使用Floyd's Cycle Finding Algorithm:想像有兩個跑者在跑道上用不同的速度跑步,如果跑道是一個圈圈,會發生什麼事? ::: ::: spoiler 解答 當兩個跑者在跑道上用不同的速度跑步時,如果跑道是一個圈圈,那麼跑得快的人最後會從後面追上跑得慢的人(但比他多跑了一圈)。所以我們只要寫一個程式讓兩個指針去賽跑,如果兩個指針在某個時間點重合了,那就代表這個跑道上有圈圈;否則,就代表跑道上沒有圈圈。 ``` class Solution: def hasCycle(self, head: ListNode) -> bool: if head is None: return False slow = head fast = head.next while slow != fast: if fast is None or fast.next is None: return False slow = slow.next fast = fast.next.next return True ::: * [插入一個已排序好的circular linked list](https://leetcode.com/problems/insert-into-a-sorted-circular-linked-list/) 給定一個已經由小到大排序好的circular linked list的head,我們想要寫一個函式,這個函式可以把一個新的值`insertVal`插入這個已經排序好的circular linked list裡面,並且讓它保持為一個排序好的circular linked list。如果給定的是一個空的list,則創建一個新的circular linked list,並回傳那個插入的節點。 注意到在這邊我們回傳的節點可以是這個circular linked list上的任何一個節點,因為對於circular linked list並沒有真正意義上的`head`。 ![](https://hackmd.io/_uploads/B1e3HXgCK.png) ![](https://hackmd.io/_uploads/B1onSmxCF.png) ::: spoiler 解答 這題我們一樣可以使用兩個跑者,`curr`和`prev`,但這次他們的速度是一樣的,只是`curr`比`prev`快一格。這樣當我們找到適合的位置插入時,就可以直接插入這兩個節點的中間。 ``` class Solution: def insert(self, head: 'Node', insertVal: int) -> 'Node': node = Node(insertVal) if not head: node.next = node return node prev, curr = head, head.next while prev.next != head: if prev.val <= node.val <= curr.val: break if prev.val > curr.val and (node.val > prev.val or node.val < curr.val): break prev, curr = prev.next, curr.next # Insert node node.next = curr prev.next = node return head :::