Golang-线性存储-链表-双向链表
1.初始化
mkdir -p linkList
cd linkList
go mod init linkList
touch linkList.go
vim linkList.go
2.定义双向链表结点结构体
// 定义双向链表的结点结构体
type LinkNode struct {
// 定义双向链表的数据域
Data interface{}
// 定义双向链表的前引指针域
Prev *LinkNode
// 定义双向链表的后引指针域
Next *LinkNode
}
3.创建双向链表
// 创建双向链表
func (node *LinkNode) NewLinkList(data ...interface{}) {
// 容错处理
if node == nil || data == nil || len(data) == 0 {
return
}
// 保存head(头结点)
head := node
// 遍历参数数据,创建双向链表的结点
for _, v := range data {
// 1.创建新结点
newNode := new(LinkNode)
// 2.初始化结点
newNode.Data = v
newNode.Prev = node // 新结点的前引地址域就是node
newNode.Next = nil // 新结点的后引地址域为nil
// 将当前结点的下一个结点指向新结点
node.Next = newNode
// 更新当前结点
node = node.Next
}
// 还原head 结点
node = head
}
4.打印双向链表
1.正向
1.递归实现
// 正向打印双向链表--递归实现
func (node *LinkNode) Print1() {
// 容错处理,同时也是递归出口
if node == nil {
// 在递归出口处执行换行
fmt.Println()
return
}
if node.Data != nil {
fmt.Print(node.Data, " ")
}
// 使用后引指针域调用打印方法
node.Next.Print1()
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6)
list.Print1()
}
2.循环实现
// 正向打印双向链表--循环实现
func (node *LinkNode) Print2() {
// 容错处理
if node == nil {
return
}
for node.Next != nil {
node = node.Next // 第一次可以跳过head结点
if node.Data != nil {
fmt.Print(node.Data, " ")
}
}
// 换行
fmt.Println()
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6)
list.Print2()
}
2.反向
1.递归实现
// 反向打印双向链表--递归实现
var isFirstHeadNode = true // 定义head结点是在第一次出现
func (node *LinkNode) Print3() {
// 容错处理,同时也是递归出口
if node == nil {
// 在递归出口处执行换行
fmt.Println()
return
}
// 如果是head结点,跳转到尾结点
// head结点:前引为nil,Data为nil,Next不为nil
// isFirstHeadNode作为控制跳转的次数,确保递归从尾到头结束后不会二次跳转到尾结点,不至于造成无限递归
if node.Prev == nil && node.Data == nil && isFirstHeadNode == true {
for node.Next != nil {
node = node.Next
}
}
if node.Data != nil {
fmt.Print(node.Data, " ")
}
// 已经不再是head结点第一次出现了
isFirstHeadNode = false
// 使用前引指针域调用打印方法
node.Prev.Print3()
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6)
list.Print3()
}
2.循环实现
// 反向打印双向链表--循环实现
func (node *LinkNode) Print4() {
// 容错处理
if node == nil {
return
}
// 跳转到尾结点
for node.Next != nil {
node = node.Next
}
for node.Prev != nil {
if node.Data != nil {
fmt.Print(node.Data, " ")
}
node = node.Prev // 跳转到上一节点
}
// 换行
fmt.Println()
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6)
list.Print4()
}
5.获取双向链表的长度[数据结点长度]
// 获取数据结点长度
func (node *LinkNode) Length() (length int) {
// 容错处理
if node == nil {
return
}
for node.Next != nil {
node = node.Next // 初始可以跳过head结点
length++
}
/* // 也可以使用如下方法获取:
for node.Next != nil {
node = node.Next // 初始跳过head 结点
if node.Data != nil {
length++
}
} */
return
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6)
length := list.Length()
fmt.Println("双向链表的数据结点的长度为:", length)
}
6.插入双向链表结点[数据结点]
1.头插法
// 插入数据结点 1.头插法
func (node *LinkNode) InsertNode(data interface{}) {
// 容错处理
if node == nil || data == nil {
return
}
// 创建新结点并初始化
newNode := new(LinkNode)
newNode.Data = data
newNode.Prev = node // 将新结点的前引指针域指向原node结点[因为第一个是head结点,所以指向的是head结点]
newNode.Next = nil
// 将新结点的后引指针域指向原数据结点[因为第一个为head结点,所以数据结点从node.Next开始
newNode.Next = node.Next
// 将head结点的后引指针域指向新结点
node.Next = newNode
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6)
list.InsertNode(11)
list.Print1()
}
2.尾插法
// 插入数据结点 2.尾插法
func (node *LinkNode) InsertNode2(data interface{}) {
// 容错处理
if node == nil || data == nil {
return
}
// 偏移到尾结点
for node.Next != nil {
node = node.Next
}
// 创建新结点并初始化
newNode := new(LinkNode)
newNode.Data = data
newNode.Prev = node // 因为已经偏移到尾结点,所以将新结点的前引指针域设置为尾结点
newNode.Next = nil
// 将尾结点的后引指针域指向新结点
node.Next = newNode
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
list.InsertNode2(12)
list.Print1()
}
3.按位置插入[位置,非索引]
// 插入数据结点 3.按位置插入
func (node *LinkNode) InsertNode3(index int, data interface{}) {
// 容错处理
if node == nil || data == nil || index < 0 {
return
}
length := node.Length()
if length == 0 {
return
}
if index > length {
return
}
// 头部插入
if index == 0 {
node.InsertNode(data)
return
}
// 尾部插入
if index == length {
node.InsertNode2(data)
return
}
// 定义待插入结点的前一个结点
preNode := node
// 遍历数据结点,到达指定位置
for i := 0; i < index; i++ {
// 待插入结点的前一个结点
preNode = node
// 偏移node结点到下一个结点
node = node.Next
} // node此时在待插入结点
// 创建新结点,并初始化
newNode := new(LinkNode)
newNode.Data = data
newNode.Prev = preNode // 将新结点的前引指针域指向待插入结点的前一个结点
newNode.Next = node // 将新结点的后引指针域指向待插入结点[原结点]
// 将待插入结点[原结点]的前引指针域指向新结点
node.Prev = newNode
// 将待插入的前一个结点的后引指针域指向新结点
preNode.Next = newNode
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
list.Print1()
list.InsertNode3(2, 13)
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
}
// 如果index为0,则删除第一个数据结点
if index == 0 {
// 保存头结点
head := node
// 到达第一个数据结点
node = node.Next
// 1.将第一个数据结点的后一个结点的前引指针域设置为头结点
node.Next.Prev = head
// 2.将头结点的后引指针域设置为第一个数据结点的下一个结点
head.Next = node.Next
// 3.置为nil,方便GC回收
// 第一个数据结点的数据清nil
node.Data = nil
// 第一个数据结点的前引指针域设置为nil
node.Prev = nil
// 第一个数据结点的后引指针域设置为nil
node.Next = nil
// 4.还原头指针
node = head
return
}
// 定义待删除结点的前一个结点
preNode := node
// 遍历结点到达待删除结点的前一个结点位置
for i := 0; i < index; i++ {
// 给待删除结点重新赋值
preNode = node
// 偏移到下一个结点
node = node.Next
} // 遍历完成后,node到达待删除结点位置
// 如果删除的是尾结点
if index == length {
// node到达尾结点
// 尾结点的前一个结点的后引指针域设置为nil
preNode.Next = nil
// 置为nil,方便GC回收
// 尾结点的数据清nil
node.Data = nil
// 尾结点的前引指针域设置为nil
node.Prev = nil
return
}
// 1.将待删除结点(node)的前一个结点(preNode)的后引地址域指向待删除结点(node)的下一个结点(node.Next)
preNode.Next = node.Next
// 2.将待删除结点(node)的后一个结点的前引地址域指向待删除结点的前一个结点
node.Next.Prev = preNode
// 3.置为nil,方便GC回收
// 将待删除结点的数据置为nil
node.Data = nil
// 将待删除结点的前引指针域置为nil
node.Prev = nil
// 将待删除结点的后引指针域置为nil
node.Next = nil
// 将待删除结点置为nil
node = nil
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
list.Print1()
list.DeleteNode(0)
list.Print1()
}
2.按元素删除
// 删除双向链表的数据结点--根据元素删除
func (node *LinkNode) DeleteNode2(data interface{}) {
// 容错处理
if node == nil || data == nil {
return
}
length := node.Length()
if length == 0 {
return
}
// 遍历结点到达待删除结点的前一个结点位置
for i := 0; i <= length; i++ {
// 判断数据类型和数据是否一致
if reflect.TypeOf(data) == reflect.TypeOf(node.Data) && reflect.DeepEqual(node.Data, data) {
// 如果删除的结点是尾结点 i == length
if i == length {
// 1.将尾结点的前一结点的后引指针域设置为空
node.Prev.Next = nil
// 2.置为nil,方便GC回收
node.Data = nil
node.Prev = nil
node.Next = nil
node = nil
return
}
// node到达待删除结点位置
// 1.将待删除结点(node)的前一个结点(node.Prev)的后引地址域指向待删除结点(node)的下一个结点(node.Next)
node.Prev.Next = node.Next
// 2.将待删除结点(node)的后一个结点(node.Next)的前引地址域指向待删除结点的前一个结点
node.Next.Prev = node.Prev
// 3.置为nil,方便GC回收
// 将待删除结点的数据置为nil
node.Data = nil
// 将待删除结点的前引指针域置为nil
node.Prev = nil
// 将待删除结点的后引指针域置为nil
node.Next = nil
// 将待删除结点置为nil
node = nil
break
}
// 偏移到下一个结点
node = node.Next
}
}
测试
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
}
// 递归调用
node.Next.Destroy()
// 置nil
node.Data = nil
node.Prev = nil
node.Next = nil
node = nil
}
测试
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
list.Print1()
list.Destroy()
list.Print1()
}
9.完整代码
package main
import (
"fmt"
"reflect"
)
// 定义双向链表的结点结构体
type LinkNode struct {
// 定义双向链表的数据域
Data interface{}
// 定义双向链表的前引指针域
Prev *LinkNode
// 定义双向链表的后引指针域
Next *LinkNode
}
// 创建双向链表
func (node *LinkNode) NewLinkList(data ...interface{}) {
// 容错处理
if node == nil || data == nil || len(data) == 0 {
return
}
// 保存head(头结点)
head := node
// 遍历参数数据,创建双向链表的结点
for _, v := range data {
// 1.创建新结点
newNode := new(LinkNode)
// 2.初始化结点
newNode.Data = v
newNode.Prev = node // 新结点的前引地址域就是node
newNode.Next = nil // 新结点的后引地址域为nil
// 将当前结点的下一个结点指向新结点
node.Next = newNode
// 更新当前结点
node = node.Next
}
// 还原head 结点
node = head
}
// 正向打印双向链表--递归实现
func (node *LinkNode) Print1() {
// 容错处理,同时也是递归出口
if node == nil {
// 在递归出口处执行换行
fmt.Println()
return
}
if node.Data != nil {
fmt.Print(node.Data, " ")
}
// 使用后引指针域调用打印方法
node.Next.Print1()
}
// 正向打印双向链表--循环实现
func (node *LinkNode) Print2() {
// 容错处理
if node == nil {
return
}
for node.Next != nil {
node = node.Next // 第一次可以跳过head结点
if node.Data != nil {
fmt.Print(node.Data, " ")
}
}
// 换行
fmt.Println()
}
// 反向打印双向链表--递归实现
var isFirstHeadNode = true // 定义head结点是在第一次出现
func (node *LinkNode) Print3() {
// 容错处理,同时也是递归出口
if node == nil {
// 在递归出口处执行换行
fmt.Println()
return
}
// 如果是head结点,跳转到尾结点
// head结点:前引为nil,Data为nil,Next不为nil
// isFirstHeadNode作为控制跳转的次数,确保递归从尾到头结束后不会二次跳转到尾结点,不至于造成无限递归
if node.Prev == nil && node.Data == nil && isFirstHeadNode == true {
for node.Next != nil {
node = node.Next
}
}
if node.Data != nil {
fmt.Print(node.Data, " ")
}
// 已经不再是head结点第一次出现了
isFirstHeadNode = false
// 使用前引指针域调用打印方法
node.Prev.Print3()
}
// 反向打印双向链表--循环实现
func (node *LinkNode) Print4() {
// 容错处理,同时也是
if node == nil {
return
}
// 跳转到尾结点
for node.Next != nil {
node = node.Next
}
for node.Prev != nil {
if node.Data != nil {
fmt.Print(node.Data, " ")
}
node = node.Prev // 跳转到上一节点
}
// 换行
fmt.Println()
}
// 获取数据结点长度
func (node *LinkNode) Length() (length int) {
// 容错处理
if node == nil {
return
}
for node.Next != nil {
length++ // 初始可以跳过head结点
node = node.Next
}
/*
也可以使用如下方法获取:
for node.Next != nil {
node = node.Next // 初始跳过head 结点
if node.Data != nil {
length++
}
}
*/
return
}
// 插入数据结点 1.头插法
func (node *LinkNode) InsertNode(data interface{}) {
// 容错处理
if node == nil || data == nil {
return
}
// 创建新结点并初始化
newNode := new(LinkNode)
newNode.Data = data
newNode.Prev = node // 将新结点的前引指针域指向原node结点[因为第一个是head结点,所以指向的是head结点]
newNode.Next = nil
// 将新结点的后引指针域指向原数据结点[因为第一个为head结点,所以数据结点从node.Next开始
newNode.Next = node.Next
// 将head结点的后引指针域指向新结点
node.Next = newNode
}
// 插入数据结点 2.尾插法
func (node *LinkNode) InsertNode2(data interface{}) {
// 容错处理
if node == nil || data == nil {
return
}
// 偏移到尾结点
for node.Next != nil {
node = node.Next
}
// 创建新结点并初始化
newNode := new(LinkNode)
newNode.Data = data
newNode.Prev = node // 因为已经偏移到尾结点,所以将新结点的前引指针域设置为尾结点
newNode.Next = nil
// 将尾结点的后引指针域指向新结点
node.Next = newNode
}
// 插入数据结点 3.按位置插入
func (node *LinkNode) InsertNode3(index int, data interface{}) {
// 容错处理
if node == nil || data == nil || index < 0 {
return
}
length := node.Length()
if index > length {
return
}
// 头部插入
if index == 0 {
node.InsertNode(data)
return
}
// 尾部插入
if index == length {
node.InsertNode2(data)
return
}
// 定义待插入结点的前一个结点
preNode := node
// 遍历数据结点,到达指定位置
for i := 0; i < index; i++ {
// 待插入结点的前一个结点
preNode = node
// 偏移node结点到下一个结点
node = node.Next
} // node此时在待插入结点
// 创建新结点,并初始化
newNode := new(LinkNode)
newNode.Data = data
newNode.Prev = preNode // 将新结点的前引指针域指向待插入结点的前一个结点
newNode.Next = node // 将新结点的后引指针域指向待插入结点[原结点]
// 将待插入结点[原结点]的前引指针域指向新结点
node.Prev = newNode
// 将待插入的前一个结点的后引指针域指向新结点
preNode.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
}
// 如果index为0,则删除第一个数据结点
if index == 0 {
// 保存头结点
head := node
// 到达第一个数据结点
node = node.Next
// 1.将第一个数据结点的后一个结点的前引指针域设置为头结点
node.Next.Prev = head
// 2.将头结点的后引指针域设置为第一个数据结点的下一个结点
head.Next = node.Next
// 3.置为nil,方便GC回收
// 第一个数据结点的数据清nil
node.Data = nil
// 第一个数据结点的前引指针域设置为nil
node.Prev = nil
// 第一个数据结点的后引指针域设置为nil
node.Next = nil
// 4.还原头指针
node = head
return
}
// 定义待删除结点的前一个结点
preNode := node
// 遍历结点到达待删除结点的前一个结点位置
for i := 0; i < index; i++ {
// 给待删除结点重新赋值
preNode = node
// 偏移到下一个结点
node = node.Next
} // 遍历完成后,node到达待删除结点位置
// 如果删除的是尾结点
if index == length {
// node到达尾结点
// 尾结点的前一个结点的后引指针域设置为nil
preNode.Next = nil
// 置为nil,方便GC回收
// 尾结点的数据清nil
node.Data = nil
// 尾结点的前引指针域设置为nil
node.Prev = nil
return
}
// 1.将待删除结点(node)的前一个结点(preNode)的后引地址域指向待删除结点(node)的下一个结点(node.Next)
preNode.Next = node.Next
// 2.将待删除结点(node)的后一个结点的前引地址域指向待删除结点的前一个结点
node.Next.Prev = preNode
// 3.置为nil,方便GC回收
// 将待删除结点的数据置为nil
node.Data = nil
// 将待删除结点的前引指针域置为nil
node.Prev = nil
// 将待删除结点的后引指针域置为nil
node.Next = nil
// 将待删除结点置为nil
node = nil
}
// 删除双向链表的数据结点--根据元素删除
func (node *LinkNode) DeleteNode2(data interface{}) {
// 容错处理
if node == nil || data == nil {
return
}
length := node.Length()
if length == 0 {
return
}
if length == 0 {
return
}
// 遍历结点到达待删除结点的前一个结点位置
for i := 0; i <= length; i++ {
// 判断数据类型和数据是否一致
if reflect.TypeOf(data) == reflect.TypeOf(node.Data) && reflect.DeepEqual(node.Data, data) {
// 如果删除的结点是尾结点 i == length
if i == length {
// 1.将尾结点的前一结点的后引指针域设置为空
node.Prev.Next = nil
// 2.置为nil,方便GC回收
node.Data = nil
node.Prev = nil
node.Next = nil
node = nil
return
}
// node到达待删除结点位置
// 1.将待删除结点(node)的前一个结点(node.Prev)的后引地址域指向待删除结点(node)的下一个结点(node.Next)
node.Prev.Next = node.Next
// 2.将待删除结点(node)的后一个结点(node.Next)的前引地址域指向待删除结点的前一个结点
node.Next.Prev = node.Prev
// 3.置为nil,方便GC回收
// 将待删除结点的数据置为nil
node.Data = nil
// 将待删除结点的前引指针域置为nil
node.Prev = nil
// 将待删除结点的后引指针域置为nil
node.Next = nil
// 将待删除结点置为nil
node = nil
break
}
// 偏移到下一个结点
node = node.Next
}
}
// 销毁双向链表
func (node *LinkNode) Destroy() {
// 容错处理
if node == nil {
return
}
// 递归调用
node.Next.Destroy()
// 置nil
node.Data = nil
node.Prev = nil
node.Next = nil
node = nil
}
func main() {
list := new(LinkNode)
list.NewLinkList(1, 2, 3, 4, 5, 6, 7)
list.Print1()
list.Destroy()
list.Print1()
}