資料結構基礎 連結串列

kirito_song發表於2018-12-06
資料結構基礎  連結串列

目錄

  • 基本性質
  • 連結串列的分類
    • 按連線方向分類
    • 按照有無迴圈分類
  • 連結串列問題程式碼實現的關鍵點
  • 連結串列插入和刪除的注意事項
  • 連結串列翻轉
  • 向一個有序的環境連結串列中插入一個節點,並保持依舊有序
  • 對於一個單連結串列,在不給定head的情況下刪除指定node。要求時間複雜度O(1)
  • 給定一個連結串列,與一個陣列num。要求實現荷蘭國旗
  • 給定兩個有序連結串列的head,列印共同部分
  • 給定一個單連結串列的head,實現一個調整連結串列的函式,使得每K個節點之間逆序,如果最後不足K個,則不調整
  • 判斷一個連結串列是否為迴文結構
  • 判斷一個單連結串列是否有環,如有則返回入環節點。時間複雜度O(N),額外空間複雜度O(1)
  • 兩個無環單連結串列是否相交,時間複雜度O(N+M),額外空間複雜度O(1)
  • 判斷兩個有環單連結串列是否相交,時間複雜度O(N+M),額外空間複雜度O(1)
  • 判斷兩個連結串列是否相交,並返回第一個相交的節點

基本性質

  • 連結串列問題演算法難度不高,但考察程式碼的實現能力
  • 連結串列和陣列一樣,都是一種線性結構
  1. 陣列是實體地址上一段連續的儲存空間。可以通過下標直接獲取元素當內容超出容量時需要重新定義陣列。
  2. 連結串列空間不一定保持聯絡,為臨時分配的。只能從連結串列的頭部開始一個一個查詢增刪的效率高於陣列,因為不需要更改記憶體結構

連結串列的分類

  • 按連線方向分類
  1. 單連結串列每個節點只能通過next指標,指向下一個節點。
  2. 雙連結串列除了next指標之外,還有一個prev指標指向其上一個節點。
  • 按照有無迴圈分類
  1. 普通連結串列頭無prev,尾無next。

  2. 迴圈連結串列首尾相接的連結串列。最後一個節點的next指標指向其第一個節點對於雙連結串列,其第一個節點的prev指標指向最後一個節點。

    資料結構基礎  連結串列

連結串列問題程式碼實現的關鍵點

  • 連結串列調整函式的返回值,往往是節點型別

連結串列在調整過程中往往遇到改變頭部的情況,如果頭節點被改變則需要返回一個新頭部。

  • 在調整連結串列的過程中,先採用畫圖的方式理清邏輯

注意那些指標變化了,同時注意對前後節點的影響。

  • 邊界條件的處理

頭節點,尾節點,空節點的特殊處理。


連結串列插入和刪除的注意事項

  • 特殊處理連結串列為空或長度為1
  • 插入過程的調整

取得前後節點,將前節點的next指向新節點,新節點的next指向後節點。

資料結構基礎  連結串列
  • 刪除過程的調整

取得前後節點,將前一個節點的next指標指向後一個節點。

資料結構基礎  連結串列
  • 頭尾插入或刪除

在邏輯的設計上應該考慮空節點的情況


連結串列翻轉

  • 特殊處理連結串列為空或長度為1
  • 單連結串列的翻轉

已翻轉的頭節點head,下一個節點now

  1. 將now節點的next指向head
  2. 將now節點設定為已翻轉部分的新head

需要注意在執行1,2步驟之前需要一個變數來儲存原now節點的next節點。步驟2設定了新的head之後,將該節點作為新的now,繼續翻轉。

資料結構基礎  連結串列

向一個有序的環境連結串列中插入一個節點,並保持依舊有序。

要求時間複雜度O(N),額外空間複雜度O(1)。

  • 如果連結串列為空

讓新節點node自己成為環形連結串列,並返回node即可。

  • 如果連結串列不為空

