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 ...
随机推荐
- 好久没发文了,一篇Vue3的Composition API使用奉上
Composition API Composition API是Vue3中推荐的组件代码书写方式,相较于传统的Options API来说,它能让业务逻辑处理和后期代码维护变的更加简单. 首先我们来看O ...
- Monte-carlo-simulation
https://towardsdatascience.com/how-to-use-monte-carlo-simulation-to-help-decision-making-a0a164bc861 ...
- 【C++ Primer Plus】编程练习答案——第12章
1 // chapter12_1_cow.h 2 3 4 #ifndef LEARN_CPP_CHAPTER12_1_COW_H 5 #define LEARN_CPP_CHAPTER12_1_COW ...
- 10.1 HTTP
1.跨网络的主机间通讯 套接字Socket是进程间通信IPC的一种实现,允许位于不同主机(或同一主机)上不同进程之间通信和数据交换 在建立通信连接的每一端,进程间的传输要有两个标志:IP地址和端口号, ...
- 从源码解析Electron的安装为什么这么慢
前言 Electron作为一款跨平台的桌面应用端解决方案已经风靡全球.作为开发者,我们几乎不用关心与操作系统的交互,直接通过Web前端技术与Electron提供的API就可以完成桌面应用端的开发. 然 ...
- 题解 Yet Another Number Sequence
题目传送门 Description 给出 \(n,k\) ,求出: \[\sum_{i=1}^{n} f_i·i^k \] 其中 \(f_i\) 表示斐波拉契第 \(i\) 项.\(n\le 10^{ ...
- Java(19)接口知识及综合案例
作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201629.html 博客主页:https://www.cnblogs.com/testero ...
- Hash窃取与传递
Hash窃取与传递 NTHASH(NTLM) 在 Windows中, 存储的密码Hash就叫做 NTHash,也叫做 NTLM,其中NTLM 全称是 "NT LAN Manager" ...
- Visual Studio Debug only user code with Just My Code
Debug only user code with Just My Code By default, the debugger skips over non-user code (if you wan ...
- 初学python-day9 函数1(已更新)
函数 一.函数基础 1.什么是函数 在一个完整的项目中,某些功能会被重复使用,那么会将代码段封装成函数,当我们要使用的时候,直接调用即可. 函数是可以实现一定的小程序或者功能. 优点: 增加了代码的重 ...