php双向链表,双向链表的GO语言实现

一、什么是双向链表

bVYdyv?w=780&h=174

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

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

1、节点

bVYdyA?w=778&h=406

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

type DNode struct {

data Object

prev *DNode

next *DNode

}

2、双向链表

bVYdyF?w=772&h=461

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

type DList struct {

size uint64

head *DNode

tail *DNode

}

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

三、接口说明及实现

bVYdyM?w=896&h=451

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

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

}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值