美文网首页
重拾数据结构101 - 链表 LinkedList (Swift

重拾数据结构101 - 链表 LinkedList (Swift

作者: Hesse_Huang | 来源:发表于2020-06-25 01:12 被阅读0次

    概念

    链表 LinkedList 与 数组 ArrayList 一样是「表」的一种。与数组会占用一块连续内存不同的是,链表的内存占用不一定是连续的。链表由一系列节点 nodes 组成,每一个节点均含有表元素和到包含该元素后继元的节点的链,即「next 链」,这是单向链表。如果还有对前驱元的节点的引用,则为双向链表。
    下面是我自己实现的双向链表。这里仿照 Swift 对 Array 的实现,给出一些常用的方法,并考虑泛型和合理的封装。

    完整代码

    节点 ListNode

    节点的结构比较简单。 element 为节点携带的元素/值,next指向后继节点, prev指向前驱节点。由于 ListNode 类的主要功能是承载链表的元素 Element 和维护对前驱、后继节点的引用,并不需要向外界暴露,因此加 private

        private class ListNode<Element> {
            /// The associated value with the node.
            var element: Element
            /// Next node in the list.
            var next: ListNode<Element>?
            /// Previous node in the list.
            weak var prev: ListNode<Element>?
            
            init(_ element: Element) {
                self.element = element
            }
        }
    

    链表 LinkList

    这里实现的是双链表,因此要维护头节点head和尾节点tail,并保存当前链表的节点总数 count。同时我们把上面 ListNode类声明在此类里。

    class LinkedList<Element> { 
        var first: Element? {
            head?.element
        }
        var last: Element? {
            tail?.element
        }
        var isEmpty: Bool {
            head == nil
        }
    
        private(set) 
        var count: Int = 0
        
        private class ListNode<Element> {  /* ... */ }
        private var head: ListNode<Element>?
        private var tail: ListNode<Element>?
    }
    

    常用方法

    • 随机索引 getter/setter:这里使用 Swift 的 subscript 语法来实现。 注意的是,链表的随机索引时间复杂度为 O(n),而不像数组的 O(1)。
        subscript(index: Int) -> Element? {
            get {
                self[nodeAt: index]?.element
            }
            set {
                if let newValue = newValue {
                    self[nodeAt: index] = ListNode(newValue)
                } else {
                    self[nodeAt: index] = nil
                }
            }
        }
        
        private subscript(nodeAt index: Int) -> ListNode<Element>? {
            get {
                assert(index >= 0, "Index should be not less than 0.")
                if index == 0 {
                    return head
                } else if index == count - 1 {
                    return tail
                } else {
                    if index <= count / 2 {
                        // Find from head.
                        var i: Int = 1
                        var curr: ListNode<Element>? = head
                        while curr?.next != nil, i < index {
                            curr = curr?.next
                            i += 1
                        }
                        return curr?.next
                    } else {
                        // Find from tail.
                        var i: Int = count - 2
                        var curr: ListNode<Element>? = tail
                        while curr?.prev != nil, index < i {
                            curr = curr?.prev
                            i -= 1
                        }
                        return curr?.prev
                    }
                }
            }
            set {
                assert(index >= 0, "Index should be not less than 0.")
                if let newValue = newValue {
                    if index == 0 {
                        // Replace the head.
                        newValue.next = head?.next
                        head?.next?.prev = newValue
                        head?.next = nil
                        head = newValue
                    } else if index == count - 1 {
                        // Replace the tail.
                        newValue.prev = tail?.prev
                        tail?.prev?.next = newValue
                        tail?.prev = nil
                        tail = newValue
                    } else if let curr = self[nodeAt: index] {
                        newValue.next = curr.next
                        newValue.prev = curr.prev
                        curr.next?.prev = newValue
                        curr.prev?.next = newValue
                        curr.next = nil
                        curr.prev = nil
                    }
                } else {
                    remove(at: index)
                }
            }
        }
    
    • 添加 append:在链表尾部添加一个新节点。时间复杂度为 O(1)。
        func append(_ newElement: Element) {
            append(node: ListNode(newElement))
        }
        
        private func append(node: ListNode<Element>) {
            if isEmpty {
                head = node
                tail = node
            } else {
                tail?.next = node
                node.prev = tail
                tail = node
            }
            count += 1
        }
    
    • 插入 insert: 在指定位置插入一个新的节点。因为涉及在随机位置上的索引,因此时间复杂度最好情况为 O(1),即对头尾两端的操作;一般情况为 O(n)。
        func insert(_ newElement: Element, at index: Int) {
            insert(node: ListNode(newElement), at: index)
        }
    
        private func insert(node: ListNode<Element>, at index: Int) {
            if let curr = self[nodeAt: index] {
                node.next = curr
                node.prev = curr.prev
                curr.prev = node
                node.prev?.next = node
                if index == 0 {
                    head = node
                }
                count += 1
            } else if index == count {
                append(node: node)
            }
        }
    
    • 删除 remove:移除指定位置上的节点。同样因为涉及在随机位置上的索引,因此时间复杂度最好情况为 O(1),最差情况为 O(n)。
        func remove(at index: Int) -> Element? {
            return remove(nodeAt: index)?.element
        }
        
        private func remove(nodeAt index: Int) -> ListNode<Element>? {
            var result: ListNode<Element>?
            if let curr = self[nodeAt: index] {
                if index == 0 {
                    head = head?.next
                } else if index == count - 1 {
                    tail = tail?.prev
                }
                curr.prev?.next = curr.next
                curr.next?.prev = curr.prev
                curr.prev = nil
                curr.next = nil
                result = curr
                count -= 1
            }
            return result
        }
    
    • 包含 contains:判定是否包含某个元素值或引用。这要求 Element 本身是 Equatable。
    extension LinkedList where Element: Equatable {
        func contains(_ element: Element) -> Bool {
            var curr = head
            while curr != nil {
                if curr?.element == element {
                    return true
                }
                curr = curr?.next
            }
            return false
        }
    }
    

    理解

    current.next: 当前节点的后继指向(setter) / 当前节点的后继节点(getter)
    current.next.next:后继节点的指向(setter) / 后继节点的下一节点(getter)

    技巧

    有关链表的解题技巧,个人小结如下:

    1. 双/三指针:使用迭代步速相同的两个或多个指针进行比对,如反转链表判断链表是否相交回文链表分隔链表等;
    2. 快慢指针:慢指针一次走一步,快指针一次走两步。这种方法可以用于找到链表的中点、找出某个相交的节点等,如回文链表环路检测等;
    3. 使用哑节点(dummy head & dummy tail):可简化对链表结构上有改动的操作(插入、删除);
    4. 使用递归:当前节点的操作结果依赖于后继节点的操作结果,即往往递归函数的入参是head.next,如排序链表反转链表等。

    相关文章

      网友评论

          本文标题:重拾数据结构101 - 链表 LinkedList (Swift

          本文链接:https://www.haomeiwen.com/subject/otthfktx.html