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()
}