Swift 链表

作者: 半心_忬 | 来源:发表于2019-12-19 20:44 被阅读0次

一、概念

什么是链表:

    1. 链表和数组类似,也是一种线性表结构
    1. 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
    1. 链表由一系列节点组成,每个节点一般至少会包含两部分信息:一部分是元素数据本身,另一部分是指向下一个元素地址的指针

链表的特点:

    1. 链表克服了数组需要提前设置长度的缺点,在运行时可以根据需要随意添加元素
    1. 计算机的存储空间并不总是连续可用的,而链表可以灵活地使用存储空间,还能更好地对内存进行动态管理
    1. 插入、删除数据效率高,只需要考虑相邻结点的指针改变,不需要搬移数据,时间复杂度是 O(1)
    1. 随机查找效率低,需要根据指针一个结点一个结点的遍历查找,时间复杂度为O(n)
    1. 与内存相比,链表的空间消耗大,因为每个结点除了要存储数据本身,还要储存上(下)结点的地址

链表的类型:

链表分为两种类型:单向链表和双向链表。我们平时说道的链表指的是单向链表。当然,除了这些普通的链表,链表由于其特点还衍生了很多特殊的情况,例如单向循环链表、双向循环链表。

Swift原生是没有链表的,本文使用Swift分析和设计单向链表和双向链表。

二、Swift链表的设计

由于链表和数组类似,所以设计时考虑如下几点:

    1. 使用与数组类似的API,方便调用
    1. 使用泛型,支持更多的数据类型,由于需要做是否等于的比较,所有泛型需遵循Equatable协议
    1. 链表调试不易,所以使用了一个description属性快速调试用

定义一个链表protocal,单向和双向都遵循该协议:

/// 链表协议
protocol LinkedListProtocol {
    /// 关联类型
    associatedtype E: Equatable
    
    /// 描述(快速debug用)
    var description: String { get }
    /// 清除所有元素
    func removeAll()
    /// 元素数量
    func count() -> Int
    /// 是否为空
    func isEmpty() -> Bool
    /// 是否包含某个元素
    /// - Parameter element: 某个元素
    func contains(_ element: E?) -> Bool
    /// 添加元素到最后的位置
    /// - Parameter element: 元素
    func append(_ element: E?)
    /// 添加某个元素到指定位置
    /// - Parameters:
    ///   - index:   指定位置
    ///   - element: 元素
    func insert(_ index: Int, element: E?)
    /// 获取指定位置的元素
    /// - Parameter index: 指定位置
    func get(_ index: Int) -> E?
    /// 设置指定位置的元素
    /// - Parameters:
    ///   - index:   指定位置
    ///   - element: 指定位置上原来的元素
    func set(_ index: Int, element: E?) -> E?
    /// 移除指定位置的元素
    /// - Parameter index: 指定位置
    func remove(_ index: Int) -> E?
    /// 获取某个元素的索引
    /// - Parameter element: 索引
    func indexOf(_ element: E?) -> Int?
}

三、单向链表

单向链表的结构如下图:

单向链表.png

具体实现如下:

/// 单向链表
class SingleLinkedList<E> where E: Equatable {
    /// 元素数量
    private var size: Int = 0
    /// 头节点
    private var first: Node<E>?
    
    /// 内部节点
    private class Node<E> {
        /// 元素
        var element: E?
        /// 下一个元素
        var next: Node<E>?
        
        init(element: E?, next: Node<E>?) {
            self.element = element
            self.next    = next
        }
    }
}

// MARK: - LinkedListProtocol
extension SingleLinkedList: LinkedListProtocol {
    typealias E = E
    
    var description: String {
        var desc = "size -> \(size), ["
        var tempNode = first
        
        (0..<size).forEach {
            if $0 != 0 { desc += ", " }
            
            desc += "\(String(describing: tempNode?.element ?? nil))"
            
            tempNode = tempNode?.next
        }
        
        desc += "]"
        
        return desc
    }
    
    func count() -> Int {
        size
    }
    
    func isEmpty() -> Bool {
        size == 0
    }
    
    func contains(_ element: E?) -> Bool {
        if let _ = indexOf(element) {
            return true
        }
        return false
    }
    
    func append(_ element: E?) {
        insert(size, element: element)
    }
    
    func insert(_ index: Int, element: E?) {
        LinkedListUtil.indexValidCheckForAdd(index, size: size)
        
        // index 为 0 时需特殊处理
        if index == 0 {
            first = Node(element: element, next: first)
        } else {
            let prevNode = node(index - 1)
            prevNode?.next = Node(element: element, next: prevNode?.next)
        }
        
        size += 1
    }
    
    func get(_ index: Int) -> E? {
        node(index)?.element
    }
    
