GO语言数据结构之链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
单向链表
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以节点来表示的,每个结点的构成:数据 + 后续元素位置指针。
单项链表代码实现
package main
import "fmt"
//定义链表节点结构
type singleNode struct {
no int
name string
next *singleNode //指向下一个数据地址,没有则为nil
}
//定义链表结构
type singLinked struct {
head *singleNode // 链表头数据
tail *singleNode // 链表尾数据
size int //长度
}
// InsertTail 从尾部插入数据
func (singLinked *singLinked) InsertTail(linked *singleNode) {
//数据校验
if linked == nil {
fmt.Println("数据为空")
return
}
//若链表长度为0
if singLinked.size == 0 {
//直接将数据插入头
singLinked.head = linked
singLinked.tail = linked
//最后一个节点next为nil
linked.next = nil
} else {
//如果存在有数据,将数据插入到上一个数据的尾部
singLinked.tail.next = linked
//并将插入的数据next置为nil
linked.next = nil
//尾部数据录入
singLinked.tail = linked
}
singLinked.size++
}
// InterHead 从头部插入数据
func (singLinked *singLinked) InterHead(linked *singleNode) {
//数据校验
if linked == nil {
fmt.Println("数据为空")
return
}
//判断链表内是否存在数据
if singLinked.size == 0 {
singLinked.head = linked
singLinked.tail = linked
linked.next = nil
} else {
//如果有数据
head := singLinked.head
linked.next = head
singLinked.head = linked
}
singLinked.size++
}
// InterNumber 根据编号大小进行插入排序
func (singLinked *singLinked) InterNumber(linked *singleNode) {
//数据校验
if linked == nil {
fmt.Println("数据为空")
return
}
//创建辅助数据
temp := singLinked.head
for {
if singLinked.size == 0 {
//链表为空直接插入
singLinked.head = linked
singLinked.tail = linked
linked.next = nil
singLinked.size++
break
} else if temp.next == nil {
//标识链表已到最后
//表示最后一位了
temp.next = linked
singLinked.tail = linked
singLinked.size++
break
} else if temp.no == linked.no {
//当前节点的下一个节点编号大于需要插入的数据编号
fmt.Println("编号冲突")
break
} else if linked.no < singLinked.head.no {
//判断是否需要插入头节点
linked.next = singLinked.head
singLinked.head = linked
singLinked.size++
break
} else if temp.next.no > linked.no {
//判断当前的数据编号是否比插入数据的编号大
linked.next = temp.next
temp.next = linked
singLinked.size++
break
}
//循环
temp = temp.next
}
}
// GetHead 获取头节点
func (singLinked *singLinked) GetHead() *singleNode {
return singLinked.head
}
// Print 遍历读取数据操作
func (singLinked *singLinked) Print() {
fmt.Printf("当前链表内有%v个数据\n", singLinked.size)
if singLinked.size == 0 {
fmt.Println("链表为空")
return
}
head := singLinked.GetHead()
for head != nil {
fmt.Printf("编号:%v 名称:%v\n", head.no, head.name)
head = head.next
}
}
// DeleteNo 根据编号删除节点
func (singLinked *singLinked) DeleteNo(id int) {
//数据校验
if singLinked.size == 0 {
fmt.Println("数据异常")
return
}
//创建辅助节点
temp := singLinked.head
for {
if singLinked.head.no == id {
//是头节点,删除头结点
singLinked.head = singLinked.head.next
singLinked.size--
break
} else if temp.next.no == id {
//不是头节点,删除节点数据
temp.next = temp.next.next
singLinked.size--
break
} else if temp.next == nil {
//已经到最后了,并未找到该编号节点
fmt.Println("未找到数据")
break
}
temp = temp.next
}
}
// Inquire 根据编号查询节点
func (singLinked *singLinked) Inquire(id int) (singleNode *singleNode) {
//数据校验
if singLinked.size == 0 {
fmt.Println("数据异常")
return
}
//创建辅助节点
temp := singLinked.head
for {
if singLinked.head.no == id {
//是头节点返回头结点
fmt.Println()
singleNode = singLinked.head
break
} else if temp.next.no == id {
//不是头节点,删除节点数据
singleNode = temp.next
break
} else if temp.next == nil {
//已经到最后了,并未找到该编号节点
fmt.Println("未找到数据")
break
}
temp = temp.next
}
return
}
// Modification 修改指定id节点
func (singLinked *singLinked) Modification(id int, singleNode *singleNode) {
//数据校验
if singLinked.size == 0 || singleNode == nil {
fmt.Println("数据异常")
return
}
//创建辅助节点
temp := singLinked.head
for {
if temp.next == nil {
//已经到最后了,并未找到该编号节点
fmt.Println("未找到数据")
break
} else if singLinked.head.no == id {
//是头结点
singLinked.head.name = singleNode.name
break
} else if temp.next.no == id {
temp.next.name = singleNode.name
break
}
temp = temp.next
}
return
}
// Destroy 链表销毁
func (singLinked *singLinked) Destroy() {
singLinked.head = nil
singLinked.tail = nil
singLinked.size = 0
}
//主函数
func main() {
//初始化
linked := singLinked{}
fmt.Println("==============尾插数据分割线===============")
//添加操作
linked.InsertTail(&singleNode{
no: 7,
name: "娃哈哈",
})
linked.InsertTail(&singleNode{
no: 8,
name: "营养快线",
next: nil,
})
linked.Print()
fmt.Println("==============编号插入排序分割线===============")
linked.InterNumber(&singleNode{
no: 1,
name: "康师傅",
next: nil,
})
linked.InterNumber(&singleNode{
no: 9,
name: "康师傅",
next: nil,
})
linked.Print()
fmt.Println("==============根据编号查询分割线===============")
inquire := linked.Inquire(8)
fmt.Println(inquire)
fmt.Println("==============修改数据分割线===============")
linked.Modification(8, &singleNode{name: "嘻嘻哈哈"})
linked.Print()
fmt.Println("==============根据id删除数据分割线===============")
linked.DeleteNo(9)
linked.DeleteNo(1)
linked.DeleteNo(8)
linked.DeleteNo(7)
linked.Print()
}
双向链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
双向链表代码实现
package main
import "fmt"
// Node 节点结构
type Node struct {
no int
name string
front *Node //前节点
queen *Node //后节点
}
// Linked 链表结构
type Linked struct {
size int //节点个数
head *Node //头节点
trail *Node //尾结点
}
// InsertTrail 尾插法
func (linked *Linked) InsertTrail(node *Node) {
//数据校验
if node == nil {
fmt.Println("数据异常")
return
}
temp := linked.head
for true {
if temp.queen == nil {
break
}
temp = temp.queen
}
//找到最后一个节点将新节点插入最后
temp.queen = node
node.front = temp
linked.size++
}
// InsertNumber 按照编号大小顺序插入
func (linked *Linked) InsertNumber(node *Node) {
//数据校验
if node == nil {
fmt.Println("数据异常")
return
}
temp := linked.head
for true {
if temp.queen == nil {
//遍历到最后,数据应插入最后
temp.queen = node
linked.trail = node
linked.size++
break
} else if temp.no == node.no {
fmt.Printf("数据ID冲突")
break
} else if temp.queen.no > node.no {
//下一个节点的id比插入节点id大
//插入逻辑,先将插入节点上指针和下指针分别指向上下节点再进行上下节点关联新增节点步骤
node.queen = temp.queen
node.front = temp
//判断是否还有下个节点
if temp.queen != nil {
temp.queen.front = node
}
temp.queen = node
linked.size++
break
}
temp = temp.queen
}
}
// DeleteNode 删除节点根据id删除
func (linked *Linked) DeleteNode(id int) {
temp := linked.head
for true {
if temp.queen == nil {
fmt.Println("未找到该id节点")
break
} else if temp.queen.no == id {
temp.queen = temp.queen.queen
//判断是否还有下个节点
if temp.queen != nil {
temp.queen.front = temp
}
linked.size--
break
}
temp = temp.queen
}
}
// InquireLinked 查询链表所有数据
func (linked *Linked) InquireLinked() {
fmt.Printf("当前链表内有%v个数据\n", linked.size)
if linked.size == 0 {
fmt.Println("链表为空")
}
temp := linked.head
for temp.queen != nil {
fmt.Printf("编号:%v 名称:%v\n", temp.queen.no, temp.queen.name)
temp = temp.queen
}
}
// InversePrinting 倒序打印
func (linked *Linked) InversePrinting() {
if linked.size == 0 {
fmt.Println("链表为空")
}
temp := linked.trail
for true {
fmt.Printf("编号:%v 名称:%v\n", temp.no, temp.name)
temp = temp.front
if temp.front == nil {
break
}
}
}
func main() {
//初始化
linked := Linked{
head: &Node{
no: 0,
name: "头结点",
},
trail: nil,
}
linked.InsertTrail(&Node{
no: 2,
name: "王老吉",
})
linked.InsertNumber(
&Node{
no: 4,
name: "加多宝",
front: nil,
queen: nil,
})
linked.InsertNumber(
&Node{
no: 3,
name: "可口可乐",
front: nil,
queen: nil,
})
linked.InsertNumber(
&Node{
no: 1,
name: "雪碧",
front: nil,
queen: nil,
})
linked.DeleteNode(5)
linked.InquireLinked()
fmt.Println("=========分割线===========")
linked.InversePrinting()
}
环形链表
循环链表是一种特殊的单链表。循环跟单链表唯一的区别就在尾结点。单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。
环形链表也可以解决约瑟夫问题
环形链表代码实现
package main
import "fmt"
//定义节点结构体
type Node struct {
no int
name string
next *Node //下一个节点
}
// AnnularLinked 环形链表结构
type AnnularLinked struct {
size int
head *Node //头结点
tail *Node // 尾结点
}
// Insertion 添加数据
func (linked *AnnularLinked) Insertion(node *Node) {
//判断链表是否有数据
if linked.size == 0 {
linked.head = node
linked.head.next = node
linked.tail = node
linked.size++
return
}
//如果有数据
linked.tail.next = node
node.next = linked.head
linked.tail = node
linked.size++
}
// Print 打印循环链表
func (linked *AnnularLinked) Print() {
fmt.Printf("当前链表内有%v个数据\n", linked.size)
if linked.size == 0 {
fmt.Println("链表为空")
return
}
temp := linked.head
for {
fmt.Printf("编号:%v 名称:%v\n", temp.no, temp.name)
if temp.next == linked.head {
break
}
temp = temp.next
}
}
// DeleteNode 删除某个节点
func (linked *AnnularLinked) DeleteNode(id int) {
//判断链表是否为空
if linked.size == 0 {
fmt.Println("空链表,无法执行删除")
return
}
//判断如果是删除头节点
if linked.head.no == id {
linked.tail.next = linked.head.next
linked.head = linked.head.next
linked.size--
return
}
temp := linked.head
for true {
if temp.next.no == id {
//如果删除的是尾节点
if temp.next == linked.tail {
linked.tail = temp
}
temp.next = temp.next.next
linked.size--
break
} else if temp.next == linked.head {
fmt.Println("未找到该id节点")
break
}
temp = temp.next
}
}
func main() {
//初始化
linked := AnnularLinked{
size: 0,
head: &Node{},
tail: nil,
}
linked.Insertion(&Node{
no: 1,
name: "嘻嘻嘻",
next: nil,
})
linked.Insertion(&Node{
no: 2,
name: "嘻嘻嘻",
next: nil,
})
linked.Insertion(&Node{
no: 3,
name: "嘻嘻嘻",
next: nil,
})
linked.Insertion(&Node{
no: 4,
name: "嘻嘻嘻",
next: nil,
})
linked.DeleteNode(2)
linked.DeleteNode(1)
linked.DeleteNode(3)
linked.DeleteNode(4)
linked.Print()
}
本文借鉴:通俗易懂讲解 链表 - 知乎 (zhihu.com)
尚硅谷Java数据结构与java算法(Java数据结构与算法)_哔哩哔哩_bilibili
GO语言数据结构之链表的更多相关文章
- C语言数据结构-单链表的实现-初始化、销毁、长度、查找、前驱、后继、插入、删除、显示操作
1.数据结构-单链表的实现-C语言 typedef struct LNode { int data; struct LNode* next; } LNode,*LinkList; //这两者等价.Li ...
- C语言数据结构-创建链表的四种方法
结点类型: typedef int datatype; typedef struct NODE{ datatype data; struct NODE *next; }Node,*LinkList; ...
- C语言实现单链表-03版
在C语言实现单链表-02版中我们只是简单的更新一下链表的组织方式: 它没有更多的更新功能,因此我们这个版本将要完成如下功能: Problem 1,搜索相关节点: 2,前插节点: 3,后追加节点: 4, ...
- linux内核数据结构之链表
linux内核数据结构之链表 1.前言 最近写代码需用到链表结构,正好公共库有关于链表的.第一眼看时,觉得有点新鲜,和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域.后来看代码注释发现该 ...
- Java描述数据结构之链表的增删改查
链表是一种常见的基础数据结构,它是一种线性表,但在内存中它并不是顺序存储的,它是以链式进行存储的,每一个节点里存放的是下一个节点的"指针".在Java中的数据分为引用数据类型和基础 ...
- C/C++语言实现单链表(带头结点)
彻底理解链表中为何使用二级指针或者一级指针的引用 数据结构之链表-链表实现及常用操作(C++篇) C语言实现单链表,主要功能为空链表创建,链表初始化(头插法),链表元素读取,按位置插入,(有序链表)按 ...
- 深入理解Redis 数据结构—双链表
在 Redis 数据类型中的列表list,对数据的添加和删除常用的命令有 lpush,rpush,lpop,rpop,其中 l 表示在左侧,r 表示在右侧,可以在左右两侧做添加和删除操作,说明这是一个 ...
- 学习javascript数据结构(二)——链表
前言 人生总是直向前行走,从不留下什么. 原文地址:学习javascript数据结构(二)--链表 博主博客地址:Damonare的个人博客 正文 链表简介 上一篇博客-学习javascript数据结 ...
- C语言实现单链表-02版
我们在C语言实现单链表-01版中实现的链表非常简单: 但是它对于理解单链表是非常有帮助的,至少我就是这样认为的: 简单的不能再简单的东西没那么实用,所以我们接下来要大规模的修改啦: Problem 1 ...
随机推荐
- 用Fiddler抓不到https的包?因为你姿势不对!往这看!
前言 刚入行测试的小伙伴可能不知道,Fiddler默认抓http的包,如果要抓https的包,是需要装证书的!什么鬼证书?不明白的话继续往下看. Fiddler 抓取 https 数据 第一步:下载 ...
- java 从零开始手写 RPC (01) 基于 websocket 实现
RPC 解决的问题 RPC 主要是为了解决的两个问题: 解决分布式系统中,服务之间的调用问题. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑. 这一节我们来学习下如何基于 we ...
- Xcode相关
Xcode相关的路径 Provisioning Profiles存放路径:~/Library/MobileDevice/Provisioning Profiles 所有模拟器(包括历史模拟器):~/L ...
- uoj21 缩进优化(整除分块,乱搞)
题目大意: 给定一个长度为\(n\)的序列 让你找一个\(x\),使得\(ans\)尽可能小 其中$$ans=\sum_{i=1}^{n}\lfloor\frac{a_i}{x}\rfloor + \ ...
- Mybatis一级缓存的锅
问题背景 项目开发中有一个树形数据结构,不像经典组织结构树.菜单级别树,我们这个树形结构是用户后期手动建立起来的关系.因此数据库表结构为两张表:数据记录表.记录关系表,通过业务规则限制,形成的树形结构 ...
- VS Code Just My Code Debugging
VS Code Just My Code Debugging VS Code for C++ doesn't support Just My Code Refer here: Add support ...
- python画图的工具及网站
①Gallery - Matplotlib 3.4.3 documentation 学会模仿并超越 ②Examples - Apache ECharts js网页端动态展示 ③WEB色見本 原色大辞典 ...
- Sharding-JDBC基本使用,整合Springboot实现分库分表,读写分离
结合上一篇docker部署的mysql主从, 本篇主要讲解SpringBoot项目结合Sharding-JDBC如何实现分库分表.读写分离. 一.Sharding-JDBC介绍 1.这里引用官网上的介 ...
- windows下安装dirmap详细教程
今天安装一下dirmap,纯小白非常详细的安装过程 1.先去下载dirmap 下载地址:https://github.com/H4ckForJob/dirmap 点这个绿色的code,然后再点下面这个 ...
- Spring Boot 2.5.0 重新设计的spring.sql.init 配置有何用?
前几天Spring Boot 2.5.0发布了,其中提到了关于Datasource初始化机制的调整,有读者私信想了解这方面做了什么调整.那么今天就要详细说说这个重新设计的配置内容,并结合实际情况说说我 ...