初識鏈表 By Swift

Githubnode

初識鏈表

鏈表:鏈式結構,使用節點來存儲內容,經過當前節點的 next 指針指向下一個節點。能夠經過分散的內存來存儲節點,所以內存利用率更高,佔用內存空間更小。但由於只有經過 next 指針才能找到下一個節點,因此每次查找節點都須要從頭結點開始。git

由於 next 指針的關係,它的查找效率和修改的效率不如數組,但添加和刪除的效率要高於數組。github

結構圖:

          (Node)           (Node)           (Node)
        -----------      -----------      -----------
        |         |      |         |      |         |
head--> |  value  | ---> |  value  | ---> |  value  | --> nil
        |         |      |         |      |         |
        -----------      -----------      -----------
複製代碼

定義一個鏈表

class Node {
    var val: Int
    var next: Node?
    
    init(val: Int, next: Node?) {
        self.val = val
        self.next = next
    }
}

class LinkList {
    var dummy: Node?
    var size: Int
    
    init() {
        dummy = Node(val: 0, next: nil)
        size = 0
    }

    private func node(_ index: Int) -> Node? {
        var curNode = dummy?.next
        var curIndex = index
        
        while curIndex > 0 {
            curNode = curNode?.next
            curIndex -= 1
        }
        return curNode
    }
}
複製代碼

首先聲明一個包含 val 和 next 的 Node 類。val 用來存儲值,而 next 用來指向下一個節點。數組

而後再聲明一個名 LinkList 類來表明鏈表。dummy 的 next 用來指向頭結點,size 用來存儲鏈表的當前長度。markdown

這裏須要說明的是,爲何要設置 dummy 節點。由於這樣作是能夠避免在添加第一個節點時的不少邏輯判斷。app

最後,建立一個 node(_:) 的私有函數,用來獲取當前 index 的節點。函數

添加

public func append(val: Int) {
    let newNode = Node(val: val, next: nil)
    
    if size == 0 {
        dummy?.next = newNode
    } else {
        var curSize = size
        var curNode = dummy
        
        while curSize > 0 {
            curNode = curNode?.next
            curSize -= 1
        }
        curNode?.next = newNode
    }
    
    size += 1
}
複製代碼

在尾部添加節點有如下兩種狀況:oop

  • condition 1:size == 0,說明沒有鏈表未保存節點。
  • condition 2:size > 0,鏈表已經保存節點,只須要找到尾結點便可。

在 condition 1 的狀況下,將 dummy 的 next 指向 newNode 便可,而在 condition 2 的狀況下,尋找到尾結點,將尾結點的 next 指向 newNode。spa

最後,不要忘記將 size + 1。指針

刪除

public func remove(_ index: Int) -> Int? {
    guard index > -1 && index < size else { return nil }
    let val: Int
    if index == 0 {
        val = dummy?.next?.val ?? 0
        dummy?.next = dummy?.next?.next
    } else {
        let prev = node(index - 1)
        val = prev?.next?.val ?? 0
        prev?.next = prev?.next?.next
    }
    size -= 1
    return val
}
複製代碼

進行移除操做時,須要對 index 的合法性進行校驗。

移除操做同添加操做也是有如下兩種狀況:

  • condition 1:移除頭結點。
  • condition 2:移除其它節點。

移除操做的核心就是找到須要移除節點的前一個節點 prev(經過 node(_:) 能夠得到),將 prev 的 next 指針指向它 next 的 next 便可。如:prev?.next = prev?.next?.next

最後不要忘記 size - 1 。

插入

public func insert(_ val: Int, atIndex index: Int) {
    guard index > -1 && index <= size else { return }
    let newNode = Node(val: val, next: nil)
    
    if index == 0 {
        newNode.next = dummy?.next
        dummy?.next = newNode
    } else {
        let pre = node(index - 1)
        newNode.next = pre?.next
        pre?.next = newNode
    }
    
    size += 1
}
複製代碼

插入操做相對來講是比較複雜的一個操做,要考慮好 index == 0,index == size 等條件的狀況。

插入操做的核心操做分兩步:

  • 將新建節點 newNode 的 next 指向當前 index 的節點。
  • 將當前節點的 prev 節點的 next 指向新建節點 newNode。

以上兩步的順序很重要,切記不要搞錯。

經過上面的步驟來分析插入操做的代碼。當 index == 0 時,說明要在頭結點的位置進行插入,首先進行第一步:將 newNode 的 next 指向當前 index 的節點 - newNode.next = dummy?.next;接着進行第二步:將當前節點的 prev 節點的 next 指向新建節點 newNode - dummy?.next = newNode

當 index > 0 時同上。最後不要忘記 size + 1 。

查詢

public func get(_ index: Int) -> Int? {
    guard index > -1 && index < size - 1 else { return nil }
    return node(index)?.val
}
複製代碼

查詢操做是最簡單的一個。只須要判斷下 index 的有效性,而後經過 node(_:) 獲取相應的節點便可。

以上,就是鏈表基本操做的實現。

擴展

支持泛型

上面的示例代碼只支持存儲 Int 類型的數據,而在項目中可能須要支持多種類型,因此將其改爲支持泛型仍是頗有必要的。

class Node<Element> {
    var val: Element?
    var next: Node?
    
    init(val: Element?, next: Node?) {
        self.val = val
        self.next = next
    }
}

class LinkList<Element> {
    var dummy: Node<Element>?
    var size: Int
    
    public init() {
        dummy = Node(val: nil, next: nil)
        size = 0
    }
    
    public func append(val: Element) {
        let newNode = Node(val: val, next: nil)
        
        if size == 0 {
            dummy?.next = newNode
        } else {
            // 查找尾結點
            var curSize = size
            var curNode = dummy
            
            while curSize > 0 {
                curNode = curNode?.next
                curSize -= 1
            }
            curNode?.next = newNode
        }
        
        size += 1
    }
    
    public func remove(_ index: Int) -> Element? {
        guard index > -1 && index < size else { return nil }
        let val: Element?
        if index == 0 {
            val = dummy?.next?.val
            dummy?.next = dummy?.next?.next
        } else {
            let prev = node(index - 1)
            val = prev?.next?.val
            prev?.next = prev?.next?.next
        }
        size -= 1
        return val
    }
    
    public func get(_ index: Int) -> Element? {
        guard index > -1 && index < size - 1 else { return nil }
        return node(index)?.val
    }
    
    public func insert(_ val: Element, atIndex index: Int) {
        guard index > -1 && index <= size else { return }
        let newNode = Node(val: val, next: nil)
        
        if index == 0 {
            newNode.next = dummy?.next
            dummy?.next = newNode
        } else {
            let pre = node(index - 1)
            newNode.next = pre?.next
            pre?.next = newNode
        }
        
        size += 1
    }
    
    private func node(_ index: Int) -> Node<Element>? {
        var curNode = dummy?.next
        var curIndex = index
        
        while curIndex > 0 {
            curNode = curNode?.next
            curIndex -= 1
        }
        return curNode
    }
}

複製代碼

支持索引

經過重寫 subscript 也能夠支持索引操做。

extension LinkList {
    public subscript(index: Int) -> Element? {
        return node(index)?.val
    }
}
複製代碼
相關文章
相關標籤/搜索