dlist,Go语言2021列表数据结构及原理剖析

本文为“Goalng全面深入系列”中的标准库部分。

1. 什么是双向链表

dlist,Go语言2021列表数据结构及原理剖析

(引用)

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

2. 和单向链表相比的优势

1. **删除不需要移动元素外,可以原地**删除

2. 可以双向遍历

**数据到中间

删除中间数据

dlist,Go语言2021列表数据结构及原理剖析

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

1.节点分析

dlist,Go语言2021列表数据结构及原理剖析

我们先把车厢分解开来。每节车厢都由煤炭、车体、拉前车厢绳索、拉后车厢绳索这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 }

通过找到其中一个节点,就可以通过prev 或 next指向得到指向的数据。

4. Go自定义实现链表

dlist,Go语言2021列表数据结构及原理剖析

1.初始化Init

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

package main

//节点数据结构 type DNode struct {         data interface{}         prev *DNode         next *DNode }

// 链表数据结构 type DList struct {         size uint64         head *DNode         tail *DNode }

// 链表的初始化 func InitList() (list *DList) {         list = *(DList)         list.size = 0         list.head = nil         list.tail = nil         return }

// 新增数据 func (dlist *DList) (data {}) {         // 创建一个节点         := &DNode{data: data}

if (*dlist).() == 0 { //只有一个节点                 (*dlist).head =                 (*dlist).tail = // 头尾节点都是自己                 (*).prev = nil   // 但是头尾的指向是nil                 (*).next = nil         } else { // 接到尾部                 // 新节点的指向修改                 (*).prev = (*dlist).tail                 (*).next = nil

// 之前尾节点的指向修改                 (*(*dlist).tail).next = // 将之前的尾节点的next指向新增节点

// 更新链表的尾节点                 (*dlist).tail =         }

// 更新链表的节点计数         (*dlist).size++ }

// 在节点后面**数据InsertNext //param //        - ele 所要**的位置 //        - data 所要**的节点数据 // func (dlist *DList) InsertNext(ele *DNode, data interface{}) bool {         if ele == nil {                 return false         }

if dlist.(ele) { //正好在尾部                 dlist.(data)         } else { // 在中间**                 //构造新节点                 := new(DNode)                 (*).data = data                 (*).prev = ele         // 上一个节点就是ele                 (*).next = (*ele).next // 下一个节点就是ele原来的下一个节点

// ele的下一个指向新节点                 (*ele).next =

// ele之前下节点的prev重新指向新节点                 *((*).next).prev =

// 更新链表计数                 (*dlist).size++         } }

//*==== 节点之前**接口都是类似的方式:         1. 首先根据数据创建新节点, 并设置指向         2. 更新位置节点的指向数据         3. 更新链表head , tail ,size数据

删除节点:         1. 首先获取要删除节点指向数据         (验证头尾)         2. 更新要删除节点的prev节点的next为要删除节点的next节点( 有点乱啊!!)         3. 更新链表数据

记得return要删除的节点数据(否则数据丢失)

查找节点:         type MatchFun func (data1 interface{}, data2 interface{}) int         func (dList *DList) Search(data Object, yourMatch MatchFun) *DNode

*=====*//

// 获取链表长度GetSize func (dList *DList) GetSize() uint64 {         return (*dList).size }

//获取头部节点GetHead

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

//获取尾部节点GetTail

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

通过自己实现链表,来更深入了解链表的结构后, 我们使用go的 container/list 库实现。

5.Go库container/list 实现链表操作

关于库的成员函数,我就不一一列举了, 看一看文档很详细,也很简单。

下面直接上案例:

func main() {         link := list.New()

// 循环**到头部         for i := 0; i = 10; i++ {                 link.PushBack(i)         }

// 遍历链表         for p := link.Front(); p != link.Back(); p = p.Next() {                 fmt.Println("Number", p.Value)         }

}

6. slice和list的性能比较

1. 创建、 添加元素的比较

package main

import (         "container/list"         "fmt"         "time" ) func main(){   T1() }

func T1() {         t := time.Now()         //1亿创建添加测试         // slice 创建

slice := make([]int, 10)         for i := 0; i 1*100000*1000; i++ {                 slice = (slice, i)         }         fmt.("slice 创建成功:", time.Now().Sub(t).())

// list创建添加         t = time.Now()         l := list.New()         for i := 0; i 1*100000*1000; i++ {                 l.(i)         }         fmt.("list创建成功:", time.Now().Sub(t).()) } func T2() {         sli := make([]int, 10)         for i := 0; i 1*100000*1000; i++ {                 sli = (sli, 1)         }

l := list.New()         for i := 0; i 1*100000*1000; i++ {                 l.(1)         }         // 比较遍历         t := time.Now()         for _, _ = range sli {                 //fmt.("[%d]=%dn", i, item)         }         fmt.("遍历slice的速度:" + time.Now().Sub(t).())         t = time.Now()         for e := l.Front(); e != nil; e = e.Next() {                 //fmt.(e.Value)         }         fmt.("遍历list的速度:" + time.Now().Sub(t).()) } func T3() {         sli := make([]int, 10)         for i := 0; i 1*100000*1000; i++ {                 sli = (sli, 1)         }

l := list.New()         for i := 0; i 1*100000*1000; i++ {                 l.(1)         }         //比较**         t := time.Now()         slif := sli[:100000*500]         slib := sli[100000*500:]         slif = (slif, 10)         slif = (slif, slib...)         fmt.("slice 的**速度" + time.Now().Sub(t).())

var em *list.         len := l.Len()         var i int         for e := l.Front(); e != nil; e = e.Next() {                 i++                 if i == len/2 {                         em = e                         break                 }         }         //忽略掉找中间元素的速度。         t = time.Now()         ef := l.(2)         l.(ef, em)         fmt.("list: " + time.Now().Sub(t).()) }

简单的测试下,如果频繁的**和删除建议用list,频繁的遍历查询选slice。

由于/list不是并发安全的,所以需要自己手动添加一层并发的包装。

dlist,Go语言2021列表数据结构及原理剖析

声明:本站所有作品(图文、音视频)均由用户自行上传分享,本文由"like刘刘"自行发布,本站仅供存储和学习交流。若您的权利被侵害,请联系我们删除。如若转载,请注明出处:https://www.flipbrief.com/fresh/8eWB7n6v.html