一、什么是双向链表

和单链表比较,双向链表的元素不但知道自己的下线,还知道自己的上线(越来越像传销组织了)。小煤车开起来,图里面可以看出,每个车厢除了一个指向后面车厢的箭头外,还有一个指向前面车厢的箭头(车头、车尾除外)。车头只有指向后面车厢的箭头,车尾只有指向前面车厢的箭头。

二、双向链表与Go的对应结构

1、节点

我们先把车厢分解开来。每节车厢都由煤炭、车体、拉前车厢绳索、拉后车厢绳索这4部分组成。车体是我们的运输工具,在Go语言里我们用结构提DNode表示;煤炭代表运的货物,用data变量表示;拉前车厢绳索和拉后车厢绳索我们分别用指针prev和next表示。这样一节车厢,用Go语言描述如下:

    type DNode struct {
data Object
prev *DNode
next *DNode
}

2、双向链表

一个运煤车队就是一个双向链表。车队要有车头、车厢、车尾,作为车队的负责人还得知道车队有多长。在Go语言里,车队用结构体DList表示,车头用head变量表示,车位用tail变量表示,车队长度就用size来表示,把这些表示合起来:

    type DList struct {
size uint64
head *DNode
tail *DNode
}

结构讲完了,下面讲讲如何增加、减少车厢,也就是双向链表的接口。

三、接口说明及实现

接口主要分为这几类。一个是双向链表本身的,还有一类是节点的。链表本身的还分为公开和私有两种。下面我们就详细聊聊这些接口。

1、初始化链表Init

双向链表的初始化,可以理解成大卫哥准备买一个车队准备运煤。第一步,得获得国家有关部门的批准,有了批准大卫哥就可以买车厢运煤了。但是,批准下来的时候,大卫哥的车队啥都没有,没有车头、车尾,连一节车厢也没有。Go语言代码实现:

    func (dList *DList) Init() {
_dList := *(dList)
_dList.size = 0 // 没车厢
_dList.head = nil // 没车头
_dList.tail = nil // 没车尾
}

2、新增数据Append

大卫哥新买了车厢,买好的车厢要挂到车队后面。第一节车厢就是车头。

    func (dList *DList) Append(data Object) {
newNode := new(DNode)
(*newNode).data = data if (*dList).GetSize() == 0 { // 买个车头
(*dList).head = newNode
(*dList).tail = newNode
(*newNode).prev = nil
(*newNode).next = nil
} else { // 挂在车队尾部
(*newNode).prev = (*dList).tail
(*newNode).next = nil
(*((*dList).tail)).next = newNode
(*dList).tail = newNode
} (*dList).size++;
}

3、在节点后面插入数据InsertNext

有时候,车厢不是放在车队尾巴,而是要放在中间,比如都是运苹果的车厢最好放一起。

    func (dList *DList) InsertNext(elmt *DNode, data Object) bool {
if elmt == nil { // apend
return false
} if dList.isTail(elmt) { // 恰好在车队尾巴
dList.Append(data)
} else {
newNode := new(DNode)
(*newNode).data = data
(*newNode).prev = elmt
(*newNode).next = (*elmt).next (*elmt).next = newNode
(*((*newNode).next)).prev = newNode
(*dList).size++;
} return true
}

5、在节点前面插入数据InsertPrev

在节点前面插入数据,可以理解为在当前节点前一个节点的后面插入数据。

    func (dList *DList) InsertPrev(elmt *DNode, data Object) bool {
if elmt == nil {
return false
} if dList.isHead(elmt) { // 如果是新增一个车头就特殊处理
newNode := new(DNode)
(*newNode).data = data
(*newNode).next = dList.GetHead()
(*newNode).prev = nil (*(dList.head)).prev = newNode
dList.head = newNode
dList.size++
return true
} else {
prev := (*elmt).prev
return dList.InsertNext(prev, data)
}
}

这里的isHead就是判断节点是否是车头,后面大卫哥会介绍。

6、删除一个节点Remove

有些车厢出现问题需要维修,就要把它从车队里卸下来。

    func (dList *DList) Remove(elmt *DNode) Object {
if elmt == nil {
return false
} prev := (*elmt).prev
next := (*elmt).next if dList.isHead(elmt) {
dList.head = next
} else {
(*prev).next = next
} if dList.isTail(elmt) {
dList.tail = prev
} else {
(*next).prev = prev
} dList.size-- return (*elmt).GetData()
}

卸下来后,车厢里的数据还是要保留的。

7、查找指定数据所在的节点Search

比如说,要找到苹果在哪节车厢。就要用到查找功能了。

    func (dList *DList) Search(data Object, yourMatch ...MatchFun) *DNode {
if dList.GetSize() == 0 {
return nil
} match := defaultMatch
if len(yourMatch) > 0 {
match = yourMatch[0]
} node := dList.GetHead()
for ; node != nil; node = node.GetNext() {
if match(node.GetData(), data) == 0 {
break
}
} return node
}

match是匹配函数,定义如下:

    type MatchFun func (data1 Object, data2 Object) int

如果data1和data2相等就返回0,data1大于data2就返回正数,小于就返回负数。

8、获取链表长度GetSize

    func (dList *DList) GetSize() uint64 {
return (*dList).size
}

9、获取头部节点GetHead

    func (dList *DList) GetHead() *DNode {
return (*dList).head
}

10、获取尾部节点GetTail

    func (dList *DList) GetTail() *DNode {
return (*dList).tail
}

11、节点是否是头部节点isHead

    func (dList *DList) isHead(elmt *DNode) bool {
return dList.GetHead() == elmt
}

