Golang(五十一)[线性表-链式存储-链表-环形链表]

是单向链表的特例。将单向链表的尾结点,指向第一个数据结点(而不是指向头结点)

1.初始化

mkdir -p linkList
cd linkList
go mod init linkList
touch linkList.go
vim linkList.go

2.定义环形链表结点结构体

// 定义环形链表的结点结构体
type LinkNode struct {
	// 定义环形链表的数据域
	Data interface{}
	// 定义环形链表指针域
	Next *LinkNode
}

3.创建环形链表

// 创建环形链表
func (node *LinkNode) NewLinkList(data ...interface{}) {
	// 容错处理
	if node == nil || len(data) == 0 {
		return
	}
	// 保存head结点
	head := node
	// 遍历data数据创建新结点
	for _, v := range data {
		// 创建新结点,并初始化
		newNode := new(LinkNode)
		newNode.Data = v
		newNode.Next = nil

		// 将当前结点的指针域指向新结点
		node.Next = newNode
		// 更新当前结点
		node = node.Next
	} // 遍历结束,node在尾结点
	// 将尾结点的指针域指向第一个数据结点(head.Next)
	node.Next = head.Next
	// 将head结点赋值给node,以便node回到head结点位置
	node = head
}

4.打印双向链表

1.递归实现

// 正向打印环形链表--递归实现
var start interface{}

func (node *LinkNode) Print1() {
	// 容错处理
	if node == nil {
		return
	}
	// 如果是头结点就给第一个数据结点标记赋值[头结点的数据域为nil]
	if node.Data == nil {
		// 做第一个数据结点的标记
		start = node.Next
	}
	// 数据结点的特点:有数据数据域
	if node.Data != nil {
		fmt.Print(node.Data, " ")
		//  递归的出口: 此时应该在尾结点[数据域不为空,],其下一个结点(node.Next)即将回到第一个数据结点
		if node.Next == start {
			// 在递归调用出口处打印换行
			fmt.Println()
			return
		}
	}
	// 递归实现
	node.Next.Print1()
}

测试

func main() {
	list := new(LinkNode)
	list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
	list.Print1()
}

1.循环实现

// 正向打印环形链表--循环实现
func (node *LinkNode) Print2() {
	// 容错处理
	if node == nil {
		return
	}
	// 定义第一个数据结点的位置标记
	start := node.Next
	for node.Next != nil {
		node = node.Next // 更新node结点位置,在初始时,可以跳过head指针[head结点:没有数据域(nil),只有指针域(指向第一个数据结点)]
		// 数据结点的特点:有数据数据域
		if node.Data != nil {
			fmt.Print(node.Data, " ")
		}
		// 判断是否在尾结点(即node.Next是否指向第一个数据结点位置)
		if start == node.Next {
			// 已经在尾结点,直接返回
			return
		}
	}
	// 打印换行
	fmt.Println()
}

测试

func main() {
	list := new(LinkNode)
	list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
	list.Print2()
}

5.获取双向链表的长度[数据结点长度]

// 获取数据结点长度
func (node *LinkNode) Length() (length int) {
	// 容错处理
	if node == nil {
		return
	}
	// 标记第一个数据结点的位置:head结点的下一个结点[node.Next]
	start := node.Next
	for {
		node = node.Next // 初始可以跳过head结点
		length++
		// 如果当前在尾结点,即指针域指向第一个数据结点时
		if start == node.Next {
			break
		}
	}
	/* // 也可以使用如下方法获取:
	for {
		node = node.Next // 初始跳过head 结点
		if node.Data != nil {
			length++
		}
		// 如果当前在尾结点,即指针域指向第一个数据结点时
		if start == node.Next {
			break
		}
	} */
	return
}

测试

func main() {
	list := new(LinkNode)
	list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
	list.Print1()
	length := list.Length()
	fmt.Println("环形链表的数据结点长度为:", length)
}

6.插入单向链表结点[数据结点]

