双向链表的GO语言实现
一、什么是双向链表
和单链表比较,双向链表的元素不但知道自己的下线,还知道自己的上线(越来越像传销组织了)。小煤车开起来,图里面可以看出,每个车厢除了一个指向后面车厢的箭头外,还有一个指向前面车厢的箭头(车头、车尾除外)。车头只有指向后面车厢的箭头,车尾只有指向前面车厢的箭头。 二、双向链表与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就是判断节点是否是车头,后面大卫哥会介绍。 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 } 这个是节点的方法,不是链表的。用来获取车厢内装的是什么。 func (dNode *DNode) GetNext() *DNode { return (*dNode).next } 这个也是节点的方法,帮助车厢找到下一节车厢。 func (dNode *DNode) GetPrev() *DNode { return (*dNode).prev } 这里同样是节点的方法。用来找到上一节车厢。 代码下载 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |