數據結構-02-鏈表(Linkied List)

##Linked List - 鏈表 鏈表是線性表的一種。線性表是最基本、最簡單、也是最經常使用的一種數據結構。線性表數據元素之間的關係是一對一的關係,即除了第一個和最後一個數據元素以外,其它數據元素都是首尾相接的。線性表有兩種存儲方式,一種是順序存儲結構,另外一種是鏈式存儲結構。咱們經常使用的數組就是一種典型的順序存儲結構。node

相反,鏈式存儲結構就是兩個相鄰的元素在內存中可能不是相鄰的,每個元素都有一個指針域,指針域通常是存儲着到下一個元素的指針。這種存儲方式的優勢是插入和刪除的時間複雜度爲 O(1),不會浪費太多內存,添加元素的時候纔會申請內存,刪除元素會釋放內存。缺點是訪問的時間複雜度最壞爲 O(n)。數組

順序表的特性是隨機讀取,也就是訪問一個元素的時間複雜度是O(1),鏈式表的特性是插入和刪除的時間複雜度爲O(1)。數據結構

鏈表就是鏈式存儲的線性表。根據指針域的不一樣,鏈表分爲單向鏈表、雙向鏈表、循環鏈表等等。翻譯

請看示例:指針

class listNode:
	def __init__(self, val):
		self.val = val
		self.next = None

##鏈表的基本操做 ###反轉鏈表 ####單向鏈表code

鏈表的基本形式是:1 -> 2 -> 3 -> null,反轉須要變爲 3 -> 2 -> 1 -> null。這裏要注意:內存

  • 訪問某個節點 curt.next 時,要檢驗 curt 是否爲 null。
  • 要把反轉後的最後一個節點(即反轉前的第一個節點)指向 null。

示例:ci

class listNode:
	def __init__(self, val):
		self.val = val
		self.next = None


	def reserse(self, head):

		prev = None

		while head:
			temp = head.next
			head.next = prev
			prev = head
			head = temp
			
		return prev

####雙向鏈表it

和單向鏈表的區別在於:雙向鏈表的反轉核心在於next和prev域的交換,還須要注意的是當前節點和上一個節點的遞推。ast

class DListNode:
    def __init__(self, val):
        self.val = val
        self.prev = self.next = null

    def reverse(self, head):
        curt = None
        while head:
            curt = head
            head = curt.next
            curt.next = curt.prev
            curt.prev = head
        return curt

###刪除鏈表中的某個節點 刪除鏈表中的某個節點必定須要知道這個點的前繼節點,因此須要一直有指針指向前繼節點。還有一種刪除是僞刪除,是指複製一個和要刪除節點值同樣的節點,而後刪除,這樣就沒必要知道其真正的前繼節點了。

而後只須要把 prev -> next = prev -> next -> next 便可。可是因爲鏈表表頭可能在這個過程當中產生變化,致使咱們須要一些特別的技巧去處理這種狀況。就是下面提到的 Dummy Node。

###鏈表指針的魯棒性 綜合上面討論的兩種基本操做,鏈表操做時的魯棒性問題主要包含兩個狀況:

  • 當訪問鏈表中某個節點 curt.next 時,必定要先判斷 curt 是否爲 null。
  • 所有操做結束後,判斷是否有環;如有環,則置其中一端爲 null。 ###Dummy Node Dummy node 是鏈表問題中一個重要的技巧,中文翻譯叫「啞節點」或者「假人頭結點」。

Dummy node 是一個虛擬節點,也能夠認爲是標杆節點。Dummy node 就是在鏈表表頭 head 前加一個節點指向 head,即 dummy -> head。Dummy node 的使用多針對單鏈表沒有前向指針的問題,保證鏈表的 head 不會在刪除操做中丟失。除此以外,還有一種用法比較少見,就是使用 dummy node 來進行head的刪除操做,好比 Remove Duplicates From Sorted List II,通常的方法current = current.next 是沒法刪除 head 元素的,因此這個時候若是有一個dummy node在head的前面。

因此,當鏈表的 head 有可能變化(被修改或者被刪除)時,使用 dummy node 能夠很好的簡化代碼,最終返回 dummy.next 即新的鏈表。

快慢指針 快慢指針也是一個能夠用於不少問題的技巧。所謂快慢指針中的快慢指的是指針向前移動的步長,每次移動的步長較大即爲快,步長較小即爲慢,經常使用的快慢指針通常是在單鏈表中讓快指針每次向前移動2,慢指針則每次向前移動1。快慢兩個指針都從鏈表頭開始遍歷,因而快指針到達鏈表末尾的時候慢指針恰好到達中間位置,因而能夠獲得中間元素的值。快慢指針在鏈表相關問題中主要有兩個應用:

快速找出未知長度單鏈表的中間節點 設置兩個指針 fast、slow 都指向單鏈表的頭節點,其中fast的移動速度是slow的2倍,當*fast指向末尾節點的時候,slow正好就在中間了。 判斷單鏈表是否有環 利用快慢指針的原理,一樣設置兩個指針 *fast、*slow 都指向單鏈表的頭節點,其中 fast的移動速度是slow的2倍。若是 *fast = NULL,說明該單鏈表 以 NULL結尾,不是循環鏈表;若是 *fast = *slow,則快指針追上慢指針,說明該鏈表是循環鏈表。

###快慢指針 快慢指針也是一個能夠用於不少問題的技巧。所謂快慢指針中的快慢指的是指針向前移動的步長,每次移動的步長較大即爲快,步長較小即爲慢,經常使用的快慢指針通常是在單鏈表中讓快指針每次向前移動2,慢指針則每次向前移動1。快慢兩個指針都從鏈表頭開始遍歷,因而快指針到達鏈表末尾的時候慢指針恰好到達中間位置,因而能夠獲得中間元素的值。快慢指針在鏈表相關問題中主要有兩個應用:

快速找出未知長度單鏈表的中間節點 設置兩個指針 *fast、*slow 都指向單鏈表的頭節點,其中*fast的移動速度是*slow的2倍,當*fast指向末尾節點的時候,slow正好就在中間了。 判斷單鏈表是否有環 利用快慢指針的原理,一樣設置兩個指針 *fast、*slow 都指向單鏈表的頭節點,其中 *fast的移動速度是*slow的2倍。若是 *fast = NULL,說明該單鏈表 以 NULL結尾,不是循環鏈表;若是 *fast = *slow,則快指針追上慢指針,說明該鏈表是循環鏈表。

class NodeCircle:
    def __init__(self, val):
        self.val = val
        self.next = None

    def has_circle(self, head):
        slow = head
        fast = head
        while (slow and fast):
            fast = fast.next
            slow = slow.next
            if fast:
                fast = fast.next
            if fast == slow:
                break
        if fast and slow and (fast == slow):
            return True
        else:
            return False
相關文章
相關標籤/搜索