令變數prve設為頭節點,current設為第二個節點,兩個節點同步移動。

  • 當有node<
    =prve &
    &
    node>
    =current
    ,則說明node應該插入二者之間

    資料結構基礎  連結串列
  • 若prve回到head但依舊沒有合適的位置插入說明node為最大值或最小值,插入head之前即可。需要區分為兩種情況下是否出現新的head,並返回。

資料結構基礎  連結串列

對於一個單連結串列,在不給定head的情況下刪除指定node。要求時間複雜度O(1)

  • 如果node.next不為空,也就是node不是尾節點

如果工程允許,可以將node.next的內容copy到node節點上,變相的刪除了node節點的資料。

  • 如果node是尾節點

無法刪除


給定一個連結串列,與一個陣列num。要求實現荷蘭國旗

  • 將連結串列遍歷成陣列,然後進行荷蘭國旗排序,最後還原成連結串列。
  • 遍歷連結串列的過程中使用三個小連結串列。小於,等於,大於。最後將三個連結串列串聯。

給定兩個有序連結串列的head,列印共同部分

  • 有一個為空直接返回
  • 採用外排的方式,直到有一個為空則停止。

給定一個單連結串列的head,實現一個調整連結串列的函式,使得每K個節點之間逆序,如果最後不足K個,則不調整。

資料結構基礎  連結串列
  1. 連結串列為空,長度<
    k或者k<
    2直接返回
  • 通過棧結構,實現逆序
資料結構基礎  連結串列
  1. 需要保留上次逆序的最後一位元素,修改其next。
  2. 最後段不足k個,直接不修改。值將上次逆序的最後一個元素next設定好。
  3. 第一組的第一個節點為頭節點。
  • 不使用棧結構,手動逆序

判斷一個連結串列是否為迴文結構

資料結構基礎  連結串列
  1. 將連結串列節點依次入棧,在彈出時與原連結串列依次比對。

  2. 使用快行指標,通過二倍速的方式遍歷。依次將慢指標的節點壓入棧中,當快節點遍歷到末尾時,慢指標正好處於中間位置。繼續移動慢指標,並與棧中彈出的元素做對比。(需要注意總量的奇偶)

    資料結構基礎  連結串列
  3. 將後半部分連結串列進行逆序處理,從兩端同時進行遍歷比對

    資料結構基礎  連結串列

判斷一個單連結串列是否有環,如有則返回入環節點。時間複雜度O(N),額外空間複雜度O(1)

  • 1. 如果連結串列有結尾,則說明無環
  • 2.1 如果不要求額外空間複雜度,可以直接用雜湊表比對。
  • 2.2 使用快行指標的方式

如果兩指標相遇則表示有環,此時將快指標改為1,並從head重新同步移動,相遇處即為入環位置或者還有另一個證明

  • 程式碼
/// 獲取入環節點////// - Parameter node: 頭節點/// - Returns: 有環則返回入環節點,否則返回空func getLoopNode(head : Node) ->
Node {
//連結串列長度為 0,1,2 不可能成環 if head==nil || head.next==nil || head.next.next==nil {
return nil
} var slowP = head //慢行指標 var fastP = head //快行指標 while slowP != fastP {
if slowP.next==nil || fastP.next.next==nil {
//連結串列有結尾,不可能成環 return nil
} slowP = slowP.next fastP = fastP.next.next
}//執行到這裡說明兩指標相遇了 //從head開始遍歷再次相交則為入環點 fastP = head while fastP != slowP {
slowP = slowP.next fastP = fastP.next
} return fastP
}複製程式碼

兩個無環單連結串列是否相交,時間複雜度O(N+M),額外空間複雜度O(1)

  • 1. 先遍歷兩個連結串列確定長度
  • 2. 若兩個連結串列結尾不同,則不相交
  • 3. 另長連結串列從短連結串列開始位置與短連結串列再次同步遍歷,檢視是否相同。
資料結構基礎  連結串列
  • 程式碼
