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 相关文章推荐
golang json数组拼接的实例
Apr 28 Golang
golang 接口嵌套实现复用的操作
Apr 29 Golang
goland设置颜色和字体的操作
May 05 Golang
完美解决golang go get私有仓库的问题
May 05 Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 Golang
golang实现一个简单的websocket聊天室功能
Oct 05 Golang
Go语言基础切片的创建及初始化示例详解
Nov 17 Golang
浅谈GO中的Channel以及死锁的造成
Mar 18 Golang
Go获取两个时区的时间差
Apr 20 Golang
Golang入门之计时器
May 04 Golang
Golang并发工具Singleflight
May 06 Golang
Go gorilla securecookie库的安装使用详解
Aug 14 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简单系统查询模块代码打包下载
2008/06/07 PHP
PHP连接SQLServer2005 的问题解决方法
2010/07/19 PHP
php的array_multisort()使用方法介绍
2012/05/16 PHP
php发送与接收流文件的方法
2015/02/11 PHP
Symfony查询方法实例小结
2017/06/28 PHP
Javascript异步表单提交,图片上传,兼容异步模拟ajax技术
2010/05/10 Javascript
初窥JQuery(一)jquery选择符 必备知识点
2010/11/25 Javascript
js的表单操作 简单计算器
2011/12/29 Javascript
jquery 简单应用示例总结
2013/08/09 Javascript
几种设置表单元素中文本输入框不可编辑的方法总结
2013/11/25 Javascript
AMD异步模块定义介绍和Require.js中使用jQuery及jQuery插件的方法
2014/06/06 Javascript
javascript实现iframe框架延时加载的方法
2014/10/30 Javascript
JavaScript获取网页表单提交方式的方法
2015/04/02 Javascript
JavaScript中window.open用法实例详解
2015/04/15 Javascript
简单学习JavaScript中的for语句循环结构
2015/11/10 Javascript
JS中创建函数的三种方式及区别
2016/03/13 Javascript
JS简单实现移动端日历功能示例
2016/12/28 Javascript
js自定义瀑布流布局插件
2017/05/16 Javascript
深入理解Vue transition源码分析
2017/07/30 Javascript
Vue scrollBehavior 滚动行为实现后退页面显示在上次浏览的位置
2019/05/27 Javascript
python实现的一个p2p文件传输实例
2014/06/04 Python
理解Python中的With语句
2015/02/02 Python
python与php实现分割文件代码
2017/03/06 Python
Django 如何获取前端发送的头文件详解(推荐)
2017/08/15 Python
Python模拟自动存取款机的查询、存取款、修改密码等操作
2018/09/02 Python
简单分析python的类变量、实例变量
2019/08/23 Python
python安装sklearn模块的方法详解
2020/11/28 Python
纯CSS3代码实现switch滑动开关按钮效果
2016/08/30 HTML / CSS
HTML5 在canvas中绘制矩形附效果图
2014/06/23 HTML / CSS
资料员的岗位职责
2013/11/20 职场文书
面试后感谢信
2014/02/01 职场文书
人力资源总监工作说明
2014/03/03 职场文书
模具设计与制造专业求职信
2014/07/19 职场文书
MySQL 5.7常见数据类型
2021/07/15 MySQL
jdbc中自带MySQL 连接池实践示例
2022/07/23 MySQL
HttpClient实现文件上传功能
2022/08/14 Java/Android