Go 语言数据结构如何实现抄一个list示例详解

目录
  • 前言
  • list是个啥
  • list结构
  • Init & New
  • InsertAfter & InsertBefore & PushBack & PushFront
  • Back & Front
  • Remove
  • 总结

前言

闲来无事,自己实现一个 Go提供的list

  • list是Go提供的一个内置的包
  • 内部就是实现了一个双向循环链表以及各种API

目标明确:就是实现一个双向循环链表

文章源码

list是个啥

在开始做之前,还是要先了解一下链表这个数据结构 ,长话短说:

  • 线性表的链式存储结构称为链表,如:
a.next = b
a.prev = c
b.next = c
b.prev = a
c.next = a
c.prev = b

这就是一个双向循环链表

  • 链表可以提升存储空间的利用率,实现了存储空间动态管理的链式存储结构

接下来,我们来看看Go官方都为这个list提供了哪些操作,我们逐一实现

  • New:创建一个链表
  • Init:初始化一个链表
  • Back:返回链表中的最后一个元素
  • Front:返回链表中的第一个元素
  • InsertAfter(e,at):将e加入at元素后
  • InsertBefore(e,at):将e加入at元素前
  • Len:返回list的长度
  • PushBack(e):将e成为链表的最后一个元素
  • PushFront(e):将e成为链表的第一个元素
  • Remove(e):将list上的e删除

list结构

定义list结构,以及list内部node节点的结构,这里采用struct实现

type Element struct {
	prev, next *Element
	Value      any
}
type List struct {
	root Element
	len  int
}

Init & New

Init就是提供初始化一个环链表的方法,并返回这个环形链表

之所以把 Init 和 New 放在一起,是因为在 New 函数中其实就是对 Init 的一层包装,这样就可以实现Go中的包名.New方法,比如:errors.New()

// 初始化一个 环list
func (list *List) Init() *List {
	// 形成环
	list.root.next = &list.root
	list.root.prev = &list.root
	list.len = 0
	return list
}
func NewList() *List {
	return new(List).Init()
}

InsertAfter & InsertBefore & PushBack & PushFront

这两个方法的作用类似,就是将 e 插入到 at 的后/前位置

这里我们先看一个图:

这个图片就是一个双向环形链表,我们要在这个里面进行插入元素操作,比如,我们要插入 e 到 e1 前面我们应该怎么做?

  • 将e的下一个变为e1:e.next = e1
  • 将e的上一个变为e1的上一个:e.prev = e1.prev
  • 将e的上一个的下一个变为自己:e.prev.next = e
  • 将e的下一个的上一个变为自己:e.next.prev = e

这样就完成了插入,回到方法实现上,一个是插入之后,一个插入之前,那么我们是不是可以看作是相同操作,其实都已插入操作,只是位置的变化。

这时候想象一下,比如让你 e 插入 at 之前,但是只提供了,参数1插入参数2后面的操作,如何办到呢?

将 e 插入到 at 的前一个的后面,是不是就ok了,就相当于自己让别人插个队,你在我前面的后面站就行了

// Insert 插入:将 currentElement 插入至 originElement 后
func (list *List) Insert(currentElement, originElement *Element) *Element {
	currentElement.next = originElement.next
	currentElement.prev = originElement
	currentElement.prev.next = currentElement
	currentElement.next.prev = currentElement
	list.len++
	return currentElement
}
// InsertAfter 插入在之后
func (list *List) InsertAfter(currentElement, originElement *Element) *Element {
	return list.Insert(currentElement, originElement)
}
// InsertBefore 插入在之前
func (list *List) InsertBefore(currentElement, originElement *Element) *Element {
	return list.Insert(currentElement, originElement.prev)
}

这样一来,好像把 PushBack 和 PushFront都实现了,这就是封装的好处

// PushBack 插入一个元素在最后
func (list *List) PushBack(originElement *Element) *Element {
	list.InsertBefore(originElement, &list.root)
	return originElement
}
// PushFront 插入一个元素在最前
func (list *List) PushFront(originElement *Element) *Element {
	list.InsertAfter(originElement, &list.root)
	return originElement
}

Back & Front

这两个方式抽象上说,也是一样的功能,一个是返回链表最后一个,另一个是返回链表第一个,因为这里提供了头结点,所以特别简单

最后一个节点 = 头结点.prev

第一个节点 = 头结点.next

// Back 返回最后一个元素
func (list *List) Back() *Element {
	if list.len == 0 {
		return nil
	}
	// 头结点的上一个就是最后一个
	return list.root.prev
}
// Front 返回第一个元素
func (list *List) Front() *Element {
	if list.len == 0 {
		return nil
	}
	// 头结点的下一个就是第一个元素
	return list.root.next
}

Remove

Remove方法就是提供了,删除链表上的某个元素,怎么样才能删除某个节点呢,本质也就是让前后的节点相互链表,我就被排挤出来了,这样就可以实现删除

  • 将要删除的元素 e.next.prev = e.prev
  • 将要删除的元素 e.prev.next = e.next
// Remove 删除某个元素
func (list *List) Remove(originElement *Element) (any,error) {
	if originElement == &list.root {
		return nil, errors.New("the origin Element can not be list.root")
	}
	for e := list.root.next; e != &list.root; e = e.next {
		if e == originElement {
			e.prev.next = e.next
			e.next.prev = e.prev
			return e.Value, nil
		} else {
			continue
		}
	}
	return nil, errors.New("the origin Element dose not belong to the list")
}

总结

  • 链表可以实现存储空间动态管理,它是一个链式存储结构
  • 封装的代码更具灵活性

以上就是Go 语言数据结构如何实现抄一个list示例详解的详细内容,更多关于Go 语言数据结构list的资料请关注我们其它相关文章!

(0)

