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