1. 程式人生 > 程式設計 >詳解go語言單鏈表及其常用方法的實現

詳解go語言單鏈表及其常用方法的實現

目的

在刷演算法題中經常遇到關於連結串列的操作,在使用go語言去操作連結串列時不熟悉其實現原理,目的是為了重溫連結串列這一基礎且關鍵的資料結構。

1、連結串列的特點和初始化

1.1、連結串列的特點
用一組任意的儲存單元儲存線性表的資料元素(這組儲存單元可以是連續的,也可以是不連續的)

1.2、結點

結點(node)

  • 資料域 => 儲存元素資訊
  • 指標域 => 儲存結點的直接後繼,也稱作指標或鏈

首元結點 是指連結串列中儲存的第一個資料元素的結點
頭結點 是在首元結點之前附設的一個結點,其指標域指向首元結點(非必須)
頭指標 是指向連結串列中第一個結點的指標

詳解go語言單鏈表及其常用方法的實現

1.3、單鏈表

特點

  • 每個結點中只包含一個指標域
  • 單鏈表是非隨機存取的儲存結構,要取得第i個數據元素必須從頭指標出發,順鏈進行尋找,也稱為順序存取的存取結構

1.4、單鏈表的常用操作

本文主要實現了單鏈表的以下操作

  • 判斷是否為空
  • 獲取連結串列長度
  • 在頭部插入元素
  • 在尾部插入元素
  • 刪除指定位置元素
  • 刪除指定值的元素
  • 查詢是否包含指定值
  • 查詢指定位置元素的值
  • 遍歷連結串列所有結點

1.5、單鏈表的初始化

//定義單鏈表結構體
type Node struct {
  data interface{} //資料域
  next *Node    //指標域
}
type List struct {
  length  int //儲存連結串列的長度
  headNode *Node
}

/*單鏈表的初始化
1、生成新結點作為頭結點,用頭指標指向頭結點
2、頭結點的指標域置空
*/
func InitList() *List {
  //即構造一個空的單鏈表L(包含頭指標)
  node := new(Node)
  L := new(List)
  L.headNode = node
  return L
}

2、單鏈表的插入

先講單鏈表的插入有利於後續相關操作的實現

2.1、在指定位置插入元素

/*單鏈表的插入=>將值為e的新結點插入到表的第i個結點的位置上,即插入到結點a(i-1)與a(i)之間
1、查詢結點a(i-1)並由指標p指向該結點
2、生成一個新結點*s
3、將新結點*s的資料域置為e
4、將新結點*s的指標域指向結點a(i)
5、將結點*p的指標域指向新結點*s
*/
func (list *List) InsertElem(index int,v interface{}) {
  if index <= 0 || index > list.length {
    fmt.Println("err")
  } else {
    pre := list.headNode
    node := &Node{data: v}
    if index == 1 {
      node.next = pre
      list.headNode = node
    } else {
      for count := 1; count < index-1; count++ {
        pre = pre.next
      }
      node.next = pre.next
      pre.next = node
    }
    list.length--
  }
}

2.2、在頭部插入元素

func (list *List) AddElem(v interface{}) {
  node := &Node{data: v}
  if list.IsNull() { //處理空表的插入,否則會導致一個空的頭結點後移
    list.headNode = node
    list.length++
    return
  }
  node.next = list.headNode
  list.headNode = node
  list.length++
  return
}

2.3、在尾部插入元素

func (list *List) AppendElem(v interface{}) {
  node := &Node{data: v}
  if list.IsNull() {
    list.headNode.next = node
  } else {
    cur := list.headNode
    for cur.next != nil {
      cur = cur.next
    }
    cur.next = node
  }
  list.length++
  return
}

3、單鏈表的刪除

3.1、刪除指定值的元素

/*單鏈表的刪除
1、查詢結點a(i-1)並由指標p指向該結點
2、臨時儲存待刪除結點a(i)的地址在q中,以備釋放
3、將結點*p的指標域指向a(i)的直接後繼結點
4、釋放結點a(i)的空間
*/
func (list *List) DeleteElem(index int) {
  if index <= 0 || index > list.length {
    fmt.Println("刪除位置不合理")
    return
  } else {
    pre := list.headNode
    if index == 1 {
      list.headNode = pre.next
    } else {
      pre := list.headNode
      for count := 1; count < index-1; count++ {
        pre = pre.next
      }
      pre.next = pre.next.next
    }
    list.length--
  }
}

3.2、刪除指定位置的元素

func (list *List) RemoveElem(v interface{}) {
  pre := list.headNode
  if pre.data == v {
    list.headNode = pre.next
    fmt.Println("ok")
  } else {
    for pre.next != nil {
      if pre.next.data == v {
        pre.next = pre.next.next
        fmt.Println("ok")
        return
      } else {
        pre = pre.next
      }
    }
    fmt.Println("fail")
    return
  }
}