相关推荐

  • Go 语言数据结构之双链表学习教程

    目录 双链表 创建节点 双链表遍历 扩展功能 链表长度 插入 删除 反转双链表 总结 双链表 双链表 (Doubly Linked List),每个节点持有一个指向列表前一个元素的指针,以及指向下一个元素的指针. 双向链表的节点中包含 3 个字段: 数据域 Value 一个 Next 指针指向双链表中的下一个节点 一个 Prev 指针,指向双链表中的前一个节点 结构体如下: type Node struct { Prev *Node Value int Next *Node } 实际应用: 音乐

  • Go 数据结构之堆排序示例详解

    目录 堆排序 堆排序过程 动画显示 开始堆排序 代码实现 总结 堆排序 堆排序是一种树形选择排序算法. 简单选择排序算法每次选择一个关键字最小的记录需要 O(n) 的时间,而堆排序选择一个关键字最小的记录需要 O(nlogn)的时间. 堆可以看作一棵完全二叉树的顺序存储结构. 在这棵完全二叉树中,如果每个节点的值都大于等于左边孩子的值,称为大根堆(最大堆.又叫大顶堆).如果每个节点的值都小于等于左边孩子的值,称为小根堆(最小堆,小顶堆). 可以,用数学符号表示如下: 堆排序过程 构建初始堆 在输

  • Go语言数据结构之希尔排序示例详解

    目录 希尔排序 算法思想 图解算法 Go 代码实现: 总结 希尔排序 在插入排序中,在待排序序列的记录个数比较少,而且基本有序,则排序的效率较高. 1959 年,Donald Shell 从“减少记录个数” 和 “基本有序” 两个方面对直接插入排序进行了改进,提出了希尔排序算法. 希尔排序又称为“缩小增量排序”.即将待排序记录按下标的一定增量分组(减少记录个数),对每组记录使用直接插入排序算法排序(达到基本有序): 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1,整个序列基本有序,再对

  • Go语言数据结构之选择排序示例详解

    目录 选择排序 动画演示 Go 代码实现 总结 选择排序 选择排序(selection sort)是一种原地(in-place)排序算法,适用于数据量较少的情况.由于选择操作是基于键值的且交换操作只在需要时才执行,所以选择排序长用于数值较大和键值较小的文件. 思想: 对一个数组进行排序,从未排序的部分反复找到最小的元素,并将其放在开头. 给定长度为 nnn 的序列和位置索引i=0 的数组,选择排序将: 遍历一遍序列,寻找序列中的最小值.在 [i...n−1] 范围内找出最小值 minValue

  • Golang迭代如何在Go中循环数据结构使用详解

    目录 引言 如何在Go中循环字符串 如何在Go中循环map结构 如何在Go中循环Struct 结论 引言 数组是存储类似类型数据的强大数据结构.您可以通过索引识别和访问其中的元素. 在Golang中,您可以通过在0初始化变量i并增加变量直到它达到数组的长度,使用for循环循环数组. 它们的语法如下所示: for i := 0; i < len(arr); i++ { // perform an operation } 例如,让我们循环一个整数数组: package main import ( &qu

  • go sync Waitgroup数据结构实现基本操作详解

    目录 WaitGroup 示例 WaitGroup 基本原理 背景知识 信号量 WaitGroup 中的信号量 WaitGroup 数据结构 noCopy state sema WaitGroup 的三个基本操作 WaitGroup 的实现 Add 的实现 Done 的实现 Wait 的实现 总结 WaitGroup 示例 本文基于 Go 1.19. go 里面的 WaitGroup 是非常常见的一种并发控制方式,它可以让我们的代码等待一组 goroutine 的结束. 比如在主协程中等待几个子

  • Go语言数据结构之插入排序示例详解

    目录 插入排序 动画演示 Go 代码实现 总结 插入排序 插入排序,英文名(insertion sort)是一种简单且有效的比较排序算法. 思想: 在每次迭代过程中算法随机地从输入序列中移除一个元素,并将改元素插入待排序序列的正确位置.重复该过程,直到所有输入元素都被选择一次,排序结束. 插入排序有点像小时候我们抓扑克牌的方式,如果抓起一张牌,我们放在手里:抓起第二张的时候,会跟手里的第一张牌进行比较,比手里的第一张牌小放在左边,否则,放在右边. 因此,对所有的牌重复这样的操作,所以每一次都是插

  • go数据结构和算法BitMap原理及实现示例

    目录 1. BitMap介绍 如何判断数字在bit数组的位置 设置数据到bit数组 从bit数组中清除数据 数字是否在bit数组中 2. Go语言位运算 左移 右移 使用&^和位移运算来给某一位置0 3. BitMap的Go语言实现 定义 创建BitMap结构 将数据添加到BitMap 从BitMap中删除数据 判断BitMap中是否存在指定的数据 1. BitMap介绍 BitMap可以理解为通过一个bit数组来存储特定数据的一种数据结构.BitMap常用于对大量整形数据做去重和查询.在这类查

  • Go语言基础变量的声明及初始化示例详解

    目录 一.概述 二.声明变量 三.编译器推导类型的格式[一定要赋值] 四.短变量声明并初始化 五.匿名变量--没有名字的变量 六.注意 七.案例 一.概述 变量的功能是存储用户的数据 二.声明变量 Go语言的每一个变量都拥有自己的类型,必须经过声明才能开始用 变量的声明格式: var <变量名称> [变量类型] var a int //声明一个整型类型的变量,可以保存整数数值 var b string //声明一个字符串类型的变量 var c float32 //声明一个32位浮点切片类型的变

  • C语言实现生成新春福字的示例详解

    目录 主要代码 字面量以及数据结构 定义一个回调函数,刷新福字 应用初始化程序 主程序 效果展示 快新年了,支付宝扫福活动又开始了,每次都要百度找福,这次不想找了,自己写一个程序生成各种字体的福字. 主要代码 字面量以及数据结构 #define FONT_DISPLAY "福" // g_fu_label中的每一个控件都是一个福字 static GtkWidget *g_fu_label[3][3]; // 记录所有的字体family typedef struct { int n_fa

  • Go语言学习教程之结构体的示例详解

    目录 前言 可导出的标识符 嵌入字段 提升 标签 结构体与JSON相互转换 结构体转JSON JSON转结构体 练习代码步骤 前言 结构体是一个序列,包含一些被命名的元素,这些被命名的元素称为字段(field),每个字段有一个名字和一个类型. 结构体用得比较多的地方是声明与数据库交互时需要用到的Model类型,以及与JSON数据进行相互转换.(当然,项目中任何需要多种数据结构组合在一起使用的地方,都可以选择用结构体) 代码段1:声明一个待办事项的Model类型: type Todo struct

  • C语言动态内存管理malloc柔性数组示例详解

    目录 1.1为什么存在动态内存管理 1.2动态内存管理函数 1.2.1malloc 1.2.2free 1.2.3calloc 1.2.4realloc 1.3动态内存管理函数易错点 1.3.1对NULL指针的解引用操作 1.3.2对动态开辟空间的越界访问 1.3.3对非动态开辟内存使用free释放 1.3.4使用free释放一块动态开辟内存的一部分 1.3.5对同一块动态内存多次释放 1.3.6动态开辟内存忘记释放(内存泄漏) 2.1常见相关笔试题 2.2C/C++语言中的内存开辟 2.3柔性

  • C语言编程C++旋转字符操作串示例详解

    目录 旋转字符串 字符串左旋 题前认知: 暴力移位: 三步翻转: 判断字符串旋转 题前认知 字符串追加判断 旋转字符串 字符串左旋 实现一个函数,可以左旋字符串中的k个字符. 例如: ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 题前认知: 一个字符串如果就定死了.eg:char arr[]="dfdf"什么的那多没意思,一点都没有人机交互的感觉,(虽然现在人机交互适合个体,不适合集群,但也是比死板的定死字符串舒服) 所以字符串得是我们可输入的,才有可玩性,玩的不

  • Go语言基础切片的创建及初始化示例详解

    目录 概述 语法 一.创建和初始化切片 make 字面量 二.使用切片 赋值和切片 切片增长 遍历切片 总结 总示例 示例一  两个slice是否相等 示例二 两个数字是否包含 概述 切片是一种动态数组 按需自动改变大小 与数组相比,切片的长度可以在运行时修改 语法 一.创建和初始化切片 make 使用内置函数make()创建切片: var slice []type = make([]type, len, cap) //简写: slice := make([]type, len, cap) 字面

  • Go语言基础go build命令用法及示例详解

    目录 go build 一个Go项目在GOPATH下,会有如下三个目录 使用: 注意: go build 1. 用于测试编译多个包或一个main包 2. build命令编译包丢弃非main包编译结果,只是检查是否能够被编译 3. 保留main包编译结果 一个Go项目在GOPATH下,会有如下三个目录 bin存放编译后的可执行文件 pkg存放编译后的包文件 src存放项目源文件 一般,bin和pkg目录可以不创建,go命令会自动创建(如 go install),只需要创建src目录即可. 使用:

  • Go语言基础if条件语句用法及示例详解

    目录 概述 语法 格式规则 概述 条件语句需要开发者通过指定一个或多个条件 并通过测试条件是否为 true 来决定是否执行指定语句 并在条件为 false 的情况再执行另外的语句. 语法 package main func main() { //第一种格式 if 条件表达式 { 语句1 } //第二种格式 if 初始化表达式; 条件表达式 { 语句1 } //第三种格式 if 初始化表达式; 条件表达式 { 语句1 }else{ 语句2 } //第四种格式 if 初始化表达式; 条件表达式 {

  • R语言使用cgdsr包获取TCGA数据示例详解

    目录 TCGA数据源 TCGA数据库探索工具 查看任意数据集的样本列表方式 选定数据形式及样本列表后获取感兴趣基因的信息,下载mRNA数据 选定样本列表获取临床信息 综合性获取 下载mRNA数据 获取病例列表的临床数据 从cBioPortal下载点突变信息 从cBioPortal下载拷贝数变异数据 把拷贝数及点突变信息结合画热图 TCGA数据源 众所周知,TCGA数据库是目前最综合全面的癌症病人相关组学数据库,包括的测序数据有: DNA Sequencing miRNA Sequencing P

  • go语言reflect.Type 和 reflect.Value 应用示例详解

    目录 一.使用 reflect.Type 创建实例 二.使用 reflect.Value 调用函数 一.使用 reflect.Type 创建实例 在通过 reflect.TypeOf 函数获取到变量的反射类型对象之后,可以通过反射类型对象 reflect.Type 的 New 函数来创建一个新的实例,注意这个实例的类型是 reflect.Type 类型的. package main import ( "fmt" "reflect" ) func main() { v

随机推荐