    func set(_ index: Int, element: E?) -> E? {
        let originNode = node(index)
        let originElement = originNode?.element
        originNode?.element = element
        return originElement
    }
    
    func indexOf(_ element: E?) -> Int? {
        var tempNode = first
        
        for i in 0..<size {
            if element == tempNode?.element { return i }
            
            tempNode = tempNode?.next
        }
        
        return nil
    }
    
    func remove(_ index: Int) -> E? {
        LinkedListUtil.indexValidCheck(index, size: size)
        
        var tempNode = first
        
        if index == 0 {
            first = tempNode?.next
        } else {
            let prevNode = node(index - 1)
            
            tempNode = prevNode?.next
            prevNode?.next = tempNode?.next
        }
        
        size -= 1
        
        return tempNode?.element
    }
    
    func removeAll() {
        size  = 0
        first = nil
    }
}

// MARK: - private method
private extension SingleLinkedList {
    /// 获取 index 的 node
    /// - Parameter index: index
    private func node(_ index: Int) -> Node<E>? {
        LinkedListUtil.indexValidCheck(index, size: size)
        
        var node = first
        
        (0..<index).forEach { _ in
            node = node?.next
        }
        
        return node
    }
}

注意点:不论添加还是删除,首尾节点的操作需要特殊处理

四、双向链表

双向链表的结构如下图:

双向链表.png

具体实现如下:

/// 双向链表
public class DoubleLinkedList<E> where E: Equatable {
    /// 元素数量
    private var size: Int = 0
    /// 首个元素
    private var first: Node<E>?
    /// 最后一个元素
    private var last: Node<E>?
    
    /// 内部节点
    private class Node<E> {
        /// 上一个元素
        weak var prev: Node<E>?
        /// 元素
        var element: E?
        /// 下一个元素
        var next: Node<E>?
        
        /// 简述(debug 用)
        var description: String {
            var desc = ""
            
            desc += "\(String(describing: prev?.element ?? nil))"
            desc += "_\(String(describing: element ?? nil))_"
            desc += "\(String(describing: next?.element ?? nil))"
            
            return desc
        }
        
        init(prev: Node<E>?, element: E?, next: Node<E>?) {
            self.prev    = prev
            self.element = element
            self.next    = next
        }
    }
}

// MARK: - LinkedListProtocol
extension DoubleLinkedList: LinkedListProtocol {
    typealias E = E
    
    var description: String {
        var desc = "size -> \(size), ["
        
        var tempNode = first

        (0..<size).forEach {
            if $0 != 0 { desc += ", " }

            desc += tempNode?.description ?? ""

            tempNode = tempNode?.next
        }

        desc += "]"

        return desc
    }
    
    func removeAll() {
        size  = 0
        first = nil
        last  = nil
    }
    
    func count() -> Int {
        size
    }
    
    func isEmpty() -> Bool {
        size == 0
    }
    
    func contains(_ element: E?) -> Bool {
        if let _ = indexOf(element) {
            return true
        }
        return false
    }
    
    func append(_ element: E?) {
        insert(size, element: element)
    }
    
    func insert(_ index: Int, element: E?) {
        LinkedListUtil.indexValidCheckForAdd(index, size: size)
        
        // 当往最后添加时
        if index == size {
            let oldLast = last
            last = Node(prev: oldLast, element: element, next: nil)
            
            if let _ = oldLast {
                oldLast?.next = last
            } else {
                first = last
            }
        } else { // 其他场景
            let next = node(index)
            let prev = next?.prev
            let current = Node(prev: prev, element: element, next: next)
            next?.prev = current
            
            // 当index 为 0时
            if let _ = prev {
                prev?.next = current
            } else {
                first = current
            }
        }
        
        size += 1
    }
    
    func get(_ index: Int) -> E? {
        node(index)?.element
    }
    
    func set(_ index: Int, element: E?) -> E? {
        let currentNode = node(index)
        let oldElemet = currentNode?.element
        currentNode?.element = element
        
        return oldElemet
    }
    
    func remove(_ index: Int) -> E? {
        LinkedListUtil.indexValidCheck(index, size: size)
        
        let current = node(index)
        let prev = current?.prev
        let next = current?.next
        
        // inde为0时
        if let _ = prev {
            prev?.next = next
        } else {
            first = next
        }
        
        // index为size - 1时
        if let _ = next {
            next?.prev = prev
        } else {
            last = prev
        }
        
        size -= 1
        return current?.element
    }
    
    func indexOf(_ element: E?) -> Int? {
        var tempNode = first
        for i in 0..<size {
            if element == tempNode?.element { return i }
            
            tempNode = tempNode?.next
        }
        
        return nil
    }
}