/// 兩個無環單連結串列是否相交////// - Parameters:///   - head1: 連結串列1///   - head2: 連結串列2/// - Returns: 相交點或者為空func noLoop(head1:Node ,head2:Node) ->
Node {
if head1==nil || head2==nil {
return nil
} var p1 = head1 var p2 = head2 //獲取兩個連結串列長度差值 var n = 0 while p1.next != nil {
p1 = p1.next n+=1
} while p2.next != nil {
p2 = p2.next n-=1
} if p1 != p2 {
//若兩個連結串列結尾不同,則一定不相交 return nil
} p1 = n>
=0 ? head1:head2 //使 p1 指向較長的連結串列 p2 = p2==head1 ? head2:head1 //使p2 指向另一個連結串列 n = abs(n) //取絕對值 while n>
0 {//將長連結串列移動n次。 p1 = p1.next n-=1
} //查詢連結串列上第一個相同的點 while p1 != p2 {
p1 = p1.next p2 = p2.next
} return p1
}複製程式碼

判斷兩個有環單連結串列是否相交,時間複雜度O(N+M),額外空間複雜度O(1)

首先都需要先去定單獨的入環節點,然後

  1. 是否入環之前已經相交

    資料結構基礎  連結串列
  2. 是否入環時才相交,則入環位置節點相同

    資料結構基礎  連結串列

    如果相交點為loop1或者loop2,則為入環時才相交

  3. 入環後才相交迴圈其中一個環,若遇到另一個的入環節點則返回。

    資料結構基礎  連結串列
  4. 否則,兩連結串列並未相交

  • 程式碼
/// 兩個有環單連結串列是否相交////// - Parameters:///   - head1: 連結串列1///   - head2: 連結串列2///   - loop1: 連結串列1入環點///   - loop2: 連結串列2入環點/// - Returns: 相交點或者為空func bothLoop(head1:Node ,head2:Node ,loop1:Node ,loop2:Node) ->
Node {
if head1==nil || head2==nil{
return nil
} if loop1 == loop2 {
//兩個連結串列在入環之前已經相交 var p1 = head1 var p2 = head2 //獲取兩個連結串列長度差值 var n = 0 while p1.next != loop1 {
p1 = p1.next n+=1
} while p2.next != loop2 {
p2 = p2.next n-=1
} p1 = n>
=0 ? head1:head2 //使 p1 指向較長的連結串列 p2 = p2==head1 ? head2:head1 //使p2 指向另一個連結串列 n = abs(n) //取絕對值 while n>
0 {//將長連結串列移動n次。 p1 = p1.next n-=1
} //查詢連結串列上第一個相同的點 while p1 != p2 {
p1 = p1.next p2 = p2.next
} return p1
}else {
//兩個連結串列在入環之後才相交 var p1 = loop1.next var p2 = loop2 while p1 == loop1 {
//迴圈loop1一次 p1 = p1.next if p1 == p2 {
return p1
}
} return nil //並未相交
}
}複製程式碼

判斷兩個連結串列是否相交,並返回第一個相交的節點

  1. 嘗試找到各自的入環節點

  2. 若一個有環一個無環,則不相交

  3. 若都為無環,則按照上文《兩個無環單連結串列是否相交》的方式查詢

  4. 若都為有環,則按照上文《判斷兩個有環單連結串列是否相交》的方式查詢

  • 程式碼
func getIntersectNode(head1:Node,head2:Node) ->
Node {
if head1 == nil || head2 == nil {
return nil
} //獲取兩個入環節點 var loop1 = getLoopNode(head: head1) var loop2 = getLoopNode(head: head2) if (loop1 == nil &
&
loop2 != nil)||(loop1 != nil &
&
loop2==nil) {
return nil //一個有環一個無環,肯定不相交
} if loop1==nil || loop2==nil {//兩個連結串列都不為環型結構 return noLoop(head1: head1, head2: head2)
}else {
//兩個環形連結串列 return bothLoop(head1: head1, head2: head2, loop1: loop1, loop2: loop2)
}
}複製程式碼

參考資料

左神牛課網演算法課

來源:https://juejin.im/post/5c08fb3ff265da61483b6814

相關文章