12、节点是否是列表尾部isTail

    func (dList *DList) isTail(elmt *DNode) bool {
return dList.GetTail() == elmt
}

13、获取节点内数据GetData

    func (dNode *DNode) GetData() Object {
return (*dNode).data
}

这个是节点的方法,不是链表的。用来获取车厢内装的是什么。

14、获取下一个节点GetNext

    func (dNode *DNode) GetNext() *DNode {
return (*dNode).next
}

这个也是节点的方法,帮助车厢找到下一节车厢。

15、获取前一个节点GetPrev

    func (dNode *DNode) GetPrev() *DNode {
return (*dNode).prev
}

这里同样是节点的方法。用来找到上一节车厢。

代码下载

第二节 双向链表的GO语言实现的更多相关文章

  1. 第二节:Web前端-ASP.NET之C#基础

    第二节:Web前端-ASP.NET之C#基础 学习ASP.NET,要掌握学习语言,控件等技能, <div style="text-align: center; line-height: ...

  2. centos单用户 救援 运行级别 yum,单用户模式,救援模式,inittab :启动级别 e2fsck wetty mingetty 物理终端 /dev/console 虚拟终端 /dev/tty(0,6) 模拟终端 /dev/pts/# grub-md5-crypt 给grub加密码 initrd 第二节课

    centos单用户 救援 运行级别  yum,单用户模式,救援模式,inittab :启动级别  e2fsck  wetty  mingetty  物理终端 /dev/console  虚拟终端 /d ...

  3. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第二节

    原文链接 第二节:第一个内核 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并 ...

  4. android内部培训视频_第二节 布局基础

    第二节:布局入门 一.线性布局 需要掌握的属性: 1.orientation:排列方式 vertical:垂直 Horizontal:水平 2.weight:水平布局的权重 3.gravity:子控件 ...

  5. seajs第二节,seajs各模块依赖关系

    index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> &l ...

  6. 学习javascript基础知识系列第二节 - this用法

    通过一段代码学习javascript基础知识系列 第二节 - this用法 this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在javascr ...

  7. VUE2.0实现购物车和地址选配功能学习第二节

    第二节 创建VUE实例 购物车项目计划: 1.创建一个vue实例 2.通过v-for指令渲染产品数据 3.使用filter对金额和图片进行格式化 4.使用v-on实现产品金额动态计算 5.综合演示 ① ...

  8. 第二节 安装CentOS

    Linux 第二节一.安装VNware workstation 10二.安装CentOS 1.root/123456 用户登录[root@localhost ~]# 2.关机 init 0 3.ifc ...

  9. 源码讲解 node+mongodb 建站攻略(一期)第二节

    源码讲解 node+mongodb 建站攻略(一期)第二节 上一节,我们完成了模拟数据,这次我们来玩儿真正的数据库,mongodb. 代码http://www.imlwj.com/download/n ...

随机推荐

  1. Beta版本 为什么程序员总是分不清万圣节和圣诞节?因为 Oct 31 == Dec 25。

    Beta版本 软件的测试版本,经常在各类著名软件中的前期发布版本的后缀中可见,包括大名鼎鼎的windows系统,这个阶段的版本一直加入一些新的功能. 软件测试可分为alpha测试.beta测试和联合测 ...

  2. hdu-4135 Co-prime---容斥定理经典&&求1-m中与n互质的数目

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4135 题目大意: 求区间[a, b]中与N互质的数目. 解题思路: 首先对n求出所有素因子. 对于区 ...

  3. Linux学习总结(四)-两种模式修复系统,单用户,救援模式

    一单用户模式 我们举例,比如忘记root 用户密码我们就可以进入单用户模式重置,该单用户模式,类似windos 安全模式开机界面快速按e 进入grub光标定位到 linux16 下一行ro crash ...

  4. python 闭包@装饰器

    1.装饰器 装饰器(Decorator)相对简单,咱们先介绍它:“装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数”,听起来有点绕,没关系,直 ...

  5. 理解基本包装类型Number,String,Boolean

    在前面我们知道了引用类型是什么了,也就能理解包装类型了.包装对象其实也是一种引用类型,之所以要单独提出来只不过是因为它们可以把原始类型的值变成(包装成)对象,这样它们也就获得了各自类型相应的特殊行为了 ...

  6. 【题解】洛谷P1896 [SCOI2005] 互不侵犯(状压DP)

    洛谷P1896:https://www.luogu.org/problemnew/show/P1896 前言 这是一道状压DP的经典题 原来已经做过了 但是快要NOIP 复习一波 关于一些位运算的知识 ...

  7. VS2013 类向导 "异常来自 HRESULT:0x8CE0000B" 解决方法

    转自 http://blog.csdn.net/skyloveyue/article/details/52105912 我用使用了第二种方法: 改变项目的位置 将项目从原来D盘的位置(D:\proje ...

  8. 第26章 FMC—扩展外部SDRAM

    本章参考资料:<STM32F76xxx参考手册2>.<STM32F7xx规格书>.库帮助文档<STM32F779xx_User_Manual.chm>. 关于SDR ...

  9. 【Javascript-ECMA6-Fetch详解】

    Fetch 由于Fetch API是基于Promise设计,因此旧的浏览器并不支持该API,需要引用时引用es6-promise. 基本知识 fetch请求返回response格式 body Fetc ...

  10. GoBelieve UseID及ImID方案

    GoBelieve: imId = (appid + uid) IM 服务器用(appid + uid)imid做用户的唯一标示 imid是IM平台上沟通的凭证 客户端请求联系人列表后,会有对应uid ...