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