IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> Go数据结构与算法之链表 -> 正文阅读

[数据结构与算法]Go数据结构与算法之链表

概述

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的 “指针” 将一组零散的内存卡串联起来实现的。

单向链表

单向链表其中有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。我们习惯性地把第一个结点叫作头结点,把最后一个结点叫作尾结点。其中,头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是链表上最后一个结点。

在这里插入图片描述

插入节点

在这里插入图片描述

删除节点

在这里插入图片描述

代码实现

package main

import "fmt"

type Node struct {
	id   int
	name string
	next *Node // 指向下一个节点
}

// 往链表中插入一个节点
func InsertNode(headNode *Node, newNode *Node) {
	// 创建一个辅助节点
	temp := headNode
	for {
		if temp.next == nil {
			break
		}
		temp = temp.next // 不断的指向下一个结点
	}
	temp.next = newNode
}

// 显示链表的所有结点信息
func ShowNode(headNode *Node) {

	temp := headNode

	// 判断该链表是不是一个空的链表
	if temp.next == nil {
		fmt.Println("空空如也...")
		return
	}

	for {
		fmt.Println(temp.next.id, temp.next.name)
		// 判断是否到链表最后
		temp = temp.next
		if temp.next == nil {
			break
		}
	}
}

// 删除链表指定的节点信息
func DelNode(headNode *Node, id int) {

	temp := headNode
	flag := false

	for {
		if temp.next == nil {
			break
		} else if temp.next.id == id {
			flag = true
			break
		}
		temp = temp.next
	}

	if flag {
		temp.next = temp.next.next
	} else {
		fmt.Println("ID不存在")
	}
}

func main() {
	// 创建一个头结点
	headNode := &Node{}

	// 创建一个新的 Node
	node1 := &Node{
		id:   1,
		name: "aa",
	}

	node2 := &Node{
		id:   2,
		name: "bb",
	}

	node3 := &Node{
		id:   3,
		name: "cc",
	}

	InsertNode(headNode, node1)
	InsertNode(headNode, node2)
	InsertNode(headNode, node3)

	DelNode(headNode, 2) // 删除指定ID

	ShowNode(headNode)

	/*输出结果:
	1 aa
	3 cc*/
}

目前往链表中插入默认是不排序的,我们可以实现根据ID来排序,代码修改如下:

// 往链表中插入一个节点(根据ID排序)
func InsertNodeOrderByID(headNode *Node, newNode *Node) {
	// 创建一个辅助节点
	temp := headNode
	flag := true
	for {
		// 让插入的结点的 no 和 temp 的下一个结点的 no 比较
		if temp.next == nil {
			break
		} else if temp.next.id > newNode.id {
			break
		} else if temp.next.id == newNode.id {
			flag = false
			break
		}
		temp = temp.next // 不断的指向下一个结点
	}

	if flag {
		newNode.next = temp.next
		temp.next = newNode
	} else {
		fmt.Println("ID已存在")
		return
	}
}

双向链表

双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。

在这里插入图片描述

插入节点

在这里插入图片描述

删除节点

在这里插入图片描述

代码实现

package main

import "fmt"

type Node struct {
	id   int
	name string
	pre  *Node // 指向前一个结点
	next *Node // 指向下一个结点
}

// 往双向链表中插入一个节点
func InsertNode(headNode *Node, newHeroNode *Node) {

	temp := headNode

	for {
		if temp.next == nil {
			break
		}
		temp = temp.next
	}

	temp.next = newHeroNode
	newHeroNode.pre = temp
}

// 往双向链表中插入一个节点(根据ID排序)
func InsertNodeOrderByID(headNode *Node, newNode *Node) {
	// 创建一个辅助节点
	temp := headNode
	flag := true
	for {
		// 让插入的结点的 no 和 temp 的下一个结点的 no 比较
		if temp.next == nil {
			break
		} else if temp.next.id > newNode.id {
			break
		} else if temp.next.id == newNode.id {
			flag = false
			break
		}
		temp = temp.next // 不断的指向下一个结点
	}

	if flag {
		newNode.next = temp.next
		newNode.pre = temp

		if temp.next != nil {
			temp.next.pre = newNode
		}

		temp.next = newNode
	} else {
		fmt.Println("ID已存在")
		return
	}
}

