Golang 链表的学习和使用


Posted in Golang onApril 19, 2022

1. 什么是链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

使用链表结构可以避免在使用数组时需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

链表允许插入和移除表上任意位置上的结点,但是不允许随机存取。

链表有三种类型:单向链表、双向链表、循环链表。

2. 单项链表的基本操作

单向链表中每个结点包含两部分,分别是数据域和指针域,上一个结点的指针指向下一结点,依次相连,形成链表。

链表通过指针将一组零散的内存块串联在一起,这里的内存块称为链表的结点。为了将这些节点给串起来,每个链表的结点除了存储数据之外,还会记录下一个结点的指针(即下一个结点的地址),这个指针称为:后继指针

Golang 链表的学习和使用

3. 使用 struct 定义单链表

利用 Struct 可以包容多种数据类型的特性

一个结构体内可以包含若干成员,这些成员可以是基本类型、自定义类型、数组类型,也可以是指针类型。

struct 定义的三种形式,其中2和3都是返回结构体的指针

//定义
var stu Student

var stu *Student = new(Student)

var stu *Student = &Student {}

//调用
stu.Name   stu.Age    stu.Score
或
(*stu).Name	   (*stu).Age   (*stu).Score

定义一个单项链表

next 是指针类型的属性,指向 Student struct 类型数据,也就是下一个节点的数据类型

type Student struct {
	Name  string
	Age   int
	Score float32
	next  *Student
}

为链表赋值,并遍历链表中的每个节点

package main

import "fmt"

type Student struct {
	Name  string
	Age   int
	Score float32
	next  *Student		//存放下一个结构体的地址,用*直接指向下一个结构体
}

func main() {
	//头部结构体
	var head Student
	head.Name = "张三"
	head.Age = 28
	head.Score = 88

	//第二个结构体节点
	var stu1 Student
	stu1.Name = "李四"
	stu1.Age = 25
	stu1.Score = 100

	head.next = &stu1

	//第三个结构体节点
	var stu2 Student
	stu2.Name = "王五"
	stu2.Age = 18
	stu2.Score = 60

	stu1.next = &stu2

	Req(&head)
}

func Req(tmp *Student) {		//tmp指针是指向下一个结构体的地址,加*就是下一个结构体
	for tmp != nil {			//遍历输出链表中每个结构体,判断是否为空
		fmt.Println(*tmp)
		tmp = tmp.next			//tmp变更为下一个结构体地址
	}
}


//输出结果如下
{张三 28 88 0xc000114480}
{李四 25 100 0xc0001144b0}
{王五 18 60 <nil>}

4. 尾部添加节点

方法一

package main

import (
	"fmt"
	"math/rand"
)

type Student struct {
	Name  string
	Age   int
	Score float32
	next  *Student
}

func main() {
	//头部结构体
	var head Student
	head.Name = "head"
	head.Age = 28
	head.Score = 88

	//第二个结构体节点
	var stu1 Student
	stu1.Name = "stu1"
	stu1.Age = 25
	stu1.Score = 100

	head.next = &stu1 //头部指向第一个结构体

	//第三个结构体节点
	var stu2 Student
	stu2.Name = "stu2"
	stu2.Age = 18
	stu2.Score = 60

	stu1.next = &stu2 //第一个结构体指向第二个结构体

	//第四个结构体节点
	var stu3 Student
	stu3.Name = "stu3"
	stu3.Age = 18
	stu3.Score = 80

	stu2.next = &stu3 //第二个结构体指向第三个结构体

	//声明变量
	var tail = &stu3
	for i := 4; i < 10; i++ {
		//定义节点
		var stu Student = Student{
			Name:  fmt.Sprintf("stu%d", i),
			Age:   rand.Intn(100),
			Score: rand.Float32() * 100,
		}
		//生产结构体串联
		tail.next = &stu
		tail = &stu
	}

	Req(&head)
}

func Req(tmp *Student) {
	for tmp != nil {
		fmt.Println(*tmp)
		tmp = tmp.next
	}
}

//输出结果如下
{head 28 88 0xc0001144b0}
{stu1 25 100 0xc0001144e0}
{stu2 18 60 0xc000114510}
{stu3 18 80 0xc000114540}
{stu4 81 94.05091 0xc000114570}
{stu5 47 43.77142 0xc0001145a0}
{stu6 81 68.682304 0xc0001145d0}
{stu7 25 15.651925 0xc000114600}
{stu8 56 30.091187 0xc000114630}
{stu9 94 81.36399 <nil>}

方法二,使用函数进行优化

package main

import (
	"fmt"
	"math/rand"
)

type Student struct {
	Name  string
	Age   int
	Score float32
	next  *Student
}

func main() {
	//头部结构体
	var head Student
	head.Name = "head"
	head.Age = 28
	head.Score = 88

	TailInsert(&head)
	Req(&head)
}

//循环遍历
func Req(tmp *Student) {
	for tmp != nil {
		fmt.Println(*tmp)
		tmp = tmp.next
	}
}

//添加结构体节点
func TailInsert(tail *Student) {
	for i := 0; i < 10; i++ {
		//定义节点
		var stu Student = Student{
			Name:  fmt.Sprintf("stu%d", i),
			Age:   rand.Intn(100),
			Score: rand.Float32() * 100,
		}
		//生产结构体串联
		tail.next = &stu	//指向下一个结构体
		tail = &stu			//把当前的结构体给tail,让其继续循环
	}
}


//输出结果如下
{head 28 88 0xc0001144b0}
{stu0 81 94.05091 0xc0001144e0}
{stu1 47 43.77142 0xc000114510}
{stu2 81 68.682304 0xc000114540}
{stu3 25 15.651925 0xc000114570}
{stu4 56 30.091187 0xc0001145a0}
{stu5 94 81.36399 0xc0001145d0}
{stu6 62 38.06572 0xc000114600}
{stu7 28 46.888985 0xc000114630}
{stu8 11 29.310184 0xc000114660}
{stu9 37 21.855305 <nil>}

5. 头部插入节点

方法一

package main

import (
	"fmt"
	"math/rand"
)

type Student struct {
	Name  string
	Age   int
	Score float32
	next  *Student
}

func main() {
	//头部结构体
	var head Student
	head.Name = "head"
	head.Age = 28
	head.Score = 88

	//调用头部插入函数
	HeadInsert(&head)

	Req(HeadInsert(&head))
}

func Req(tmp *Student) {
	for tmp != nil {
		fmt.Println(*tmp)
		tmp = tmp.next
	}
}

func HeadInsert(p *Student) *Student {
	for i := 0; i < 10; i++ {
		var stu = Student{
			Name:  fmt.Sprintf("stu%d", i),
			Age:   rand.Intn(100),
			Score: rand.Float32() * 100,
		}
		//当前新节点指向head,因为head是下一个节点
		stu.next = p //指向下一个节点
		p = &stu     //把当前的结构体给tail,让其继续循环
	}
	return p
}

//输出结果如下
{stu9 85 30.152267 0xc000094840}
{stu8 37 5.912065 0xc000094810}
{stu7 29 7.9453626 0xc0000947e0}
{stu6 87 60.72534 0xc0000947b0}
{stu5 41 2.8303082 0xc000094780}
{stu4 90 69.67192 0xc000094750}
{stu3 87 20.658266 0xc000094720}
{stu2 47 29.708258 0xc0000946f0}
{stu1 28 86.249146 0xc0000946c0}
{stu0 95 36.08714 0xc0000944b0}
{head 28 88 <nil>}

Golang 链表的学习和使用

方法二

使用指针的指针

package main

import (
	"fmt"
	"math/rand"
)

type Student struct {
	Name  string
	Age   int
	Score float32
	next  *Student
}

func main() {
	//头部结构体
	var head *Student = &Student{}
	head.Name = "head"
	head.Age = 28
	head.Score = 88

	//调用头部插入函数
	HeadInsert(&head)

	Req(head)
}

func Req(tmp *Student) {
	for tmp != nil {
		fmt.Println(*tmp)
		tmp = tmp.next
	}
}

func HeadInsert(p **Student) {
	for i := 0; i < 10; i++ {
		var stu = Student{
			Name:  fmt.Sprintf("stu%d", i),
			Age:   rand.Intn(100),
			Score: rand.Float32() * 100,
		}
		//当前新节点指向head,因为head是下一个节点
		stu.next = *p //指向下一个节点
		*p = &stu     //把当前的结构体给tail,让其继续循环
	}
}


//输出结果如下
{stu9 37 21.855305 0xc000114660}
{stu8 11 29.310184 0xc000114630}
{stu7 28 46.888985 0xc000114600}
{stu6 62 38.06572 0xc0001145d0}
{stu5 94 81.36399 0xc0001145a0}
{stu4 56 30.091187 0xc000114570}
{stu3 25 15.651925 0xc000114540}
{stu2 81 68.682304 0xc000114510}
{stu1 47 43.77142 0xc0001144e0}
{stu0 81 94.05091 0xc0001144b0}
{head 28 88 <nil>}

总结

如果想要外部的数据和函数处理结果进行同步,两种方法:

① 传参,传递指针

② return 进行值的返回

6. 指定节点后添加新节点

package main

import (
	"fmt"
	"math/rand"
)

type Student struct {
	Name  string
	Age   int
	Score float32
	next  *Student
}

func main() {
	//头部结构体
	var head *Student = &Student{} //定义指针类型
	head.Name = "head"
	head.Age = 28
	head.Score = 88

	//定义新的节点
	var newNode *Student = &Student{} //定义指针类型
	newNode.Name = "newNode"
	newNode.Age = 19
	newNode.Score = 78
	HeadInsert(&head)

	//指定位置插入函数
	Add(head, newNode)

	Req(head)
}

func Req(tmp *Student) {
	for tmp != nil {
		fmt.Println(*tmp)
		tmp = tmp.next
	}
}

func HeadInsert(p **Student) { //传入指针的指针
	for i := 0; i < 10; i++ {
		var stu = Student{
			Name:  fmt.Sprintf("stu%d", i),
			Age:   rand.Intn(100),
			Score: rand.Float32() * 100,
		}
		//当前新节点指向head,因为head是下一个节点
		stu.next = *p //指向下一个节点
		*p = &stu     //把当前的结构体给tail,让其继续循环
	}
}

//p为当前节点,newnode为插入的节点
func Add(p *Student, newNode *Student) {
	for p != nil {
		if p.Name == "stu6" {
			//对接下一个节点
			newNode.next = p.next
			p.next = newNode
		}
		//插入节点指向下一个节点
		p = p.next //p.next赋予给p,继续进行循环遍历
	}
}


//输出结果如下
{stu9 37 21.855305 0xc0000c0660}
{stu8 11 29.310184 0xc0000c0630}
{stu7 28 46.888985 0xc0000c0600}
{stu6 62 38.06572 0xc0000c04b0}
{newNode 19 78 0xc0000c05d0}
{stu5 94 81.36399 0xc0000c05a0}
{stu4 56 30.091187 0xc0000c0570}
{stu3 25 15.651925 0xc0000c0540}
{stu2 81 68.682304 0xc0000c0510}
{stu1 47 43.77142 0xc0000c04e0}
{stu0 81 94.05091 0xc0000c0480}
{head 28 88 <nil>}

7. 删除节点

package main

import (
	"fmt"
	"math/rand"
)

type Student struct {
	Name  string
	Age   int
	Score float32
	next  *Student
}

func main() {
	//头部结构体
	var head *Student = &Student{} //定义指针类型
	head.Name = "head"
	head.Age = 28
	head.Score = 88

	//定义新的节点
	var newNode *Student = &Student{} //定义指针类型
	newNode.Name = "newNode"
	newNode.Age = 19
	newNode.Score = 78
	HeadInsert(&head)

	//指定位置插入函数
	Add(head, newNode)

	//删除节点
	del(head)

	Req(head)
}

func Req(tmp *Student) {
	for tmp != nil {
		fmt.Println(*tmp)
		tmp = tmp.next
	}
}

func HeadInsert(p **Student) { //传入指针的指针
	for i := 0; i < 10; i++ {
		var stu = Student{
			Name:  fmt.Sprintf("stu%d", i),
			Age:   rand.Intn(100),
			Score: rand.Float32() * 100,
		}
		//当前新节点指向head,因为head是下一个节点
		stu.next = *p //指向下一个节点
		*p = &stu     //把当前的结构体给tail,让其继续循环
	}
}

//p为当前节点,newnode为插入的节点
func Add(p *Student, newNode *Student) {
	for p != nil {
		if p.Name == "stu6" {
			//对接下一个节点
			newNode.next = p.next
			p.next = newNode
		}
		//插入节点指向下一个节点
		p = p.next //p.next赋予给p,继续进行循环遍历
	}
}

//删除节点
func del(p *Student) {
	var prev *Student = p			//p=head   prev=head  ——》prev=p
	for p != nil {
		if p.Name == "newNode" {
			prev.next = p.next
			break
		}
		prev = p			//进行平移,前节点赋值
		p = p.next			//后节点赋值
	}
}
 
 //输出结果如下
 {stu9 37 21.855305 0xc0000c0660}
{stu8 11 29.310184 0xc0000c0630}
{stu7 28 46.888985 0xc0000c0600}
{stu6 62 38.06572 0xc0000c05d0}
{stu5 94 81.36399 0xc0000c05a0}
{stu4 56 30.091187 0xc0000c0570}
{stu3 25 15.651925 0xc0000c0540}
{stu2 81 68.682304 0xc0000c0510}
{stu1 47 43.77142 0xc0000c04e0}
{stu0 81 94.05091 0xc0000c0480}
{head 28 88 <nil>}

以上就是Go语言学习之链表的使用详解的详细内容!

Golang 相关文章推荐
Go语言使用select{}阻塞main函数介绍
Apr 25 Golang
golang在GRPC中设置client的超时时间
Apr 27 Golang
解决Golang time.Parse和time.Format的时区问题
Apr 29 Golang
go语言中GOPATH GOROOT的作用和设置方式
May 05 Golang
go web 预防跨站脚本的实现方式
Jun 11 Golang
go goroutine 怎样进行错误处理
Jul 16 Golang
Go 中的空白标识符下划线
Mar 25 Golang
victoriaMetrics库布隆过滤器初始化及使用详解
Apr 05 Golang
Golang 遍历二叉树
Apr 19 Golang
Golang 入门 之url 包
May 04 Golang
Golang入门之计时器
May 04 Golang
Golang Elasticsearches 批量修改查询及发送MQ
Apr 19 #Golang
GO语言异常处理分析 err接口及defer延迟
Apr 14 #Golang
GO语言字符串处理函数之处理Strings包
Apr 14 #Golang
golang的文件创建及读写操作
Apr 14 #Golang
golang定时器
Apr 14 #Golang
golang用type-switch判断interface的实际存储类型
Apr 14 #Golang
golang语言指针操作
Apr 14 #Golang
You might like
php下几个常用的去空、分组、调试数组函数
2009/02/22 PHP
gearman中任务的优先级和返回状态实例分析
2020/02/27 PHP
jquery api参考 visualjquery 中国线路 速度快
2007/11/30 Javascript
jquery ajax abort()的使用方法
2010/10/28 Javascript
javascript 得到文件后缀名的思路及实现
2020/05/09 Javascript
javascript生成随机数的方法
2014/05/16 Javascript
用javascript将数据导入Excel示例代码
2014/09/10 Javascript
js实现鼠标悬停图片上时滚动文字说明的方法
2015/02/17 Javascript
JS判断字符串包含的方法
2015/05/05 Javascript
通过Tabs方法基于easyUI+bootstrap制作工作站
2016/03/28 Javascript
javascript函数中的3个高级技巧
2016/09/22 Javascript
微信小程序之ES6与事项助手的功能实现
2016/11/30 Javascript
JavaScript字符串检索字符的方法
2017/06/23 Javascript
使用Bootstrap + Vue.js实现表格的动态展示、新增和删除功能
2017/11/27 Javascript
使用express+multer实现node中的图片上传功能
2018/02/02 Javascript
javascript自定义右键菜单插件
2019/12/16 Javascript
[58:15]2018DOTA2亚洲邀请赛 4.1 小组赛 A组 NB vs Liquid
2018/04/02 DOTA
python中requests小技巧
2017/05/10 Python
python利用urllib实现爬取京东网站商品图片的爬虫实例
2017/08/24 Python
python随机数分布random测试
2018/08/27 Python
用python给自己做一款小说阅读器过程详解
2019/07/11 Python
PyCharm中代码字体大小调整方法
2019/07/29 Python
基于python进行抽样分布描述及实践详解
2019/09/02 Python
keras 获取某层输出 获取复用层的多次输出实例
2020/05/23 Python
Python Map 函数的使用
2020/08/28 Python
实例讲解CSS3中的border-radius属性
2015/08/18 HTML / CSS
iRobot官网:改变生活的家用机器人品牌
2016/09/20 全球购物
魅力教师事迹材料
2014/01/10 职场文书
石油工程专业毕业生求职信
2014/04/13 职场文书
彩色的翅膀教学反思
2014/04/25 职场文书
保护环境演讲稿
2014/05/10 职场文书
教师批评与自我批评心得体会
2014/10/16 职场文书
政风行风评议工作总结
2014/10/21 职场文书
学校办公室主任岗位职责
2015/04/01 职场文书
2015年学校安全工作总结
2015/04/22 职场文书
JS前端宏任务微任务及Event Loop使用详解
2022/07/23 Javascript