Go数据结构与算法—链表

Posted 小圣.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go数据结构与算法—链表相关的知识,希望对你有一定的参考价值。

介绍

链表是一种物理存储单元上的非连续、非顺序的存储结构。链表由一系列节点(链表中的每一个元素)组成。节点可以在运行时动态生成。每个节点包括两个部分:存储数据元素的数据域、存储下一个节点的指针域。 相比于线性表的顺序结构,链表操作复杂。但是由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

单链表

type HeroNode struct {
	Id       int
	Name     string
	NickName string
	Next     *HeroNode
}

// 单链表的插入
func Insert(head *HeroNode, newNode *HeroNode) {
	temp := head
	for {
		if temp.Next == nil {
			break
		}
		temp = temp.Next
	}

	temp.Next = newNode
}

// 按顺序插入
func InsertByOrder(head *HeroNode, newNode *HeroNode) {
	temp := head
	flag := true
	for {
		// 已到链表的最后
		if temp.Next == nil {
			break
		} else if temp.Next.Id > newNode.Id { // 已经找到第一个比当前ID大的节点
			break
		} else if temp.Next.Id == newNode.Id { // 已存在相同ID的节点
			flag = false
			break
		}
		temp = temp.Next
	}

	if flag {
		newNode.Next = temp.Next
		temp.Next = newNode
	} else {
		fmt.Println("已存在相同的节点")
		return
	}
}

// 单链表的删除
func Delete(head *HeroNode, id int) {
	temp := head
	for {
		if temp.Next.Id == id {
			break
		}
		temp = temp.Next
	}
	temp.Next = temp.Next.Next
}

// 显示所有的节点信息
func List(head *HeroNode) {
	temp := head
	for {
		if temp.Next == nil {
			break
		}
		temp = temp.Next
		fmt.Printf("[%d, %s, %s]\\n", temp.Id, temp.Name, temp.NickName)

	}
}

// 测试
func main() {

	head := &HeroNode{}

	hero1 := &HeroNode{
		Id:       1,
		Name:     "宋江",
		NickName: "及时雨",
	}

	hero2 := &HeroNode{
		Id:       2,
		Name:     "卢俊义",
		NickName: "玉麒麟",
	}

	hero3 := &HeroNode{
		Id:       3,
		Name:     "林冲",
		NickName: "豹子头",
	}

	// Insert(head, hero1)
	// Insert(head, hero3)
	// Insert(head, hero2)
	InsertByOrder(head, hero1)
	InsertByOrder(head, hero3)
	InsertByOrder(head, hero2)
	List(head)
	Delete(head, 1)
	List(head)
}

循环链表

对于单链表,由于每个节点只存储了向后节点的指针,到了尾标志就停止了向后链的操作,这样,其中的节点无法找到它的前驱节点了。
将单链表中的尾节点的指针指向头节点,就可以使整个单链表形成一个环。这种头尾相接的单链表称为单循环链表(circle linked list)

type CatNode struct {
	Id   int
	Name string
	Next *CatNode
}

func Insert(head *CatNode, newNode *CatNode) {

	// 第一次添加
	if head.Next == nil {
		head.Id = newNode.Id
		head.Name = newNode.Name
		head.Next = head
		return
	}

	temp := head
	// 找到最后一个节点
	for {
		if temp.Next == head {
			break
		}
		temp = temp.Next
	}

	// 加入到链表中
	temp.Next = newNode
	newNode.Next = head
}

// 遍历环形链表
func List(head *CatNode) {
	temp := head
	if temp.Next == nil {
		fmt.Println("the linked is empty")
		return
	}

	for {
		fmt.Println("猫的名称:", temp.Name)
		if temp.Next == head {
			break
		}
		temp = temp.Next
	}
}

// 删除
func Delete(head *CatNode, id int) *CatNode {
	temp := head
	helper := head

	if temp.Next == nil {
		fmt.Println("the linked is empty")
		return head
	}

	// 如果链表只有一个节点
	if temp.Next == head {
		if temp.Id == id {
			temp.Next = nil
		}
		return head
	}
	// 把helper放到链表的最后
	for {
		if helper.Next == head {
			break
		}
		helper = helper.Next
	}

	// 删除指定的节点
	flag := true
	for {
		if temp.Next == head { // 最后一个节点,还没找到
			break
		}
		if temp.Id == id { // 找到了指定的节点,进行删除
			if temp == head { // 删除的是头节点
				head = head.Next
			}
			helper.Next = temp.Next
			fmt.Println("被删除的小猫:", id)
			flag = false
			break
		}
		temp = temp.Next
		helper = helper.Next
	}

	if flag {
		if temp.Id == id {
			helper.Next = temp.Next
			fmt.Println("被删除的小猫:", id)
		} else {
			fmt.Println("没有这个猫")
		}
	}

	return head
}

双链表

在单链表中,每个节点只有一个指向后继节点的链。若要查找前驱节点,则必须从单链表的头指针开始沿着链表方向逐个检测,操作效率低,这时候就需要采用双链表了。

双链表(doubly linked list)是每个节点有两个地址域的线性链表,两个地址域分别指向前驱节点和后继结点。

type HeroNode struct {
	Id       int
	Name     string
	NickName string
	Prev     *HeroNode
	Next     *HeroNode
}

// 插入
func Insert(head *HeroNode, newNode *HeroNode) {
	temp := head
	for {
		if temp.Next == nil {
			break
		}
		temp = temp.Next
	}
	temp.Next = newNode
	newNode.Prev = temp
}

// 按照id的大小进行顺序插入
func InsertByOrder(head *HeroNode, newNode *HeroNode) {
	temp := head
	flag := true
	for {
		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.Prev = temp
		if temp.Next != nil {
			temp.Next.Prev = newNode
		}
		temp.Next = newNode
	} else {
		fmt.Println("链表已存在该节点")
		return
	}
}

// 删除
func Delete(head *HeroNode, id int) {
	temp := head
	for {
		if temp.Next.Id == id {
			break
		}
		temp = temp.Next
	}

	temp.Next = temp.Next.Next
	// 当要删除的的节点不是最后一个时进行下面的处理
	if temp.Next != nil {
		temp.Next.Prev = temp
	}
}

// 显示所有的节点
func List(head *HeroNode) {
	temp := head
	for {
		if temp.Next == nil {
			break
		}
		temp = temp.Next
		fmt.Printf("[%d, %s, %s]\\n", temp.Id, temp.Name, temp.NickName)
	}
}

Josephus问题解决

问题描述:设标号为1,2…, n的n个小孩围坐在一圈,约定编号为k(1<=k<=n)的小孩从1开始报数,数到m的小孩出列,它的下一位又开始从1开始报数,数到m的小孩又出列,以此类推,直到所有小孩出列为止,求出由此产生一个出队编号的序列。

// 小孩的结构体
type Boy struct {
	No   int
	next *Boy
}

// num:表示小孩的个数
// *Boy:返回该环形链表的第一个小孩的指针
func AddBoy(num int) *Boy {
	first := &Boy{}
	currentBoy := &Boy{} // 辅助指针
	if num < 1 {
		fmt.Println("number is uncorrect")
		return first
	}

	// 循环构建链表
	for i := 1; i <= num; i++ {
		boy := &Boy{
			No: i,
		}
		// 第一个小孩
		if i == 1 {
			// 第一个小孩的头指针不能动,否则后面的节点就无法找到第一个小孩的位置
			first = boy
			currentBoy = boy
			currentBoy.next = first // 形成循环
		} else {
			currentBoy.next = boy
			currentBoy = boy
			currentBoy.next = first
		}
	}
	return first
}

// 显示单向的环形链表
func List(first *Boy) {
	// 处理空的环形链表
	if first.next == nil {
		fmt.Println("链表为空")
		return
	}

	currentBoy := first
	for {
		fmt.Printf("小孩编号:%d\\n", currentBoy.No)
		// 退出条件
		if currentBoy.next == first {
			break
		}
		currentBoy = currentBoy.next
	}
}

// 按照要求,在环形链表中留下最后一个人
func palyGame(first *Boy, startNo int, countNum int) {

	// 空链表处理
	if first.next == nil {
		fmt.Println("null")
		return
	}
	// 辅助指针,帮助删除小孩
	tail := first
	// 让tail指向环形链表的最后一个节点`
	for {
		// 到了最后一个小孩了
		if tail.next == first {
			break
		}
		tail = tail.next
	}
	// 让first移动到startNo
	for i := 0; i < startNo-1; i++ {
		first = first.next
		tail = tail.next
	}

	// 开始数countNum下,然后删除first指向的小孩
	for {
		// 开始数countNUm-1下,自己也要数
		for i := 0; i < countNum-1; i++ {
			first = first.next
			tail = tail.next
		}
		fmt.Println("出圈小孩为:", first.No)
		// 删除first指向的节点
		first = first.next
		tail.next = first

		// 剩下最后一个小孩不删除
		if tail == first {
			break
		}
	}
	fmt.Printf("%d小孩编号\\n", first.No)
}

// 测试
func main() {
	first := AddBoy(5)
	palyGame(first, 2, 3)
}

最后

完整代码在这里:https://github.com/bigzoro/go_algorithm/tree/main/linkedList

以上是关于Go数据结构与算法—链表的主要内容,如果未能解决你的问题,请参考以下文章

Go基础加密算法和数据结构

Go基础加密算法和数据结构

数据结构与算法6-链表下

数据结构与算法什么是链表?并用代码手动实现一个单向链表

数据结构与算法链表(下)

Go-Golang学习总结笔记