【Go 数据结构】链表,栈与队列

链表

单链表

链表是一个个数据节点组成的,它是一个递归结构,要么为空,要么它存在一个指向另外一个数据节点的引用。

简单的链表:

package main

import "fmt"

// 单链表
type LinkNode struct {
	Data     int64
	NextNode *LinkNode
}

func main() {
	node := new(LinkNode)

	node.Data = 1

	node1 := new(LinkNode)
	node1.Data = 2
	node.NextNode = node1

	node2 := new(LinkNode)
	node2.Data = 3
	node1.NextNode = node2

	// print list data
	nowNode := node
	for {
		if nowNode != nil {
			fmt.Println(nowNode.Data)

			nowNode = nowNode.NextNode
			continue
		}
		break
	}

}

打印结果为:

1
2
3

除了单链表我们还需要知道哪些链表呢?

  1. 双链表。每个节点既可以找到它之前的节点也可以找到它后面的节点,是双向的。=
  2. 循环链表。从一个点开始往下寻找数据,转一圈之后仍可以回到它本身那个节点,形成一个回路。
  3. 循环单链表和循环双链表的区别就是,一个只能一个方向走,一个可以两个方向走。

接下来,我们来实现一下循环链表。

循环链表

定义结构:

type Ring struct {
	next, prev *Ring
	Value      interface{}
}

循环链表有三个字段,next 表示后驱节点,prev 表示前驱节点, Value 表示值。

初始化循环链表

// 初始化循环链表
func (r *Ring) init() *Ring {
	r.next = r
	r.prev = r
	return r
}

创建指定大小

// New 创建一个链表
func New(n int) *Ring {
	if n <= 0 {
		return nil
	}
	r := new(Ring)
	p := r
	for i := 1; i < n; i++ {
		p.next = &Ring{prev: p}
		p = p.next
	}
	p.next = r
	r.prev = p
	return r
}

获取上一个/下一个节点

/*
获取下一个节点
获取上一个节点
*/

func (r *Ring) Next() *Ring {
	if r.next == nil {
		return r.init()
	}
	return r.next
}

func (r *Ring) Prev() *Ring {
	if r.prev == nil {
		return r.init()
	}
	return r.prev
}

获取第 n 个节点

// 当 n 为负数的时候,表示向前移动,当 n 为正数的时候,表示向后移动

func (r *Ring) Move(n int) *Ring {
	if r.next == nil {
		return r.init()
	}
	switch {
	case n < 0:
		for ; n < 0; n++ {
			r = r.prev
		}
	case n > 0:
		for ; n > 0; n-- {
			r = r.next
		}
	}
	return r
}

添加节点

// 往节点A, 连接一个节点,并且返回之前节点 p 的后驱节点
func (r *Ring) Link(s *Ring) *Ring {
	n := r.Next()
	if s != nil {
		p := s.Prev()
		r.next = s
		s.prev = r
		n.prev = p
		p.next = n
	}
	return n
}

也就是说,在节点 r 之后插入一个新的节点 s ,而 r 节点之前的后继节点,将会链接到新节点后面,并且返回之前的第一个后驱节点 n

删除节点

// 删除节点后的第 n 个节点

func (r *Ring) Unlink(n int) *Ring {
	if n < 0 {
		return nil
	}
	return r.Link(r.Move(n + 1))
}

获取长度

// 获取链表长度
func (r *Ring) Len() int {
	n := 0
	if r != nil {
		for p := r.Next(); p != r; p = p.next {
			n++
		}
	}
	return n
}

数组与链表

一个简单的数组做成的链表:

func ArrayLink() {
	type Value struct {
		Data      string
		NextIndex int64
	}

	var array [5]Value

	array[0] = Value{"a", 3}
	array[1] = Value{"b", 4}
	array[2] = Value{"c", 1}
	array[3] = Value{"d", 2}
	array[4] = Value{"e", -1}

	node := array[0]
	for {
		fmt.Println(node.Data)
		if node.NextIndex == -1 {
			break
		}
		node = array[node.NextIndex]
	}

}

链表和数组是两个不同的概念。一个是编程语言的基本数据类型,表示一个连续的内存地址空间,可以通过一个索引来访问数据。另一个是我们自己定义的数据结构,通过一个数据节点,我们可以定位到另一个数据节点上,不要求连续的空间。

