1. 程式人生 > 其它 >【Go資料結構】連結串列 Linked List

【Go資料結構】連結串列 Linked List

連結串列 Linked List

元素在記憶體中不是連續存放。
元素間通過指向指標聯絡在一起,訪問元素必須從第一個元素開始遍歷查詢。

  • 優點:插入、刪除元素只需改變指標,快 O(1)
  • 缺點:隨機訪問慢 O(N)
  • 場景:經常插入、刪除元素

分類

  • 單向連結串列:節點僅指向下一節點,最後一個節點指向 nil
  • 雙向連結串列:每個節點有 2 個指標 pre 和 next,最後一個節點的 next 指向 nil
  • 迴圈連結串列:單鏈表 + 最後一個節點指向第一個節點

時間複雜度

  • 查詢:O(N)
  • 插入、移除:O(1)

實現細節

不帶頭結點:

帶頭結點:

頭指標:

  1. 頭指標是指向第一個結點的指標,若是連結串列中只有頭結點,則是指向頭結點的指標
  2. 頭指標具有標識作用,所以常常以頭指標冠以連結串列的名字(指標變數名)
  3. 無論連結串列是否為空,頭結點均不為空
  4. 頭指標是連結串列必要元素

頭結點:

  1. 頭結點是為了操作統一和方便而設立的,放在第一個元素結點之前,其資料域一般無意義(也可以存放表長度)
  2. 有了頭結點,對第一個元素結點的插入,刪除,其操作和其他結點操作統一了
  3. 頭結點不一定是連結串列的必要元素

Go實現

package list

const (
   MAXSIZE = 20
)

type ElemType int //元素型別

const EmptyElement = 0 //空值

/**
線性表的抽象資料型別定義

定義:由零個(空表)或多個數據元素組成的序列

ADT:
    線性表List
Data:
    線性表的資料物件集合為{a1,a2,...,an},每個元素型別為DataType。除了第一個無前驅,最後一個無後繼,
    其他每個元素都有一個位元組前驅和直接後繼結點。資料元素間關係一對一。
Operation:
    InitList(*L);//初始線性表,建立空表
    ClearList(*L);//清空線性表資料
    ListEmpty(L);//判斷列表是否為空
    ListLength(L);//獲取線性表的長度

    GetElem(L,i,* e);//獲取指定位置的元素,返回在指標元素中
    LocateElem(L,e);//查詢元素線上性表中的位置
    ListInsert(*L,i,e);//向線性表中指定位置插入元素
    ListDelete(*L, i, *e);//刪除指定位置處的元素
*/

//注意線性表中的位置不是按照陣列一樣從0開始,而是按照我們正常習慣1開始的

type MyList interface {
   //四個基本操作,初始,清空,判斷是否為空,獲取長度
   InitList()
   ClearList()
   ListEmpty() bool
   ListLength() int

   //四個元素操作,插入,刪除,兩種查詢
   GetElem(index int, e *ElemType) bool
   LocateElem(value ElemType) int
   ListInsert(index int, value ElemType) bool
   ListDelete(index int, e *ElemType) bool

   Echo()
   //Test()
}
package my_chain

import (
   "fmt"
   "my_go/dataStructure/list"
)

//鏈式儲存
//可以用頭指標用來儲存長度 也就是第0個
//但是這個有個弊端就是當儲存的不是int型別時會有問題
//所有還是新增一個欄位表示長度
//為了保持從1開始,頭指標還是不放東西
type LinkList struct {
   Head   *Node
   Length int
}

type Node struct {
   Data list.ElemType
   Next *Node
}

//初始化列表
func (l *LinkList) InitList() {
   l.Head = new(Node)
   l.Length = 0
}

//清空列表(不會清除頭結點)
func (l *LinkList) ClearList() {
   p := new(Node)
   q := l.Head //q指向第一個結點

   //釋放記憶體,其實go可以不需要這個迴圈
   for q != nil {
      p = q
      q = p.Next
      p = nil
   }
   l.Head.Next = nil
   l.Length = 0
}

//判斷是否為空
func (l *LinkList) ListEmpty() bool {
   if l.Length == 0 {
      return true
   }
   return false
}

