zoukankan      html  css  js  c++  java
  • [Golang学习笔记] 08 链表

    链表(Linked list)是一种常见数据结构,但并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针。

    由于不必须按顺序存储,链表在插入的时候可以达到O(1),比顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间。

    Go语言的container包中,目前有heap,list,ring三种数据结构。

    Heap 堆 (可实现最大堆和最小堆)
    List 双向链表
    Ring 环形链表

    链表实现标准库的container/list代码包中。
    代码包中2个程序实体List和Element
    List:实现一个双向链表
    Element:代表链表中元素的结构
    List实现了一个双向链表(每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。)

    特点1:链表占用的内存空间要比包含相同元素的数组所占的内存要大得多,因为其元素不是连续存储的,相邻元素之间需要保存对方的指针。
    特点2:每个元素要保存所属链表的指针,在初始化时就拥有了头部元素(根元素),还记录了链表的长度。

    面试题:可以把自己生成的Element类型值传给链表吗?

    List有4种方法:
    MoveBefore:把给定元素移动到另一个元素前面
    MoveAfter:把给定元素移动到另一个元素后面
    MoveFront:把给定元素移动到链表的最前端
    MoveToBack:把给定元素移动到链表的最后端

    func (l *List) MoveBefore(e, mark *Element)
    func (l *List) MoveAfter(e, mark *Element)
    
    func (l *List) MoveToFront(e *Element)
    func (l *List) MoveToBack(e *Element)

    “给定的元素”都是*Element类型,即Element类型的指针类型,该指针类型的值就是元素的指针。

    回答:对于我们在代码中生成的值,链表不会接受,也不会对链表产生任何影响。

    因为代码生成的Element值并不在链表中,而且链表也不允许我们把自己生成的Element值插入链表。

    问题解析:
    List类型中插入新元素的方法只接受interface{}类型的值,这些方法内部会使用Element值包装接受到的新元素。这样做避免了直接使用我们自己生成的元素,也避免了链表的内部关联遭到破坏。

    List.Front:获取链表中最前端元素,理解为List.GetFront
    List.Back:获取链表中最后端元素,理解为List.GetBack
    List.InsertBefore:在指定的元素之前插入新元素
    List.InsertAfter:在指定的元素之后插入新元素
    List.PushFront:在链表的最前端插入新元素
    List.PushBack:在链表的最后端插入新元素

    func (l *List) Front() *Element
    func (l *List) Back() *Element
    
    func (l *List) InsertBefore(v interface{}, mark *Element) *Element
    func (l *List) InsertAfter(v interface{}, mark *Element) *Element
    
    func (l *List) PushFront(v interface{}) *Element
    func (l *List) PushBack(v interface{}) *Element

    这些方法返回的都是Element值的指针(*Element),它们也是链表给出的安全接口,有了这些内部元素的指针,我们就可以去调用那些用于移动元素的方法了。

    扩展知识:

    1、问题:为什么链表可以做到开箱即用?

    List这个“结构体类型”(关键字struct)有两个字段:

    一个是Element类型的字段root(根元素),
    一个是int类型的字段len(链表长度),它们属于包级私有,使用者无法查看和修改。

    例如:

    var l list.List

    l有字段root和len,都被赋予相应的零值,len为0,表示该链表未包含任何元素;root的零值表示该类型的空壳,用字面量表示为:Element{}。

    Element类型包含几个包级私有字段,分别用于存储前一个元素、后一个元素以及所属链表的指针值;

    还有一个Value公开字段,该字段代表了持有元素的实际值,也是interface{}类型。在Element类型的零值中,这些字段的默认值都是nil。

    延迟初始化:

    延迟初始化就是把初始化操作延后,仅在实际需要使用的时候才进行,可以分散初始化操作过于集中带来的计算量和存储空间的密集消耗,优点就在于“延后”。

    例如,如果我们在初始化时,需要集中声明大量的大容量切片的时候,此时CPU和内存空间的使用会激增,

    只有设法让其中的切片及其底层数组被及时回收,内存的使用量才会有所降低。

    而延迟初始化操作,可以将计算量和存储空间的压力分散到实际使用它们的时候,这些数组或切片被实际使用的时间越分散,延迟初始化带来的优势会越明显。

    但是,延迟初始化的缺点,也在于“延后”,试想如果在调用链表的每个方法的时候,都需要去判断链表是否已经被初始化,那么也是一个计算量上的浪费。

    这些方法被调用的越频繁,程序的性能也会降低的越多。

    在这些链表实现方法中,Front方法和Back方法无需对是否初始化做出判断,因为一旦发现链表的长度为0就直接返回nil了。

    又如,在删除元素、移动元素,以及一些插入元素的方法时,只要判断一下传入的元素指向所属链表的指针,是否与当前链表的指针相等就可以了。

    如果不相等,就说明传入的元素不是这个链表的,就可以不用进行后续操作。反之,就一定说明这个链表已经被初始化了。

    原因在于,链表的PushFront,PushBack,PushBackList,PushFrontList方法总会先判断链表的状态,并在必要时进行初始化,这就是延迟初始化;

    而且,在向一个空链表中添加新元素时,必定会先调用这四个方法中的一个,这时新元素中指向所属链表的指针,一定会被设定为当前链表的指针,

    所以说,链表初始化后当前指针肯定会等于新元素指针。

    List类型利用了自身以及Element在结构上的特点,平衡了“延迟初始化”的利弊,使得链表可以“开箱即用”,并在性能上达到最优。

    2、问题:Ring与List的区别在哪儿?

    Ring类型是一个循环链表,也叫“环”。

    List在内部也是一个循环链表根元素永远不会持有任何实际的元素值,而该元素的存在,就是为了连接这个循环链表的首尾两端。

    所以说,List的零值是一个只包含了根元素root,但不包含任何实际元素值的空链表。

    Ring和List的区别:

    1、Ring类型的数据结构由它自身即可代表,而List类型则需要由它及Element类型联合表示;
    2、一个Ring类型的值只代表了其所属的循环链表中的一个元素,而一个List类型的值则代表了一个完整的链表;
    3、在创建并初始化(New函数)一个Ring值时可以指定它包含的元素的数量,而一个List值却不需要。循环链表一旦被创建,其长度是不可变的。
    4、通过ring.Ring声明的变量将会是一个长度为l的循环链表,而List类型的零值则是一个长度为0的链表。
    5、Ring值的Len方法的算法复杂度是O(N),而List值的Len方法复杂度则是O(1),性能上差别较大。

    思考题:
    1、container/ring包中的循环链表的适用场景有哪些?

    Ring用于保持固定数量的元素,保存最近10天日志,最近5天登陆信息等。

    08-1 Ring环形链表(另外总结)

    2、使用过container/heap包中的堆吗?适用场景有哪些?
    Heap用于排序。

     本学习笔记仅为了总结自己学到的Go语言核心知识,方便以后回忆,文中部分内容摘录自极客时间的《Go语言核心36讲》专栏,如有侵权,请联系我删除。

  • 相关阅读:
    Alpha冲刺(5/10)
    Alpha冲刺(4/10)
    团队现场编程
    关于合同标准形的专题讨论
    关于相似标准形的专题讨论
    关于数学分析的计算题I(积分)
    关于数学分析的证明题III(级数)
    关于数学分析的证明题II(微分)
    关于数学分析的证明题I(积分)
    关于反常积分收敛的专题讨论
  • 原文地址:https://www.cnblogs.com/fyql/p/10229056.html
Copyright © 2011-2022 走看看