// 显示链表的所有结点信息
func ShowNode(headNode *Node) {

	temp := headNode

	// 判断该链表是不是一个空的链表
	if temp.next == nil {
		fmt.Println("空空如也...")
		return
	}

	for {
		fmt.Println(temp.next.id, temp.next.name)
		// 判断是否到链表最后
		temp = temp.next
		if temp.next == nil {
			break
		}
	}
}

// 删除双向链表指定的节点信息
func DelNode(headNode *Node, id int) {

	temp := headNode
	flag := false

	for {
		if temp.next == nil {
			break
		} else if temp.next.id == id {
			flag = true
			break
		}
		temp = temp.next
	}

	if flag {
		temp.next = temp.next.next
		if temp.next != nil {
			temp.next.pre = temp
		}
	} else {
		fmt.Println("ID不存在")
	}
}

func main() {
	// 创建一个头结点
	headNode := &Node{}

	// 创建一个新的 Node
	node1 := &Node{
		id:   1,
		name: "aa",
	}

	node2 := &Node{
		id:   2,
		name: "bb",
	}

	node3 := &Node{
		id:   3,
		name: "cc",
	}

	InsertNodeOrderByID(headNode, node3)
	InsertNodeOrderByID(headNode, node1)
	InsertNodeOrderByID(headNode, node2)

	DelNode(headNode, 2) // 删除指定ID

	ShowNode(headNode)

	/*输出结果:
	1 aa
	3 cc*/
}

环形链表

环形链表是一种特殊的单链表。实际上,环形链表也很简单。它跟单链表唯一的区别就在尾结点。我们知道,单链表的尾结点指针指向空地址,表示这就是最后的结点了。而环形链表的尾结点指针是指向链表的头结点。

在这里插入图片描述

插入节点

在这里插入图片描述

删除节点

在这里插入图片描述

代码实现

package main

import "fmt"

type Node struct {
	id   int
	name string
	next *Node
}

// 往环形链表中插入一个节点
func InsertNode(headNode *Node, newNode *Node) {

	// 判断是否第一个节点
	if headNode.next == nil {
		headNode.id = newNode.id
		headNode.name = newNode.name
		headNode.next = headNode // 构成一个环状
		return
	}

	// 定义一个临时变量,帮忙找到环形的最后节点
	temp := headNode
	for {
		if temp.next == headNode {
			break
		}
		temp = temp.next
	}

	// 加入到链表中
	temp.next = newNode
	newNode.next = headNode
}

// 显示链表的所有结点信息
func ShowNode(headNode *Node) {

	temp := headNode
	if temp.next == nil {
		fmt.Println("空空如也...")
		return
	}

	for {
		fmt.Println(temp.id, temp.name)
		if temp.next == headNode {
			break
		}
		temp = temp.next
	}
}

// 删除环形链表指定的节点信息
func DelNode(headNode *Node, id int) *Node {

	temp := headNode
	helper := headNode

	if temp.next == nil {
		fmt.Println("这是一个空的环形链表,不能删除")
		return headNode
	}

	// 如果只有一个结点
	if temp.next == headNode {
		temp.next = nil
		return headNode
	}

	// 将 helper 定位到链表最后
	for {
		if helper.next == headNode {
			break
		}
		helper = helper.next
	}

	flag := true
	for {
		// 如果到这里,说明比较到最后一个(最后一个还没比较)
		if temp.next == headNode {
			break
		}

		if temp.id == id {
			// 找到了可以直接删除
			helper.next = temp.next
			flag = false
		}

		temp = temp.next
		helper = helper.next
	}

	if flag && temp.id == id {
		helper.next = temp.next
		fmt.Println("删除ID:", id)
	} else {
		fmt.Println("ID不存在")
	}

	return headNode
}