// 插入数据结点:按位置插入
func (node *LinkNode) InsertNode(index int, data interface{}) {
	// 容错处理
	if node == nil || data == nil || index <= 0 {
		return
	}
	length := node.Length()
	if length == 0 {
		return
	}
	if index > length {
		return
	}
	// 1.定义一个标记第一个数据结点的标记
	start := node.Next
	// 2.定义待插入结点的前一个结点
	preNode := node
	// 遍历数据结点,到达指定位置
	for i := 0; i < index; i++ {
		// 待插入结点的前一个结点
		preNode = node
		// 偏移node结点到下一个结点
		node = node.Next
	} // node此时在待插入结点
	// 创建新结点,并初始化
	newNode := new(LinkNode)
	newNode.Data = data
	newNode.Next = node // 将新结点的指针域指向待插入结点[原结点]
	// 将待插入的前一个结点的指针域指向新结点,如果是头部插入,那么preNode此时就是head结点
	preNode.Next = newNode
	// 如果是头部插入
	if index == 1 {
		// 遍历到达尾结点
		for {
			node = node.Next
			// 判断是否是尾结点:环形链表的尾结点指向第一个数据结点
			if start == node.Next {
				break
			}
		}
		// 将尾结点指针域指向新插入的数据结点(新插入的结点就是第一个数据结点)
		node.Next = newNode
	}

}

测试

func main() {
	list := new(LinkNode)
	list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
	list.Print1()
	list.InsertNode(1, 11)
	list.Print1()
}

7.删除数据结点

1.按位置删除[位置,非索引]

// 删除环形链表的数据结点--根据位置删除
func (node *LinkNode) DeleteNode(index int) {
	// 容错处理
	if node == nil || index <= 0 {
		return
	}
	length := node.Length()
	if length == 0 {
		return
	}
	if index > length {
		return
	}
	// 1.定义第一个数据结点的标记
	start := node.Next
	// 2.定义待删除结点的前一个结点
	preNode := node
	// 遍历到达待删除结点
	for i := 0; i < index; i++ {
		// 待删除结点的前一个结点赋值
		preNode = node
		// 偏移node结点
		node = node.Next
	} // 此时node结点在待删除结点位置
	// 如果删除的是第一个数据结点
	if index == 1 {
		// 偏移到尾结点,一定要使用临时变量来做,否则node直接偏移,导致preNode.Next = node.Next并不能达到想要的结果
		temp := node
		for {
			if start == temp.Next {
				break
			}
			temp = temp.Next
		} // 此时在尾结点位置
		// 将尾结点的指针域指向头结点的下一个结点
		temp.Next = start.Next// 或者temp.Next = node.Next
	}
	// 将待删除结点的前一个结点指向待删除结点的下一个结点(node.Next)
	preNode.Next = node.Next // 如果删除的是第一个数据结点,那么preNode就是head结点
	// 置为nil,促使GC工作
	node.Data = nil
	node.Next = nil
	node = nil
}

测试

func main() {
	list := new(LinkNode)
	list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
	list.Print1()
	list.DeleteNode(1)
	list.Print1()
}

2.按元素删除

// 删除环形链表的数据结点--根据元素删除
func (node *LinkNode) DeleteNode2(data interface{}) {
	// 容错处理
	if node == nil || data == nil {
		return
	}

	// 保存前一个结点
	preNode := node
	// 遍历结点到达待删除结点的前一个结点位置
	for node.Next != nil {
		preNode = node
		// 偏移到下一个结点
		node = node.Next
		// 判断数据类型和数据是否一致
		if reflect.TypeOf(data) == reflect.TypeOf(node.Data) && reflect.DeepEqual(node.Data, data) {
			// 如果删除的结点是尾结点 node.Next ==nil
			if node.Next == nil {
				// 将前一个结点的指针域设置为空
				preNode.Next = nil
				// 置为nil,方便GC回收
				node.Data = nil
				node.Next = nil
				node = nil
				return
			}
			// node到达待删除结点位置
			// 1.将待删除结点(node)的前一个结点(preNode)的地址域指向待删除结点(node)的下一个结点(node.Next)
			preNode.Next = node.Next
			// 2.置为nil,方便GC回收
			// 将待删除结点的数据置为nil
			node.Data = nil
			// 将待删除结点的后引指针域置为nil
			node.Next = nil
			// 将待删除结点置为nil
			node = nil
			break
		}
	}
}

测试

func main() {
	list := new(LinkNode)
	list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
	list.Print1()
	list.DeleteNode2(7)
	list.Print1()
}

8.销毁环形链表