//獲取長度
func (l *LinkList) ListLength() int {
   return l.Length
}

//查
func (l *LinkList) GetElem(index int, e *list.ElemType) bool {
   if l.Length == 0 {
      fmt.Println("獲取失敗,佇列為空")
      return false
   }
   if index < 1 || index > l.Length {
      fmt.Println("獲取失敗,位置錯誤")
      return false
   }

   j := 1
   q := l.Head.Next
   for q != nil && j < index {
      q = q.Next
      j++
   }

   //有了這一步其實開頭的判斷可以去掉
   if q == nil || j > index {
      return false
   }

   *e = q.Data
   return true
}

//按照元素進行查詢,獲取索引
func (l *LinkList) LocateElem(value list.ElemType) int {

   if l.Length == 0 {
      fmt.Println("獲取失敗,佇列為空")
      return 0
   }

   j := 0
   q := l.Head.Next
   for q != nil {
      j++
      if q.Data == value {
         break
      }
      q = q.Next
   }

   if j >= list.MAXSIZE {
      return 0
   }

   return j
}

//按照索引進行插入資料
func (l *LinkList) ListInsert(index int, value list.ElemType) bool {

   if l.Length == list.MAXSIZE { //滿了
      fmt.Println("插入失敗,佇列已滿")
      return false
   }
   if index < 1 || index > l.Length+1 {
      fmt.Println(fmt.Sprintf("插入失敗,位置錯誤:%d", index))
      return false
   }

   front := l.Head

   //找到插入位置的前驅
   for j := 1; j < index; j++ {
      front = front.Next
   }

   //新建節點,加入連結串列
   n := new(Node)
   n.Next = front.Next
   n.Data = value
   front.Next = n

   l.Length++

   return true
}

//刪
func (l *LinkList) ListDelete(index int, e *list.ElemType) bool {
   if l.Length == 0 {
      fmt.Println("獲取失敗,佇列為空")
      return false
   }
   if index < 1 || index > l.Length {
      fmt.Println("獲取失敗,位置錯誤")
      return false
   }

   j := 1
   front := l.Head

   //找到索引的直接前驅
   for front.Next != nil && j < index {
      front = front.Next
      j++
   }

   if front.Next == nil || j > index {
      return false
   }

   //開始刪除
   tmp := front.Next     //記錄要刪除的
   *e = tmp.Data         //返回刪除節點的資料
   front.Next = tmp.Next //前驅節點直接指向後繼節點,就跳過了要刪除的節點
   //free(tmp)

   l.Length--

   return true
}

//輸出
func (l *LinkList) Echo() {
   //遍歷的寫法
   curItem := l.Head.Next
   for i := 0; i < l.Length; i++ {
      fmt.Print(curItem.Data, " ")
      curItem = curItem.Next
   }
   fmt.Println()
}

測試

package my_chain

import (
   "fmt"
   "my_go/dataStructure/list"
   "testing"
)

func Test(t *testing.T) {
   fmt.Println("測試開始")

   my_list := new(LinkList)
   my_list.InitList()

   for i := 1; i <= 10; i++ {
      my_list.ListInsert(i, list.ElemType(i*i+1))
      my_list.Echo()
   }

   fmt.Println("第5個這裡插入256")
   my_list.ListInsert(5, 256)
   my_list.Echo()
   my_list.ListInsert(199, 99)

   var e list.ElemType

   my_list.ListDelete(1, &e)
   fmt.Println("刪除頭元素:", e)
   my_list.Echo()

   my_list.ListDelete(my_list.ListLength(), &e)
   fmt.Println("刪除尾元素:", e)
   my_list.Echo()

   my_list.GetElem(6, &e)
   fmt.Println("獲取第6個:", e)

   fmt.Println("256的位置:", my_list.LocateElem(256))

   fmt.Println("長度:", my_list.ListLength())

   fmt.Println("開始清空")
   my_list.ClearList()
   if my_list.ListEmpty() {
      fmt.Println("已清空")
      my_list.Echo()
   }

   fmt.Println("測試完成")
}