func main() {
	// 创建一个头结点
	headNode := &Node{}

	// 创建一个新的 Node
	node1 := &Node{
		id:   1,
		name: "aa",
	}

	node2 := &Node{
		id:   2,
		name: "bb",
	}

	node3 := &Node{
		id:   3,
		name: "cc",
	}

	InsertNode(headNode, node3)
	InsertNode(headNode, node1)
	InsertNode(headNode, node2)

	DelNode(headNode, 2) // 删除指定ID

	ShowNode(headNode)

	/*输出结果:
	删除ID: 2
	3 cc
	1 aa*/
}

约瑟夫问题

Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 num(1<=num<=n)的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

实现思路

用一个不带头结点的循环链表来处理 Josephu 问题:先构成一个有 n 个结点的单循环链表,然后由 k 结点起从 1 开始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1 开始计数,直到最后一个结点从链表中删除算法结束。

代码实现

package main

import "fmt"

type Boy struct {
	No   int  // 编号
	Next *Boy // 指向下一个小孩的指针
}

// 编写一个函数,构成单向的环形链表
func AddBoy(num int) *Boy {

	first := &Boy{}
	curBoy := &Boy{}

	if num < 1 {
		fmt.Println("num 的值不对")
		return first
	}

	// 循环构建环形链表
	for i := 0; i <= num; i++ {
		boy := &Boy{
			No: i,
		}

		// 因为第一个Boy比较特殊
		if i == 1 {
			first = boy
			curBoy = boy
			curBoy.Next = first
		} else {
			curBoy.Next = boy
			curBoy = boy
			curBoy.Next = first // 构成环形链表
		}
	}

	return first
}

// 显示单向的环形链表
func ShowBoy(first *Boy) {

	// 处理一下如果环形链表为空
	if first.Next == nil {
		fmt.Println("链表为空")
		return
	}

	// 创建一个指针,帮助遍历(至少有一个Boy)
	curBoy := first
	for {
		fmt.Println("Boy No=", curBoy.No)
		if curBoy.Next == first {
			break
		}

		// curBoy移动到下一个
		curBoy = curBoy.Next
	}
}

func PlayGame(first *Boy, startNo int, countNum int) {

	if first.Next == nil {
		fmt.Println("链表为空")
		return
	}

	tail := first
	for {
		// 说明tail到了最后的Boy
		if tail.Next == first {
			break
		}
		tail = tail.Next
	}

	for i := 1; i <= startNo; i++ {
		first = first.Next
		tail = tail.Next
	}

	for {
		for i := 1; i <= countNum-1; i++ {
			first = first.Next
			tail = tail.Next
		}

		fmt.Println("出列的 Boy No=", first.No)

		first = first.Next
		tail.Next = first

		if tail == first {
			break
		}
	}

	fmt.Println("最后出列的 Boy No=", first.No)
}

func main() {
	
	first := AddBoy(30)
	// ShowBoy(first)

	PlayGame(first, 2, 3)
	/*输出结果:
	出列的 Boy No= 5
	出列的 Boy No= 8
	出列的 Boy No= 11
	出列的 Boy No= 14
	出列的 Boy No= 17
	出列的 Boy No= 20
	出列的 Boy No= 23
	出列的 Boy No= 26
	出列的 Boy No= 29
	出列的 Boy No= 2
	出列的 Boy No= 6
	出列的 Boy No= 10
	出列的 Boy No= 15
	出列的 Boy No= 19
	出列的 Boy No= 24
	出列的 Boy No= 28
	出列的 Boy No= 3
	出列的 Boy No= 9
	出列的 Boy No= 16
	出列的 Boy No= 22
	出列的 Boy No= 30
	出列的 Boy No= 7
	出列的 Boy No= 18
	出列的 Boy No= 27
	出列的 Boy No= 12
	出列的 Boy No= 25
	出列的 Boy No= 13
	出列的 Boy No= 4
	出列的 Boy No= 21
	最后出列的 Boy No= 1*/
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-12 16:53:24  更:2021-08-12 16:55:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 4:22:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码