鏈表是一種遞歸的數據結構,是一種線性結構,可是並不會按線性的順序存儲數據,而是在每個節點裏存到下一個節點的指針(Pointer),簡單來講鏈表並不像數組那樣將數組存儲在一個連續的內存地址空間裏,它們能夠不是連續的由於他們每一個節點保存着下一個節點的引用(地址)java
單鏈表(又稱單向鏈表)是鏈表中的一種,其特色是鏈表的連接方向是單向的,對鏈表的訪問要從頭部(head)開始,而後依次經過next指針讀取下一個節點。node
單鏈表的數據結構能夠分爲兩部分:數據域和指針域,數據域存儲數據,指針域指向下一個存儲節點的地址。注意: 單向鏈表只可向一個方向進行遍歷算法
//(Kotlin描述)
class LinkedNode(var value: Int) {
var next: LinkedNode? = null //指向下一個存儲節點的next指針
}
複製代碼
//(Java描述)
public class LinkedNode {
int value;
LinkedNode next; //指向下一個存儲節點的next指針
public LinkedNode(int value) {
this.value = value;
}
}
複製代碼
雙鏈表(又稱雙向鏈表),是鏈表中一種,與單鏈表不一樣的是它的每一個節點都有兩個指針,分別指向直接後繼節點和直接前驅節點;因此,從雙鏈表中的任意一個結點開始,均可以很方便地訪問它的前驅結點和後繼結點。設計模式
雙鏈表的數據結構能夠分爲三部分:prev指針域、數據域和next指針域,prev指針域指向上一個存儲節點的地址(也即指向直接前驅節點),數據域存儲數據,next指針域指向下一個存儲節點的地址(也即指向直接後繼節點)。注意: 單向鏈表可向兩個方向進行遍歷,分別爲正序和逆序遍歷數組
//(Kotlin描述)
class LinkedNode(var value: Int) {
var prev: LinkedNode? = null //指向上一個存儲節點的prev指針
var next: LinkedNode? = null //指向下一個存儲節點的next指針
}
複製代碼
//(Java描述)
public class LinkedNode {
int value;
LinkedNode prev; //指向上一個存儲節點的prev指針
LinkedNode next; //指向下一個存儲節點的next指針
public LinkedNode(int value) {
this.value = value;
}
}
複製代碼
單向循環鏈表,只是在單鏈表的基礎上,它的最後一個結點再也不爲null而是指向頭結點,造成一個環。而且在節點結構上和單鏈表是同樣的。所以,從單向循環鏈表中的任何一個結點出發都能找到任何其餘結點。數據結構
雙向循環鏈表,只是在雙鏈表的基礎,它的頭節點的prev指針再也不爲null,而是直接指向它的尾節點;它的尾節點的next指針再也不爲null,而是直接指向它的頭節點。app
咱們知道一個節點類型的變量就能夠表示一條鏈表,只要保證對應的每一個節點的next指針可以指向下一個節點便可或指向null(表示鏈表最後一個節點)函數
//鏈表結構定義
class LinkedNode(var value: Int) {
var next: LinkedNode? = null
}
//鏈表的構造
fun main(args: Array<String>) {
val node1 = LinkedNode(value = 1)//建立節點1
val node2 = LinkedNode(value = 2)//建立節點2
val node3 = LinkedNode(value = 3)//建立節點3
node1.next = node2//經過node1的next指針指向node2,把node1和node2鏈接起來
node2.next = node3//經過node2的next指針指向node3,把node2和node3鏈接起來
}
複製代碼
class LinkedNode(var value: Int) {
var prev: LinkedNode? = null
var next: LinkedNode? = null
}
fun main(args: Array<String>) {
val node1 = LinkedNode(value = 1)//建立節點1 此時的prev,next均爲null
val node2 = LinkedNode(value = 2)//建立節點2 此時的prev,next均爲null
val node3 = LinkedNode(value = 3)//建立節點3 此時的prev,next均爲null
node1.next = node2 //node1的next指針指向直接後繼節點node2
node2.prev = node1 //node2的prev指針指向直接前驅節點node1
node2.next = node3 //node2的next指針指向直接後繼節點node3
node3.prev = node2 //node3的prev指針指向直接前驅節點node2
}
複製代碼
在鏈表表頭插入一個節點是最簡單的一種操做,通常處理方式,先建立一個oldFirst指向第一個節點,而後從新建立一個新的節點,將新節點的next指向oldFirst指向的節點,first指向新插入的節點。post
fun insertToHead(head: LinkedNode): LinkedNode {
var first: LinkedNode = head
val oldFirst: LinkedNode = head
first = LinkedNode(value = 6)
first.next = oldFirst
return first
}
複製代碼
fun insertToHead(head: LinkedNode): LinkedNode {
var first: LinkedNode = head
val oldFirst: LinkedNode = head
first = LinkedNode(value = 6)
oldFirst.prev = first
first.next = oldFirst
return first
}
複製代碼
fun deleteToHead(head: LinkedNode): LinkedNode? {
var first: LinkedNode? = head
first = first?.next
return first
}
複製代碼
fun deleteToHead(head: LinkedNode): LinkedNode? {
var first: LinkedNode? = head
first = first?.next
first?.prev = null
return first
}
複製代碼
fun insertToTail(head: LinkedNode): LinkedNode? {
var last = getTailNode(head) //經過遍歷獲得尾部節點
val oldLast = last
last = LinkedNode(value = 4)
oldLast?.next = last
return head
}
複製代碼
fun insertToTail(head: LinkedNode): LinkedNode? {
var last = getTailNode(head) //經過遍歷獲得尾部節點
val oldLast = last
last = LinkedNode(value = 4)
oldLast?.next = last
last.prev = oldLast
return head
}
複製代碼
fun insertToOther(head: LinkedNode): LinkedNode? {
val current = getInsertPrevNode(head) //拿到須要的插入位置的上一個節點
val newNode = LinkedNode(value = 6)
newNode.next = current?.next// 新插入的節點next指向插入位置的上一個節點的next
current?.next = newNode//而後斷開插入位置的上一個節點的next,並把指向新插入的節點
return head
}
複製代碼
fun insertToOther(head: LinkedNode): LinkedNode? {
val current = getInsertPrevNode(head) //拿到須要的插入位置的上一個節點
val newNode = LinkedNode(value = 6)
newNode.next = current?.next// 新插入的節點next指向插入位置的上一個節點的next
newNode.prev = current //新插入的節點prev指向插入位置的上一個節點
current?.next = newNode//而後斷開插入位置的上一個節點的next,並把它指向新插入的節點
current?.next?.prev = newNode //而後斷開插入位置的上一個節點的prev,並把它指向新插入的節點
return head
}
複製代碼
fun deleteToOther(head: LinkedNode): LinkedNode? {
val current = getInsertPrevNode(head) //拿到須要的刪除節點的上一個節點
current?.next = current?.next?.next
return head
}
複製代碼
fun deleteToOther(head: LinkedNode): LinkedNode? {
val current = getDeletePrevNode(head) //拿到須要的刪除節點的上一個節點
current?.next = current?.next?.next
current?.next?.prev = current
return head
}
複製代碼
fun traverseLinkedList(head: LinkedNode?) {
var current = head
while (current != null){
println(current.value)
current = current.next
}
}
複製代碼
fun getLength(head: LinkedNode?): Int {
var len = 0
var current = head
while (current != null){
len++
current = current.next
}
return len
}
複製代碼
因爲棧是一個表,所以任何實現表的方法都能實現棧。顯然,Java中經常使用的ArrayList和LinkedList集合都是支持棧操做的。性能
單鏈表也是能實現棧的,經過在表的頂端插入實現棧的push壓棧操做,經過刪除表的頂端元素實現pop入棧操做。top操做只須要返回頂部的元素的值便可。
class LinkedStack {
private var first: Node? = null
private var len: Int = 0
fun push(value: Int) {//至關於鏈表從表頭插入新的元素
val oldFirst = first
first = Node(value)
first?.next = oldFirst
len++
}
fun pop(): Int {//至關於鏈表從表頭刪除新的元素
val value = first?.value
first = first?.next
return value ?: -1
}
fun top(): Int {
return first?.value ?: -1
}
fun isEmpty(): Boolean {
return first == null
}
fun size(): Int {
return len
}
inner class Node(var value: Int) {
var next: Node? = null
}
}
複製代碼
class LinkedQueue {
private var first: Node? = null
private var last: Node? = null
private var len: Int = 0
fun enqueue(value: Int) {//至關於鏈表從尾部插入新的節點
val oldLast = last
last = Node(value)
last?.next = null
if (isEmpty()) {
first = last
} else {
oldLast?.next = last
}
len++
}
fun dequeue(): Int {//至關於鏈表從尾部刪除最後節點
val value = first?.value ?: -1
first = first?.next
if (isEmpty()) {
last = null
}
return value
}
fun isEmpty(): Boolean {
return first == null
}
fun size(): Int {
return len
}
inner class Node(var value: Int) {
var next: Node? = null
}
}
複製代碼
鏈表反轉(也稱鏈表的逆序)是鏈表中一種比較經典的操做,在一些數據結構的題目鏈表的反轉也是常考點,鏈表的反轉也會作爲一部分融入題目,好比迴文鏈表問題等
二、實現過程
三、代碼描述
fun reverseLinkedList(head: LinkedNode?): LinkedNode? {
var prev: LinkedNode? = null
var current: LinkedNode? = head
var next: LinkedNode? = head
while (current != null) {
next = current.next
current.next = prev
prev = current
current = next
}
return prev
}
複製代碼
快慢指針追趕問題在鏈表中是很是經典的,快慢指針問題通常用於解決鏈表中間節點問題和鏈表是否含有環以及鏈表中環的入口位置等問題。
若是使用快慢指針是判斷鏈表是否含有環的問題,咱們更但願fast和slow指針的相對路程是正好是環的長度,(也就是slow指針剛進入環,而fast指針剛繞環一圈,此時兩指針正好相遇)這樣兩個指針就相遇了。這樣取每步的速度差可以被環長度整除的數字。可是咱們並不知道環的具體長度,因此只能取每步的速度差可以被環長度整除的數字爲1(1能被全部的數整除),因此咱們取fast指針每次走2步,slow指針每次走1步,實際上只要保證二者速度差爲1就能夠了,你甚至能夠fast每次走3步,slow指針每次走2步都是能夠的,這樣一來只要它們在環裏面就必定能相遇。
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null) return false;
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;//慢指針每次走1步
fast = fast.next.next;//快指針每次走2步
if(slow == fast){//若是鏈表存在環,那麼slow和fast指針會相遇
return true;
}
}
return false;
}
複製代碼
由快慢指針追趕的原理可知,若是fast指針和slow指針同時從鏈表(鏈表不含環)的頭結點出發開始遍歷,若是fast指針的每次遍歷步數是slow指針的兩倍,那麼可獲得若是fast遍歷到鏈表的尾部,那麼此時的slow指針應該處於鏈表的中間節點位置(具體題目可參考:LeetCode第876題)。
public ListNode middleNode(ListNode head) {
if(head == null) return null;
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
複製代碼
一、刪除鏈表的節點
二、反轉鏈表
三、鏈表的中間節點
四、合併兩個有序鏈表
五、刪除排序鏈表中的重複元素
六、移除鏈表中的元素
七、相交鏈表
八、環形鏈表
九、迴文鏈表
十、設計鏈表
歡迎關注Kotlin開發者聯盟,這裏有最新Kotlin技術文章,每週會不按期翻譯一篇Kotlin國外技術文章。若是你也喜歡Kotlin,歡迎加入咱們~~~
Kotlin邂逅設計模式系列:
數據結構與算法系列:
翻譯系列:
原創系列:
Effective Kotlin翻譯系列
實戰系列: