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语言map与string的相互转换的实现
Apr 07 Golang
golang 实现菜单树的生成方式
Apr 28 Golang
golang 比较浮点数的大小方式
May 02 Golang
Go标准容器之Ring的使用说明
May 05 Golang
Golang 编译成DLL文件的操作
May 06 Golang
浅谈Go语言多态的实现与interface使用
Jun 16 Golang
Go语言设计模式之结构型模式
Jun 22 Golang
Golang表示枚举类型的详细讲解
Sep 04 Golang
Go语言并发编程 sync.Once
Oct 16 Golang
golang的文件创建及读写操作
Apr 14 Golang
实现GO语言对数组切片去重
Apr 20 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 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
玩家交还《星际争霸》原始码光盘 暴雪报以厚礼
2017/05/05 星际争霸
php根据操作系统转换文件名大小写的方法
2014/02/24 PHP
PHP关联数组实现根据元素值删除元素的方法
2015/06/26 PHP
php查询操作实现投票功能
2016/05/09 PHP
PHP实现二维数组去重功能示例
2017/01/12 PHP
PHP实现随机生成水印图片功能
2017/03/22 PHP
实例讲解PHP页面静态化
2018/02/05 PHP
用XMLDOM和ADODB.Stream实现base64编码解码实现代码
2010/11/28 Javascript
jQuery在html有效在jsp无效的原因及解决方法
2013/08/02 Javascript
javaScript中slice函数用法实例分析
2015/06/08 Javascript
jQuery表格行上移下移和置顶的实现方法
2015/10/08 Javascript
Node.JS 循环递归复制文件夹目录及其子文件夹下的所有文件
2017/09/18 Javascript
Node.js之readline模块的使用详解
2019/03/25 Javascript
javascript原型链学习记录之继承实现方式分析
2019/05/01 Javascript
微信小程序如何使用globalData的方法
2019/06/06 Javascript
在vue中使用echars实现上浮与下钻效果
2019/11/08 Javascript
[37:02]OG vs INfamous 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
python实现监控windows服务并自动启动服务示例
2014/04/17 Python
Python学习_几种存取xls/xlsx文件的方法总结
2018/05/03 Python
python 输入一个数n,求n个数求乘或求和的实例
2018/11/13 Python
PyQt5重写QComboBox的鼠标点击事件方法
2019/06/25 Python
python单例模式的应用场景实例讲解
2021/02/24 Python
利用css3制作3D样式按钮实现代码
2013/03/18 HTML / CSS
C++:局部变量能否和全局变量重名
2014/03/03 面试题
材料成型专业个人求职信范文
2013/09/25 职场文书
毕业生文员求职信
2013/11/03 职场文书
学生请假条
2014/04/11 职场文书
2014年生产管理工作总结
2014/12/23 职场文书
三孔导游词
2015/02/05 职场文书
2015年爱牙日活动总结
2015/03/23 职场文书
车间质检员岗位职责
2015/04/08 职场文书
2015年检察院个人工作总结
2015/05/20 职场文书
2015年财务人员个人工作总结
2015/07/27 职场文书
浅谈Redis的几个过期策略
2021/05/27 Redis
MySQL配置主从服务器(一主多从)
2021/08/07 MySQL
Redis如何使用乐观锁(CAS)保证数据一致性
2022/03/25 Redis