4、單鏈表的查詢

4.1、查詢是否包含指定值

/*單鏈表的按值查詢
1、用指標p指向首元結點
2、從首元結點開始以此順著鏈域next向下查詢,只要指向當前結點的指標p不為空,
並且p所指結點的資料域不等於給定值e,則執行以下操作:p指向下一個結點
3、返回p。若查詢成功,p此時即為結點的地址值,若查詢失敗,p的值即為NULL。
*/
func (list *List) LocateElem(v interface{}) bool {
  if IsNull() {
    fmt.Println("err")
  } else {
    pre := list.headNode
    for pre != nil {
      if pre.data == v {
        return true
      }
      pre = pre.next
    }
    return false
  }
}

4.2、查詢指定位置的值

/*單鏈表的取值
1、用指標P指向首元結點,用j做計數器初值賦為1
2、從首元結點開始依次順著鏈域(指標域)next向下訪問,
只要指向當前結點的指標P不為空,並且沒有達到序號為i的結點,則迴圈執行以下操作:
  2.1、P指向下一個結點
  2.2、計數器j相應加1
3、退出迴圈時,如果指標P為空,或者計數器j大於i,說明指定的序號i值不合法(i大於表長n或i小於等於0),
取值失敗返回ERROR;否則取值成功,
此時j==i時,P所指的結點就是要找的第i個結點,用引數e儲存當前結點的資料域,返回OK
*/
func (list *List) GetElem(index int) int {
  if index <= 0 || index > list.length {
    fmt.Println("err")
    return
  } else {
    pre := list.headNode
    for j := 0; j < index; j++ {
      if pre != nil {
        pre = pre.next
      }
    }
    return pre.data
  }
}

4.3、遍歷單鏈表

func (list *List) ShowList() {
  if !list.IsNull() {
    cur := list.headNode
    for {
      fmt.Printf("\t%v",cur.data)
      if cur.next != nil {
        cur = cur.next
      } else {
        break
      }
    }
  }
}

5、完整程式碼及結果展示

package main

import "fmt"

//定義單鏈表結構體

type Node struct {
  data interface{} //資料域
  next *Node    //指標域
}
type List struct {
  length  int //儲存連結串列的長度
  headNode *Node
}

/*
type Method interface {
  IsNull() bool          //1、判斷是否為空
  GetLength() int         //2、獲取連結串列長度
  InsertElem(i int,v interface{}) //3、在指定位置新增元素
  AddElem(v interface{})      //4、在頭部插入元素
  AppendElem(v interface{})    //5、在尾部插入元素
  DeleteElem(i int)        //6、刪除指定位置元素
  RemoveElem(v interface{})    //7、刪除指定值的元素
  ContaineElem(v interface{}) bool //8、是否包含指定值的元素
  LocateElem(i int) interface{}  //9、查詢指定位置元素的值
  ShowList()            //10、遍歷連結串列所有結點
}
*/
/*單鏈表的初始化
1、生成新結點作為頭結點,用頭指標指向頭結點
2、頭結點的指標域置空
*/
func InitList() *List {
  //即構造一個空的單鏈表L(包含頭指標)
  node := new(Node)
  L := new(List)
  L.headNode = node
  return L
}

/*單鏈表的取值
1、用指標P指向首元結點,用j做計數器初值賦為1
2、從首元結點開始依次順著鏈域(指標域)next向下訪問,只要指向當前結點的指標P不為空,
並且沒有達到序號為i的結點,則迴圈執行以下操作:
  2.1、P指向下一個結點
  2.2、計數器j相應加1
3、退出迴圈時,如果指標P為空,或者計數器j大於i,說明指定的序號i值
不合法(i大於表長n或i小於等於0),取值失敗返回ERROR;否則取值成功,
此時j==i時,P所指的結點就是要找的第i個結點,用引數e儲存當前結點的資料域,返回OK
*/
func (list *List) GetElem(index int) int {
  if index <= 0 || index > list.length {
    return 0
  } else {
    pre := list.headNode
    for j := 0; j < index-1; j++ {
      if pre != nil {
        pre = pre.next
      }
    }
    return pre.data.(int)
  }
}

/*單鏈表的按值查詢
1、用指標p指向首元結點
2、從首元結點開始以此順著鏈域next向下查詢,只要指向當前結點的
指標p不為空,並且p所指結點的資料域不等於給定值e,則執行以下操作:
  2.1、p指向下一個結點
3、返回p。若查詢成功,p此時即為結點的地址值,若查詢失敗,p的值即為NULL。
*/
func (list *List) LocateElem(v interface{}) bool {
  if list.IsNull() {
    fmt.Println("err")
    return false
  } else {
    pre := list.headNode
    for pre != nil {
      if pre.data == v {
        return true
      }
      pre = pre.next
    }
    return false
  }
}

