數據結構之鏈表【上】

1. 什麼是鏈表

鏈表是經過指針把一組零散的內存塊串聯在一塊兒的線性數據結構。算法

鏈表和數組的內存分佈以下圖所示:segmentfault

能夠看出,鏈表和數組的最大區別在於,數組須要一塊連續的內存空間來存儲,對內存的要求較高。而鏈表不須要連續的內存空間,它經過指針將一組零散的內存塊串聯起來使用。數組

根據指針的不一樣使用方式,鏈表又能夠分爲單鏈表、雙向鏈表和循環鏈表。緩存

  • 單鏈表數據結構

    • 結點包括當前數據和後繼結點的地址
  • 雙向鏈表spa

    • 結點包括當前數據、前驅結點的地址和後繼結點的地址
  • 循環鏈表指針

    • 結點包括當前數據和後繼結點的地址,尾結點的指針指向頭結點
  • 雙向循環鏈表blog

    • 結點包括當前數據、前驅結點的地址和後繼結點的地址,尾結點的後繼結點是頭結點,頭結點的前驅結點是尾結點

2. 鏈表的基本操做及其複雜度

2.1 查找

想要隨機訪問鏈表的第 k 個元素,就沒有數組那麼高效了。由於鏈表中的數據並不是連續存儲的,不能像數組那樣,根據下標和首地址,經過尋址公式就能直接計算出對應的內存地址。而是要根據指針一個結點一個結點的依次遍歷。所以,須要 O(n) 的時間複雜度。ip

特別的,對於雙向鏈表,給定一個結點,要找出其前驅結點時,時間複雜度爲 O(1),單鏈表則仍爲 O(n)。內存

2.2 插入、刪除

前一篇中咱們提到,進行數組的插入、刪除操做時,爲了保證內存的連續性,須要作大量的數據搬移,因此時間複雜度是 O(n)。

而在鏈表中插入、刪除時,由於不須要爲了保持內存的連續性而搬移結點,因此是很是快速的,只須要 O(1) 的時間複雜度。

雖然鏈表的插入、刪除操做時間複雜度只要 O(1),可是,實際狀況下卻並不是如此。由於,在實際開發中,還須要定位到進行操做的位置。例如,在鏈表中刪除一個數據有多是這兩種狀況:

  • 刪除結點中「值等於某個給定值」的結點
  • 刪除給定指針指向的結點

對於第一種狀況,須要對鏈表進行遍歷,找到相應的位置而後刪除,此時刪除操做的時間複雜度爲 O(n)。

對於第二種狀況,已知了要刪除的結點,可是刪除某個結點須要知道它的前驅結點,對於單鏈表仍然須要遍歷尋找,時間複雜度爲 O(n);而對於雙向鏈表,能夠直接找到,因此時間複雜度爲 O(1)。這也是雙向鏈表在實際開發中常常使用的緣由。

3. 鏈表和數組的比較

鏈表和數組是兩種大相徑庭的內存組織方式,正因如此,它們插入、刪除、隨機訪問的時間複雜度正好相反。

數組使用的是連續的內存空間,能夠利用空間局部性原理,藉助 CPU cache 進行預讀,因此訪問效率更高。而鏈表不是連續存儲,沒法進行緩存,隨機訪問效率也較低。

數組的缺點是大小固定,一經聲明就要佔用整塊連續的內存空間。若是聲明的數組過大,系統可能沒有足夠的連續內存空間用於分配,就會致使「內存不足(out of memory)」。而若是聲明的數組太小,當不夠用時,又須要從新申請一塊更大的內存,而後進行數據拷貝,很是費時。

而鏈表則沒有大小限制,支持動態擴容。固然,由於鏈表中每一個結點都須要存儲前驅 / 後繼結點的指針,因此內存消耗會翻倍。並且,對鏈表頻繁的插入、刪除操做會致使頻繁的內存申請和釋放,容易形成內存碎片和觸發垃圾回收(Garbage Collection, GC)。


本文是《數據結構與算法之美》的讀書筆記,首發於公衆號《代碼寫完了》

相關文章
相關標籤/搜索