链表(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讲》专栏,如有侵权,请联系我删除。

[Golang学习笔记] 08 链表的更多相关文章

  1. golang学习笔记20 一道考察对并发多协程操作一个共享变量的面试题

    golang学习笔记20 一道考察对并发多协程操作一个共享变量的面试题 下面这个程序运行的能num结果是什么? package main import ( "fmt" " ...

  2. golang学习笔记19 用Golang实现以太坊代币转账

    golang学习笔记19 用Golang实现以太坊代币转账 在以太坊区块链中,我们称代币为Token,是以太坊区块链中每个人都可以任意发行的数字资产.并且它必须是遵循erc20标准的,至于erc20标 ...

  3. golang学习笔记18 用go语言编写移动端sdk和app开发gomobile

    golang学习笔记18 用go语言编写移动端sdk和app开发gomobile gomobile的使用-用go语言编写移动端sdk和app开发https://blog.csdn.net/u01249 ...

  4. golang学习笔记17 爬虫技术路线图,python,java,nodejs,go语言,scrapy主流框架介绍

    golang学习笔记17 爬虫技术路线图,python,java,nodejs,go语言,scrapy主流框架介绍 go语言爬虫框架:gocolly/colly,goquery,colly,chrom ...

  5. golang学习笔记16 beego orm 数据库操作

    golang学习笔记16 beego orm 数据库操作 beego ORM 是一个强大的 Go 语言 ORM 框架.她的灵感主要来自 Django ORM 和 SQLAlchemy. 目前该框架仍处 ...

  6. golang学习笔记14 golang substring 截取字符串

    golang学习笔记14 golang substring 截取字符串golang 没有java那样的substring函数,但支持直接根据 index 截取字符串mystr := "hel ...

  7. golang学习笔记13 Golang 类型转换整理 go语言string、int、int64、float64、complex 互相转换

    golang学习笔记13 Golang 类型转换整理 go语言string.int.int64.float64.complex 互相转换 #string到intint,err:=strconv.Ato ...

  8. golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题

    golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题 今天测试了重新建一个项目生成新的表,然后复制到旧的项目 ...

  9. golang学习笔记11 golang要用jetbrain的golang这个IDE工具开发才好

    golang学习笔记11   golang要用jetbrain的golang这个IDE工具开发才好  jetbrain家的全套ide都很好用,一定要dark背景风格才装B   从File-->s ...

随机推荐

  1. Web前端——jQuery----细节

    jQuery终极思想:创建jQuery对象(当发现相同的选择器在你的代码里出现多次时,请用变量把它储存起来.一切面向对象) 认识jQuery 1.jQuery库可以做什么? HTML元素选取 HTML ...

  2. The good life is one inspired by love and guided by knowledge

    The good life is one inspired by love and guided by knowledge 伯特兰·罗素Bertrand Russell18721970 I can a ...

  3. 关于Class类的getResource().getPath()方法

    程序中配置文件如果放置在classes文件夹,那么我们就可以使用Class类的getResource().getPath()方法获取文件路径. 例如: String path = DBUtil.cla ...

  4. webpack环境搭建开发环境,JavaScript面向对象的详解,UML类图的使用

    PS:因为所有的设计模式都是基于面向对象来完成的,所以在讲解设计模式之前先来过一下面向对象都有哪些知识点 搭建开发环境 初始化npm环境 下载安装nodejs安装即可,nodejs自带npm管理包,然 ...

  5. BZOJ5072:[Lydsy1710月赛]小A的树(树形DP)

    Description BZOJ只是扔了个下载链接 Solution 设$f[x][i]$表示$x$点选中$i$个黑点的最小连通块. 设$g[x][i]$表示$x$点选中$i$个黑点的最大连通块. 转 ...

  6. 【[ZJOI2008]泡泡堂】

    想贪心就是反复\(hack\)自己的过程 首先这很田忌赛马,但是又不完全一样 田忌赛马保证了所有马的实力不同,因此没有平局 田忌赛马的策略是当自己最强的马比不过对方最强的马的时候,就用自己最弱的马来自 ...

  7. WEB测试—兼容测试

    现在的操作系统,浏览器越来越多样性,导致软件兼容性测试在目前软件测试领域占有很重要的地位,我在测试项目经历中,以浏览器的兼容测试为主,平台.分辨率为辅. (我的经验前提:我测试过3类的不同行业的web ...

  8. php json格式化输出

    1.json格式是适用于多种语言的数据格式,通用性高 2.在php中将array格式的数据转化为json格式 3.默认情况下转化后的json格式为一个串,需要将这个串格式化成相应的样式输出 主要的函数 ...

  9. JNI由浅入深_8_JNI缓存字段和方法ID

    获取字段ID和方法ID时,需要用字段.方法的名字和描述符进行一个检索.检索过程相对比较费时,因此本节讨论用缓存技术来减少这个过程带来的消耗.缓存字段ID和方法ID的方法主要有两种.两种区别主要在于缓存 ...

  10. Linux 嵌入式 开发环境 交叉编译安装

    1.安装 Ubuntu 系统 安装完毕,系统 提示 重启,这个时候 请拔掉U盘,进行重启 OK. 2.安装 NFS 服务 3.安装 openssh服务 4.开启openSSH服务 5.就可以使用 Wi ...