/*單鏈表的插入=>將值為e的新結點插入到表的第i個結點的位置上,即插入到結點a(i-1)與a(i)之間
1、查詢結點a(i-1)並由指標p指向該結點
2、生成一個新結點*s
3、將新結點*s的資料域置為e
4、將新結點*s的指標域指向結點a(i)
5、將結點*p的指標域指向新結點*s
*/
func (list *List) InsertElem(index int,v interface{}) {
  if index <= 0 || index > list.length {
    fmt.Println("err")
  } else {
    pre := list.headNode
    node := &Node{data: v}
    if index == 1 {
      node.next = pre
      list.headNode = node
    } else {
      for count := 1; count < index-1; count++ {
        pre = pre.next
      }
      node.next = pre.next
      pre.next = node
    }
    list.length--
  }
}

/*單鏈表的刪除
1、查詢結點a(i-1)並由指標p指向該結點
2、臨時儲存待刪除結點a(i)的地址在q中,以備釋放
3、將結點*p的指標域指向a(i)的直接後繼結點
4、釋放結點a(i)的空間
*/
func (list *List) DeleteElem(index int) {
  if index <= 0 || index > list.length {
    fmt.Println("刪除位置不合理")
    return
  } else {
    pre := list.headNode
    if index == 1 {
      list.headNode = pre.next
    } else {
      pre := list.headNode
      for count := 1; count < index-1; count++ {
        pre = pre.next
      }
      pre.next = pre.next.next
    }
    list.length--
  }
}

func (list *List) RemoveElem(v interface{}) {
  pre := list.headNode
  if pre.data == v {
    list.headNode = pre.next
  } else {
    for pre.next != nil {
      if pre.next.data == v {
        pre.next = pre.next.next
        return
      } else {
        pre = pre.next
      }
    }
    fmt.Println("fail")
    return
  }
}

func (list *List) IsNull() bool {
  if list.length == 0 {
    return true
  } else {
    return false
  }
}

func (list *List) AddElem(v interface{}) {
  node := &Node{data: v}
  if list.IsNull() { //處理空表的插入,否則會導致一個空的頭結點後移
    list.headNode = node
    list.length++
    return
  }
  node.next = list.headNode
  list.headNode = node
  list.length++
  return
}

func (list *List) AppendElem(v interface{}) {
  node := &Node{data: v}
  if list.IsNull() {
    list.headNode.next = node
  } else {
    cur := list.headNode
    for cur.next != nil {
      cur = cur.next
    }
    cur.next = node
  }
  list.length++
  return
}

func (list *List) ShowList() {
  if !list.IsNull() {
    cur := list.headNode
    for {
      fmt.Printf("\t%v",cur.data)
      if cur.next != nil {
        cur = cur.next
      } else {
        break
      }
    }
    fmt.Printf("\n")
  }
}

func main() {
  L := InitList()
  msg := []int{12,5,3,8,55,13}
  for i := range msg {
    L.AddElem(msg[i])
  }
  fmt.Println("---- 新增元素 ----")
  L.AppendElem(66)
  L.ShowList()
  fmt.Println("---- 按位刪除元素 ----")
  L.DeleteElem(3)
  L.ShowList()
  fmt.Println("---- 按值刪除元素 ----")
  L.RemoveElem(13)
  L.ShowList()
  fmt.Println("---- 插入元素 ----")
  L.InsertElem(1,88)
  L.ShowList()
  fmt.Println("---- 按值尋找元素 ----")
  fmt.Println(L.LocateElem(88))
  fmt.Println("---- 按位尋找元素 ----")
  fmt.Println(L.GetElem(4))
}

結果
---- 新增元素 ----
13 55 8 3 5 12 66
---- 按位刪除元素 ----
13 55 3 5 12 66
---- 按值刪除元素 ----
55 3 5 12 66
---- 插入元素 ----
88 55 3 5 12 66
---- 按值尋找元素 ----
true
---- 按位尋找元素 ----
5

6、總結

本文中除了初始化時為連結串列添加了一個空的頭結點,其他情況下均無頭結點,正如書中所說,為單鏈表新增頭結點會方便很多,對連結串列進行相關操作時,不需要對首元結點做額外的處理,也便於對空表和非空表做統一處理
關於刪除時釋放結點空間及指標回收,我們交由go強大的垃圾回收來完成

參考部落格
Golang之單鏈表實現
go語言實現單鏈表

到此這篇關於詳解go語言單鏈表及其常用方法的實現的文章就介紹到這了,更多相關go語言單鏈表內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!