数据结构 | 如何一文搞定链表问题?(附20本书获奖名单)
這是本公號的第127篇原創。
近期看到一個數據結構題目,翻轉鏈表。動手寫了下代碼,手生了不少,發現好鐵不用也會生銹,大腦也如此。
于是就整體回顧了一下鏈表的常見操作和數據結構題,整理下分享出來,萬一對讀者有幫助呢?想必那是極好的!目錄如下~
鏈表基礎知識
鏈表常見操作
鏈表常見題目
鏈表基礎知識
鏈表和順序表相對應,順序表將表中的元素按照順序放置于連續的存儲區間,便于訪問和查找。而鏈表則將元素放置于一系列結點中,各結點空間不要去連續,雖然給訪問和查找帶來了一定不便,但是在元素的刪除和插入等操作上有著得天獨厚的優勢。(數組和鏈表結構的在復雜度方面的優缺點你get到了嗎?)
鏈表的結點包括數據域和指針域兩部分,顧名思義,數據域保存著元素的數據信息,指針域保存著下一個結點的指針標識。當然 Python 中沒有指針這個概念,刷題中也是模擬指針的概念進行操作。
談到鏈表一定不能避開的兩個定義:頭結點和頭指針。
頭結點的設立是為了便于操作,指的是放在第一個元素結點前的結點。不是鏈表結構必不可少的一部分。頭指針則不一樣,屬于必不可少的一部分,指向鏈表結構的第一個結點(頭結點存在的時候指向頭結點)。通常我們對鏈表的訪問和各操作都是基于頭指針進行的。
鏈表分類則可以根據不同方式:
從內存角度出發:?鏈表可分為 靜態鏈表、動態鏈表。?
從鏈表存儲方式的角度出發: 鏈表可分為 單鏈表、雙鏈表、以及循環鏈表。
鏈表的常見操作包括但不局限于求鏈表長度、結點的刪除、插入等,但小詹之前文章說過許多次,Python 中由于不存在指針,所以指針和鏈表指的都是模擬指針和鏈表。所以這里的常見操作應當加上鏈表的構建操作。
1.首先是用 Python 構建鏈表
如上所述,鏈表由一系列結點組成,所以構建鏈表的第一步是定義一個結點類。
class?Node(object):????"""
????定義一個節點類
????"""
????def?__init__(self,data):
????????self.data?=?data
????????self.next?=?None
有了結點,下一步就是構建鏈表了。如下的 InitList() 方法用一個非空 data 構建的鏈表,并定義了一個 PrintList() 方法打印出鏈表結果。
????"""
????創建一個鏈表類,包括判斷是否為空、打印鏈表
????"""
????def?__init__(self):
????????self.head?=?Node(None)?
????def?InitList(self,?data):
????????if?len(data)?==?0:
????????????print('\nIt?is?a?empty?link?list')
????????????return?False
????????self.head?=?Node(data[0])#頭結點,data的一個元素為數據域信息
????????p?=?self.head?#頭指針,指向第一個結點
????????for?i?in?data[1:]:
????????????node?=?Node(i)
????????????p.next?=?node
????????????p?=?p.next
????????def?IsEmpty(self):
????????p?=?self.head?#指向第一個結點
????def?IsEmpty(self):
????????p?=?self.head?#指向第一個結點????
????????if?p?==?None:
#???????????print('\nIt?is?a?empty?link?list')
????????????return?True?#是空鏈表????
#???????print('\nIt?is?not?empty')
????????return?False?#非空鏈表
????def?PrintList(self):
????????p?=?self.head
????????while?p:?#注意不是p.next
????????????print(p.data,?end?=?'?')?#end表示結束的標識,不添加說明時默認為換行符
????????????p?=?p.next?????
這是一個簡單的構建鏈表方法并將其打印出來,可以測試下結果。
????"""?
????測試
????"""
????data?=?[1,?2,?3,?4,?5]????
????lst?=?LinkList()
????lst.InitList(data)
????lst.PrintList()
main()????
對應的結果為:
2.其次是鏈表長度的求取
鏈表長度的求取在使用中經常遇到,所以構建了一個鏈表后,我們應當把一些常見的操作封裝到一起,為此,可以創建一個求表長的方法,通過便歷鏈表所有結點即可。
????????"""
????????求取鏈表長度
????????"""
????????p?=?self.head
????????size?=?0
????????while?p:
????????????size?+=?1
????????????p?=?p.next
????????return?size?
3.還有常見的結點刪除與插入
開篇提到順序表對元素的查找和訪問比較便捷,但是涉及到元素刪除和插入就比較雞肋了,鏈表中就相反,非常的便捷!
需要注意情況分類,有大概 3 種情況。且刪除位置 n 應當有區間限制。
當鏈表只有一個結點的時候
當刪除的是第一個結點的時候
其他普適情況
對于第一種,只需要直接將頭結點為 None 即可;第二種情況和普適情況類似,只是普適情況的前驅結點在第二種情況為頭結點。重點還在普適情況的刪除操作,可以簡單的將前一個結點的指針指向待刪除結點的下一個結點即可跳過該結點。代碼如下所示:
????"""
????刪除第n個結點,注意特殊邊界情況
????"""
????if?self.IsEmpty()?or?n?<?0?or?n?>?self.Size():
????????print('error?occured')
????????return
????p?=?self.head?
????if?p.next?is?None:???#?當只有一個節點的鏈表時
????????self.head?=?None
????????return????????
????if?n?==?1:???#?當刪除第一個節點時
????????self.head?=?p.next
????????return
????#普通情況
????p?=?self.head????????
????index?=?1
????while?index?<?n:#找到要刪除的結點位置
????????pre?=?p
????????index?+=?1
????????p?=?p.next?????
??????pre.next?=?p.next#跳過該結點即可刪除
??????p?=?None
以上是刪除操作,考慮了各種刪除情況。插入操作和刪除有點類似倒轉的感覺,只需要找到待插入位置,打斷前后兩個結點的指針,改變 3 者的指向關系即可。插入位置n范圍應當在閉區間(0,鏈表長度)之間。值得注意的是這里也需要分情況討論,許多教程忽略了邊緣情況,這里著重討論下。
插入的鏈表為空鏈表
插入在鏈表的頭結點處
普適情況
前倆種情況類似,只用將插入結點做頭結點即可,普適情況只需要將該結點的前后位置結點指針做些調整即可,代碼給出如下,可以手動畫圖方便理解噢。
????"""
????把data插入第n個結點之后的位置
????"""
????if?n?<?0?or?n?>?self.Size()?+?1:?
????#n最小值為0,且插入位置超過長度+1時實際只能插入到最后,即最大值為鏈表長【0,size】區間內有效
????????print('error?occured')
????????return
????p?=?self.head
????if?self.IsEmpty():#空鏈表時插入頭指針之后即可
????????node?=?Node(data)
????????p.next?=?node
????????return
????if?n?==?0:
????????node?=?Node(data)
????????lat?=?self.head
????????self.head?=?node?#新插入的做頭結點
????????node.next?=?lat?#新插入結點后續指向之前的頭結點
????????return
????index?=?1
????while?index?<?n:
????????index?+=?1
????????p?=?p.next
????????#插入操作關鍵之處,注意指針變換順序
??????node?=?Node(data)
??????node.next?=?p.next
??????p.next?=?node
這里重要講解 2 個題型。
1.鏈表翻轉問題
簡單的說就是將一個單鏈表翻轉過來,這是 LeetCode 的第 206 題,在鏈表題目中很有分量。
輸出:5->4->3->2->1
這個題目其實不算難,思路比較清晰,只需要從前往后依次將結點指向顛倒,例如:
第1輪:5<-4??3->2->1
第2輪:5<-4<-3??2->1
第3輪:5<-4<-3<-2??1
第4輪:5<-4<-3<-2<-1
關鍵在于指針的轉換順序不能顛倒變換,建議自己按照我上邊給的示例進行繪圖幫助理解,代碼如下:
????"""
????leetcode題目中提交函數都只用放在此類下
????"""
????def?reverseList(self,?head):
????????"""
????????翻轉操作leetcode題目206
????????"""
????????cur,?pre?=?head,?None
????????while?cur:
????????????cur.next,?pre,?cur?=?pre,?cur,?cur.next
????????????#這一行等同于如下?4?行:
????????????#lat?=?cur.next
????????????#cur.next?=?pre
????????????#pre?=?cur
????????????#cur?=?lat
????????return?pre
鏈表有環問題也是一個常見的問題了,之前在總結雙指針類型問題(見文末推薦閱讀)時候講過,可以去回顧下。思路如下:
定義兩個指針 ,一快一慢 。比如慢指針一次移動 1 個位置 ,快指針移動 2 個 。
初始快慢指針放在一個位置 ,并開始循環移動 。
如果有環 ,那么隨著移動的進行 ,終有快指針經過環遇到并超過慢指針的時候 ,那么這就可以用來判斷是否存在環的依據啦 。
前幾天送的 20 本簽名書名單出來啦,恭喜這些 B 如此幸運,快來看看有沒有你自己,如果沒有,繼續混臉熟,希望下一次就是你!記得添加微信領獎(python_jiang),24小時后視作棄權噢~
名單:FavoreiteCheese;tj;阿飄-沖鋒版;明日之前;老孫9857;魏星;Nevermore陽;部落大圣;D;mascot;微笑;Cosmos;飛;心雨;杜明羅;,。;陳偉;梅破知春近;寂夜;LuoYe;
推薦閱讀:
LeetCode 系列——雙指針問題 。
LeetCode | 你不得不了解的哈希算法 !
總結
以上是生活随笔為你收集整理的数据结构 | 如何一文搞定链表问题?(附20本书获奖名单)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机器学习中如何处理不平衡数据?
- 下一篇: 快速入门numpy