// 销毁环形链表
func (node *LinkNode) Destroy() {
	// 容错处理
	if node == nil {
		return
	}
	// 1.首先删除尾结点的指针域
	start := node.Next
	temp := node
	for {

		if temp.Next == start {
			temp.Next = nil
			break
		}
	}
	// 2.尾结点的指针域变为nil,就是一个单向链表了,执行递归调用即可
	node.Next.Destroy()
	// 置nil
	node.Data = nil
	node.Next = nil
	node = nil
}

测试

func main() {
	list := new(LinkNode)
	list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
	list.Print1()
	list.Destroy()
	fmt.Println("销毁环形链表后执行的打印:")
	list.Print1()
}

9.完整代码

package main

import (
	"fmt"
	"reflect"
)

// 定义环形链表的结点结构体
type LinkNode struct {
	// 定义环形链表的数据域
	Data interface{}
	// 定义环形链表指针域
	Next *LinkNode
}

// 创建环形链表
func (node *LinkNode) NewLinkList(data ...interface{}) {
	// 容错处理
	if node == nil || len(data) == 0 {
		return
	}
	// 保存head结点
	head := node
	// 遍历data数据创建新结点
	for _, v := range data {
		// 创建新结点,并初始化
		newNode := new(LinkNode)
		newNode.Data = v
		newNode.Next = nil

		// 将当前结点的指针域指向新结点
		node.Next = newNode
		// 更新当前结点
		node = node.Next
	} // 遍历结束,node在尾结点
	// 将尾结点的指针域指向第一个数据结点(head.Next)
	node.Next = head.Next
	// 将head结点赋值给node,以便node回到head结点位置
	node = head
}

// 正向打印环形链表--递归实现
var start interface{}

func (node *LinkNode) Print1() {
	// 容错处理
	if node == nil {
		return
	}
	// 如果是头结点就给第一个数据结点标记赋值[头结点的数据域为nil]
	if node.Data == nil {
		// 做第一个数据结点的标记
		start = node.Next
	}
	// 数据结点的特点:有数据数据域
	if node.Data != nil {
		fmt.Print(node.Data, " ")
		//  递归的出口: 此时应该在尾结点[数据域不为空,],其下一个结点(node.Next)即将回到第一个数据结点
		if node.Next == start {
			// 在递归调用出口处打印换行
			fmt.Println()
			return
		}
	}
	// 递归实现
	node.Next.Print1()
}

// 正向打印环形链表--循环实现
func (node *LinkNode) Print2() {
	// 容错处理
	if node == nil {
		return
	}
	// 定义第一个数据结点的位置标记
	start := node.Next
	for node.Next != nil {
		node = node.Next // 更新node结点位置,在初始时,可以跳过head指针[head结点:没有数据域(nil),只有指针域(指向第一个数据结点)]
		// 数据结点的特点:有数据数据域
		if node.Data != nil {
			fmt.Print(node.Data, " ")
		}
		// 判断是否在尾结点(即node.Next是否指向第一个数据结点位置)
		if start == node.Next {
			// 已经在尾结点,直接返回
			return
		}
	}
	// 打印换行
	fmt.Println()
}

// 获取数据结点长度
func (node *LinkNode) Length() (length int) {
	// 容错处理
	if node == nil {
		return
	}
	// 标记第一个数据结点的位置:head结点的下一个结点[node.Next]
	start := node.Next
	for {
		node = node.Next // 初始可以跳过head结点
		length++
		// 如果当前在尾结点,即指针域指向第一个数据结点时
		if start == node.Next {
			break
		}
	}
	/* // 也可以使用如下方法获取:
	for {
		node = node.Next // 初始跳过head 结点
		if node.Data != nil {
			length++
		}
		// 如果当前在尾结点,即指针域指向第一个数据结点时
		if start == node.Next {
			break
		}
	} */
	return
}