数组的优点是占用空间小,查询快,直接使用索引就可以获取数据元素,缺点是移动和删除数据元素要移动大量的空间。

链表的优点是移动和删除元素速度快,只需要把相关元素重新链接在一起就可以了,但缺点是占用空间大,查询需要进行遍历。

可变长数组

其实在 Golang 中实现对于变长数组,那就是我们熟悉的 Slice 切片。

接下来,我们会自己动手将 Slice 的底层实现一下,原理大致与切片类似。

定义数据类型

// Array 定义可变长的数组
type Array struct {
	array []int       // 使用切片来代替
	len   int         //  表示数组的真实长度
	cap   int         // 容量
	lock  *sync.Mutex // 并发安全使用锁
}

初始化数组

func Make(len, cap int) *Array {
	s := new(Array)
	if len > cap {
		panic("len > cap")
	}

	// 把切片当做数组来用
	array := make([]int, cap, cap)

	// 元数据
	s.array = array
	s.len = 0
	s.cap = cap
	s.lock = &sync.Mutex{} // 初始化锁
	return s
}

我们可以使用满容量和满长度的切片来充当固定数组。

添加元素

// Append 向数组中添加一个元素,如果数组已满,则自动扩容
func (a *Array) Append(element int) {
	a.lock.Lock()
	defer a.lock.Unlock()

	if a.len == a.cap {
		newCap := a.len * 2

		if a.cap == 0 {
			newCap = 1
		}

		newArray := make([]int, newCap, newCap)

		// 把老的数据传输到新的数组里边
		for k, v := range a.array {
			newArray[k] = v
		}

		a.array = newArray
		a.cap = newCap
	}

	a.array[a.len] = element
	a.len = a.len + 1
}

添加多个元素

// AppendMany 向数组中添加多个元素
func (a *Array) AppendMany(element ...int) {
	for _, v := range element {
		a.Append(v)
	}
}

为什么我们这里要使用锁呢?

在并发编程中,锁是一种同步机制,用于保证在任何时刻至多有一个线程执行某一段代码,这段代码通常被称为临界区

如果在 AppendAppendMany 方法中不使用锁,则当多个 goroutine 同时调用这些方法时,它们可能会同时访问和修改 Array 的状态(如 lencap ),这可能会导致数据竞争,并产生不一致的状态,例如你可能会遇到以下情况:

  • 两个 goroutines 同时读取 Array 的长度(假设为 n
  • 它们各自向 Array 添加一个元素
  • 由于它们读取的长度都是 n,所以它们都会将元素添加到索引 n 的位置,从而导致一个元素被覆盖

通过在修改 Array 状态的操作前后获取和释放锁,我们可以保证在任何时间只有一个 goroutine 在执行这些操作,从而避免上述情况的发生。

获取指定下表元素

// Get 通过下标获取元素
func (a *Array) Get(index int) int {
	// 处理越界
	if a.len == 0 || index >= a.len {
		panic("index over len")
	}
	return a.array[index]
}

获取长度和容量

// Len 返回真实的长度
func (a *Array) Len() int {
	return a.len
}

// Cap 返回真实的容量
func (a *Array) Cap() int {
	return a.cap
}

辅助打印函数

func PrintArray(array *Array) (result string) {
	result = "["
	for i := 0; i < array.Len(); i++ {
		// 获取第一个元素
		if i == 0 {
			result += fmt.Sprintf("%s%d", result, array.Get(i))
			continue
		}

		result = fmt.Sprintf("%s, %d", result, array.Get(i))
	}
	result = result + "]"
	return
}

栈与队列

先说一下栈与队列的基本特点:

  • 栈:先进后出,先进队的元素最后出来
  • 队列:后进先出,先进队的元素先出来

对于这两种结构的实现,我们可以使用数组和链表两种实现方式,各有优缺点。

数组栈

定义数据类型

type ArrayStack struct {
	array []string
	size  int
	lock  sync.Mutex
}

入栈

// Push 入栈
func (stack *ArrayStack) Push(v string) {
	stack.lock.Lock()
	defer stack.lock.Unlock()

	stack.array = append(stack.array, v)

	stack.size++
}

出栈

// Pop 出栈
func (stack *ArrayStack) Pop() string {
	stack.lock.Lock()
	defer stack.lock.Unlock()

	if stack.size == 0 {
		panic("stack is empty")
	}
	v := stack.array[stack.size-1]

	// 收缩切片,但空间可能会越拉越大
	//stack.array = stack.array[:stack.size-1]

	// 创建新的切片,长度为原来的长度-1,但是移动次数较多
	newArray := make([]string, stack.size-1, stack.size-1)
	for i := 0; i < stack.size-1; i++ {
		newArray[i] = stack.array[i]
	}
	stack.array = newArray

	stack.size--

	return v
}

获取栈顶元素

// Peek 查看栈顶
func (stack *ArrayStack) Peek() string {
	if stack.size == 0 {
		panic("stack is empty")
	}
	v := stack.array[stack.size-1]
	return v
}

获取栈大小

// Size 大小
func (stack *ArrayStack) Size() int {
	return stack.size
}

判断栈是否为空

// IsEmpty 是否为空
func (stack *ArrayStack) IsEmpty() bool {
	return stack.size == 0
}

链表栈

定义数据类型

type LinkNode struct {
	Value string
	Next  *LinkNode
}

type LinkStack struct {
	root *LinkNode // 链表起点
	size int
	lock sync.Mutex
}

入栈

// Push 入栈
func (stack *LinkStack) Push(v string) {
	stack.lock.Lock()
	defer stack.lock.Unlock()

	// 表示栈目前为空,直接向栈顶插入元素
	if stack.root == nil {
		stack.root = &LinkNode{Value: v, Next: nil}
	} else {
		// 将元素插入到栈顶 , 头插法
		preNode := stack.root

		// 新节点
		newNode := new(LinkNode)
		newNode.Value = v

		newNode.Next = preNode

		// 更新 root
		stack.root = newNode
	}
	stack.size++
}

出栈

// Pop 弹出栈顶元素
func (stack *LinkStack) Pop() string {
	stack.lock.Lock()
	defer stack.lock.Unlock()

	if stack.size == 0 {
		panic("stack is empty")
	}

	// 栈顶元素
	topNode := stack.root
	stack.root = topNode.Next

	stack.size--

	return topNode.Value
}

获取栈顶元素

// Peek 栈顶元素
func (stack *LinkStack) Peek() string {
	// 栈为空
	if stack.size == 0 {
		panic("stack is empty")
	}
	// 栈顶元素
	v := stack.root.Value
	return v
}

获取栈的大小

// Size 栈大小
func (stack *LinkStack) Size() int {
	return stack.size
}

判断是否为空栈

// IsEmpty 栈是否为空
func (stack *LinkStack) IsEmpty() bool {
	return stack.size == 0
}

数组队列

定义数据类型

type ArrayQueue struct {
	array []string
	size  int
	lock  sync.Mutex
}

入队

// Add 添加一个元素給队列
func (queue *ArrayQueue) Add(v string) {
	queue.lock.Lock()
	defer queue.lock.Unlock()

	queue.array = append(queue.array, v)

	queue.size++
}

出队

// Remove 移除队列的第一个元素
func (queue *ArrayQueue) Remove() string {
	queue.lock.Lock()
	defer queue.lock.Unlock()

	if queue.size == 0 {
		panic("Queue is empty")
	}

	v := queue.array[0]

	/* 原地移动,但缩容后空间不会被释放
	for i := 1; i < queue.size; i++ {
		queue.array[i-1] = queue.array[i]
	}
	// 缩容后,数组长度减1
	queue.array = queue.array[:queue.size-1]
	*/
	newArray := make([]string, queue.size-1, queue.size-1)
	for i := 1; i < queue.size; i++ {
		newArray[i-1] = queue.array[i]
	}
	queue.array = newArray
	queue.size--
	return v
}

链表队列

定义数据类型

type LinkNode struct {
	Next  *LinkNode
	Value string
}

type LinkQueue struct {
	root *LinkNode
	size int
	lock sync.Mutex
}

入队

// Add 新元素入队
func (queue *LinkQueue) Add(v string) {
	queue.lock.Lock()
	defer queue.lock.Unlock()

	// 队列为空直接入队, 尾查法
	if queue.root == nil {
		queue.root = &LinkNode{Value: v, Next: nil}
	} else {
		newNode := new(LinkNode)
		newNode.Value = v

		nowNode := queue.root
		for nowNode.Next != nil {
			nowNode = nowNode.Next
		}
		nowNode.Next = newNode

		queue.size++
	}
}

出队

func (queue *LinkQueue) Remove() string {
	queue.lock.Lock()
	defer queue.lock.Unlock()

	// 队列为空直接返回
	if queue.root == nil {
		panic("queue is empty")
	}
	// 头部出元素
	topNode := queue.root
	v := topNode.Value

	queue.root = queue.root.Next
	queue.size--
	return v
}

总结

本次我们介绍使用Go语言实现数据结构中的链表,栈与队列。数据结构这一系列我们没有涉及到具体的细节的讲解,适合有一定数据结构基础的童鞋,本系列代码已经上传至Github,欢迎大家 Star。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/594081.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从一到无穷大 #26 Velox:Meta用cpp实现的大一统模块化执行引擎

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言业务案例PrestoSparkXStreamDistributed messaging systemData IngestionData Pr…

ES集群数据备份与迁移

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、文章涉及概念讲解二、操作步骤1.创建 snapshot repository操作主机hadoop1分别操作从机hadoop2和hadoop3 2. 查看仓库信息3. 备份索引&#xff0c;生成快照…

电商中文场景多模态测试prompt

魔搭社区汇聚各领域最先进的机器学习模型&#xff0c;提供模型探索体验、推理、训练、部署和应用的一站式服务。https://www.modelscope.cn/datasets 多模态大模型Yi-VL-plus体验 效果很棒 - 知乎最近测了一下零一万物的多模态大模型Yi-VL-plus的效果&#xff0c;发现多模态理解…

【hive】transform脚本

文档地址&#xff1a;https://cwiki.apache.org/confluence/display/Hive/LanguageManualTransform 一、介绍二、实现1.脚本上传到本地2.脚本上传到hdfs 三、几个需要注意的点1.脚本名不要写全路径2.using后面语句中&#xff0c;带不带"python"的问题3.py脚本Shebang…

Nginx(搭建高可用集群)

文章目录 1.基本介绍1.在微服务架构中的位置2.配置前提3.主从模式架构图 2.启动主Nginx和两个Tomcat1.启动linux的tomcat2.启动win的tomcat3.启动主Nginx&#xff0c;进入安装目录 ./sbin/nginx -c nginx.conf4.windows访问 http://look.sunxiansheng.cn:7777/search/cal.jsp 3…

基于 Dockerfile 部署nginx服务(实现HTTPS功能)

目录 前言 1、任务要求 2、建立工作目录并上传nginx安装包 3、创建自签名证书 4、创建 nginx Dockerfile 文件 5、准备并编写 nginx.conf 配置文件 6、准备nginx页面文件 7、工作目录文件结构 8、生成镜像 8、启动容器并开启宿主机端口映射 9、浏览器测试 前言 Ngi…

DS:顺序表、单链表的相关OJ题训练(1)

欢迎各位来到 Harper.Lee 的学习小世界&#xff01; 博主主页传送门&#xff1a;Harper.Lee的博客主页 想要一起进步的uu可以来后台找我交流哦&#xff01; 在DS&#xff1a;单链表的实现 和 DS&#xff1a;顺序表的实现这两篇文章中&#xff0c;我详细介绍了顺序表和单链表的…

CMakeLists.txt语法规则:foreach 循环基本用法

一. 简介 cmake 中除了 if 条件判断之外&#xff0c;还支持循环语句&#xff0c;包括 foreach()循环、while()循环。 本文学习 CMakeLists.txt语法中的循环语句。 CMakeLists.txt语法中 有两种 循环实现方式&#xff1a;foreach循环与 while循环。 二. CMakeLists.txt语法规则…

tomcat+maven+java+mysql图书管理系统1-配置项目环境

目录 一、软件版本 二、具体步骤 一、软件版本 idea2022.2.1 maven是idea自带不用另外下载 tomcat8.5.99 Javajdk17 二、具体步骤 1.新建项目 稍等一会&#xff0c;创建成功如下图所示&#xff0c;主要看左方目录相同不。 给maven配置国外镜像 在左上…

asp.net朱勇项目个人博客(3)

引文:按照书上的项目&#xff0c;我们最后实现管理端的三个增删改查的功能即可,相对与三个增删改查&#xff0c;文章&#xff0c;分类和留言&#xff0c;这里我们所需要用的的关联的一个表就是文章表&#xff0c;因为文章表每一个文章的增加显示和修改都需要对应的一个分类&…

Spring入门及注解开发

1 引言 自定义注解可以用来为代码添加元数据信息,简化配置,提高代码的可读性和可维护性。通过自定义注解,可以实现自定义的业务逻辑、约束条件、配置参数等功能。在Spring中,自定义注解常用于标记组件、配置依赖注入、AOP切面等。 自定义注解可以添加元数据信息,低代码框…

银行智能化数据安全分类分级实践分享

文章目录 前言一、数据安全智能分类分级平台建设背景二、数据安全分类分级建设思路和实践1、做标签– 数据安全标签体系2、打标签– 鹰眼智能打标平台 3.03、用标签– 全行统一“数据安全打标签结果”服务提供前言 随着国家对数据安全的高度重视,以及相关法律法规的出台,数据…

【linuxC语言】stat函数

文章目录 前言一、stat函数二、示例代码总结 前言 在Linux系统编程中&#xff0c;stat() 函数是一个非常重要的工具&#xff0c;用于获取文件的元数据信息。无论是在系统管理、文件处理还是应用开发中&#xff0c;都可能会用到 stat() 函数。通过调用 stat() 函数&#xff0c;…

ue引擎游戏开发笔记(31)——对角色移动进行优化:角色滑步处理

1.需求分析&#xff1a; 角色的移动与动画不匹配&#xff0c;角色移动起来像是在滑行。。。适当进行优化。 2.操作实现&#xff1a; 这个问题本质是角色的运动速度并没有匹配世界动画的运行速度&#xff0c;不论世界动画快慢于角色移动速度&#xff0c;都会感到有滑步感。所以…

基于 Spring Boot 博客系统开发(六)

基于 Spring Boot 博客系统开发&#xff08;六&#xff09; 本系统是简易的个人博客系统开发&#xff0c;为了更加熟练地掌握 SprIng Boot 框架及相关技术的使用。&#x1f33f;&#x1f33f;&#x1f33f; 基于 Spring Boot 博客系统开发&#xff08;五&#xff09;&#x1f…

适合打工人的赚钱软件有哪些?盘点5个实用的赚钱软件(真实靠谱)

在这个互联网时代&#xff0c;手机不仅仅是我们的通讯工具&#xff0c;更是我们赚钱的小助手。今天&#xff0c;就让我带你一探究竟&#xff0c;揭秘那些真实靠谱的赚钱软件&#xff0c;让你在家也能轻松赚钱&#xff01; 一、抖音极速版&#xff1a;刷视频也能赚钱 抖音极速版…

Flutter笔记:Widgets Easier组件库(11)- 使用提示吐丝

Flutter笔记 Widgets Easier组件库&#xff08;11&#xff09;使用提示吐丝 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this …

【前端学习——网络相关】浏览器同源策略和跨域

浏览器的同源策略 为什么要有&#xff1f; 帮助阻隔恶意文档&#xff0c;减少可能被攻击的媒介。&#xff08;就是为了安全&#xff09; 如果非同源&#xff0c;共有三种行为受到限制 &#xff08;1&#xff09; Cookie、LocalStorage 和 IndexDB 无法读取。 &#xff08;2…

央视影音 视频下载 2

浏览器猫抓插件&#xff0c;拿到视频地址&#xff0c;这个地址的播放不正常&#xff0c;花屏。https://dh5.cntv.qcloudcdn.com/asp/h5e/hls/2000/0303000a/3/default/6edd15a0ebb3467993bec51a95be0e22/2000.m3u8 改一下地址&#xff0c;把代码中的h5e去掉。网址改为https://…

解决MySQL进行group by 字段返回大量异常结果

目录 问题 原因 解决方案 问题 看这条sql CH2O这个字段的取值只有1&#xff0c;2&#xff0c;3&#xff0c;正常进行group by 分类累加统计返回结果应该是这样&#xff1a; [{"CH2O": 2.0,"insufficient_weight": 142,"Normal_Weight": 164…
最新文章