// MARK: - private method
private extension DoubleLinkedList {
    /// 获取 index 的 node
    /// - Parameter index: index
    private func node(_ index: Int) -> Node<E>? {
        LinkedListUtil.indexValidCheck(index, size: size)
        
        // 使用二分法提升效率
        if (index < (size >> 1)) {
            var node = first
            
            (0..<index).forEach { _ in
                node = node?.next
            }
            
            return node
        } else {
            var node = last
            
            (index..<size - 1).forEach { _ in
                node = node?.prev
            }
            
            return node
        }
    }
}

注意点:

  • 1.双向链表中节点的设计,需要将上一个或下一个节点设置为weak,来避免循环引用

    例如现在在一个链表中,节点B在节点A后面,这样A是指向B的。假如现在节点B也指向节点A,这就导致A和B互相强引用。在某些情况下,这种所有权循环(ownership cycle)会使得即使你删除它们,节点依然存活着(也就是所谓的内存泄露)。所以我们需要将其中一个指针设置为weak,用来打破这种循环。

  • 2.不论添加还是删除,首尾节点的操作需要特殊处理

五、链表的使用

经典的链表应用场景: LRU 缓存淘汰算法

缓存是一种提高数据读取性能的技术,在硬件设计、软件开发中都有着非常广泛的应用,比如常见的CPU缓存、数据库缓存、浏览器缓存等等。

缓存空间的大小有限,当缓存空间被用满时,哪些数据应该被清理出去,哪些数据应该被保留?这就需要缓存淘汰策略来决定。

常见的缓存清理策略有三种:
  • 先进先出策略FIFO(First In, First Out)
  • 最少使用策略 LFU(Least Frequently Used)
  • 最近最少使用策略LRU(Least Recently Used)
如何用链表来实现LRU缓存淘汰策略呢?

思路:维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时,我们从链表头部开始顺序遍历链表。

  1. 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据的对应结点,并将其从原来的位置删除,并插入到链表头部
  2. 如果此数据没在缓存链表中,又可以分为两种情况处理

如果此时缓存未满,可直接在链表头部插入新节点存储此数据,
如果此时缓存已满,则删除链表尾部节点,再在链表头部插入新节点。

YYCache中的缓存策略使用的淘汰算法即是,LRU缓存淘汰算法

设计该链表也是为了后续使用YYCache的思路写一个Swift版本的缓存库。

六、后记

源码地址

  • 思考:
  1. 单向链表提升性能和统一操作的方式,加一个虚拟节点怎么实现
  2. 单向循环列表的实现
  3. 双向循环列表的实现
  4. 双向循环链表提升性能,加一个当前节点怎么实现
  • 链表的一些练习题:
  1. 如何反转一个单向链表,可以有哪些方式实现

    思路:递归和循环的方式均可实现

  2. 判断一个链表中是否有环

    使用快慢指针即可

--

参考文档:

http://www.chinacion.cn/article/4419.html
https://blog.csdn.net/actionabll/article/details/100764473

相关文章

  • 无标题文章

    Swift算法俱乐部:Swift 链表数据结构@(Swift)在本教程中,我们将会在Swift 3中实现链表。##...

  • Swift 链表

    一、概念 什么是链表: 链表和数组类似,也是一种线性表结构 链表是一种物理存储单元上非连续、非顺序的存储结构,数据...

  • Swift - 链表

    概念 链表是由数据项组成的一个序列,其中每个数据项被称为节点。链表有两种主要类型: 单链表 每一个节点只包含一个指...

  • LRU算法—Swift代码实现

    Swift 需要用到哈希表和双向链表进行实现。哈希表可以快速查找,双向链表能够通过自身从链表中删除自身

  • 数据结构之 swift 实现链表反转

    链表反转很熟悉的面试题,关于链表的基础知识就不再累赘了,如何swift实现链表的反转。 传入链表的头结点 返回一个...

  • 工作日志

    8-15目标 science network 链表的所有swift 练习 数组,栈的所有的swift练习 完成 s...

  • swift 实现 LeetCode

    swift实现链表 swift实现队列功能 swift实现栈的功能 判断给定的一组数是否是回文结构。如:1、2、3...

  • iOS开发集锦之 2017.03.29(Swift 算法实战之路

    1. Swift 算法实战之路:链表 作者: 故胤道长描述:链表基本结构; Dummy节点;尾插法;快行指针 2....

  • 双向链表的快速排序(swift版本)

    面试经常会被问到的单向链表的快速排序or双向链表的快速排序,现在用swift写了一个双向链表的快速排序,直接上代码...

  • 链表

    用Swift实现链表 基本概念 链表节点 有了节点,就可以实现链表了 有了上面的基本操作,我们来看如何解决复杂的问...

网友评论

    本文标题:Swift 链表

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