// 插入数据结点:按位置插入
func (node *LinkNode) InsertNode(index int, data interface{}) {
	// 容错处理
	if node == nil || data == nil || index <= 0 {
		return
	}
	length := node.Length()
	if length == 0 {
		return
	}
	if index > length {
		return
	}
	// 1.定义一个标记第一个数据结点的标记
	start := node.Next
	// 2.定义待插入结点的前一个结点
	preNode := node
	// 遍历数据结点,到达指定位置
	for i := 0; i < index; i++ {
		// 待插入结点的前一个结点
		preNode = node
		// 偏移node结点到下一个结点
		node = node.Next
	} // node此时在待插入结点
	// 创建新结点,并初始化
	newNode := new(LinkNode)
	newNode.Data = data
	newNode.Next = node // 将新结点的指针域指向待插入结点[原结点]
	// 将待插入的前一个结点的指针域指向新结点,如果是头部插入,那么preNode此时就是head结点
	preNode.Next = newNode
	// 如果是头部插入
	if index == 1 {
		// 遍历到达尾结点
		for {
			node = node.Next
			// 判断是否是尾结点:环形链表的尾结点指向第一个数据结点
			if start == node.Next {
				break
			}
		}
		// 将尾结点指针域指向新插入的数据结点(新插入的结点就是第一个数据结点)
		node.Next = newNode
	}
}

// 删除环形链表的数据结点--根据位置删除
func (node *LinkNode) DeleteNode(index int) {
	// 容错处理
	if node == nil || index <= 0 {
		return
	}
	length := node.Length()
	if length == 0 {
		return
	}
	if index > length {
		return
	}
	// 1.定义第一个数据结点的标记
	start := node.Next
	// 2.定义待删除结点的前一个结点
	preNode := node
	// 遍历到达待删除结点
	for i := 0; i < index; i++ {
		// 待删除结点的前一个结点赋值
		preNode = node
		// 偏移node结点
		node = node.Next
	} // 此时node结点在待删除结点位置
	// 如果删除的是第一个数据结点
	if index == 1 {
		// 偏移到尾结点,一定要使用临时变量来做,否则node直接偏移,导致preNode.Next = node.Next并不能达到想要的结果
		temp := node
		for {
			if start == temp.Next {
				break
			}
			temp = temp.Next
		} // 此时在尾结点位置
		// 将尾结点的指针域指向头结点的下一个结点
		temp.Next = start.Next // 或者temp.Next = node.Next
	}
	// 将待删除结点的前一个结点指向待删除结点的下一个结点(node.Next)
	preNode.Next = node.Next // 如果删除的是第一个数据结点,那么preNode就是head结点
	// 置为nil,促使GC工作
	node.Data = nil
	node.Next = nil
	node = nil
}

// 删除环形链表的数据结点--根据元素删除
func (node *LinkNode) DeleteNode2(data interface{}) {
	// 容错处理
	if node == nil || data == nil {
		return
	}

	// 保存前一个结点
	preNode := node
	// 遍历结点到达待删除结点的前一个结点位置
	for node.Next != nil {
		preNode = node
		// 偏移到下一个结点
		node = node.Next
		// 判断数据类型和数据是否一致
		if reflect.TypeOf(data) == reflect.TypeOf(node.Data) && reflect.DeepEqual(node.Data, data) {
			// 如果删除的结点是尾结点 node.Next ==nil
			if node.Next == nil {
				// 将前一个结点的指针域设置为空
				preNode.Next = nil
				// 置为nil,方便GC回收
				node.Data = nil
				node.Next = nil
				node = nil
				return
			}
			// node到达待删除结点位置
			// 1.将待删除结点(node)的前一个结点(preNode)的地址域指向待删除结点(node)的下一个结点(node.Next)
			preNode.Next = node.Next
			// 2.置为nil,方便GC回收
			// 将待删除结点的数据置为nil
			node.Data = nil
			// 将待删除结点的后引指针域置为nil
			node.Next = nil
			// 将待删除结点置为nil
			node = nil
			break
		}
	}
}

// 销毁环形链表
func (node *LinkNode) Destroy() {
	// 容错处理
	if node == nil {
		return
	}
	// 1.首先删除尾结点的指针域
	start := node.Next
	temp := node
	for {

		if temp.Next == start {
			temp.Next = nil
			break
		}
	}
	// 2.尾结点的指针域变为nil,就是一个单向链表了,执行递归调用即可
	node.Next.Destroy()
	// 置nil
	node.Data = nil
	node.Next = nil
	node = nil
}

func main() {
	list := new(LinkNode)
	list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
	list.Print1()
	list.Destroy()
	fmt.Println("销毁环形链表后执行的打印:")
	list.Print1()
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值