《大話數據結構》學習筆記 —— 03 線性表(golang實現)


線性表


定義

線性表:零個或多個數據元素的有限序列。


序列

元素之間是有順序

若存在多個元素,則第一個元素無前驅元素,最後一個元素無後繼元素,每個元素有且僅有一個前驅和後繼元素

而且元素類型也相同!


有限

線性表強調錶裏的元素有限。


長度

線性表元素的個數n(n≥0)定義爲線性表的長度,

當n=0時,稱爲空表。





線性表的順序存儲結構


定義

用一段地址連續的存儲單元依次存儲線性表的數據元素。


關鍵詞:


  • 起始位置

存儲這個線性表需要在內存中找一塊地,所以這個地的起始位置很重要。


  • 最大存儲容量

我們需要估算線性表的最大存儲容量,實現的時候,我們建立一個固定長度的數組,數組的長度就是這個線性表最大存儲容量。

最大存儲容量在分配後一般是不變的,現在很多的高級語言都可以動態調整數組長度,但是這會帶來性能的損耗。


  • 長度

線性表當前的長度,也就是當前線性表的元素個數,隨着線性表插入和刪除操作的進行,這個當前長度是變化的

在任意時刻,線性表的長度應該小於等於最大存儲容量



代碼實現

package ArrayList

import (
	"errors"
	"fmt"
)

// 接口
type List interface {

	// 數組大小
	Size() int

	// 獲取
	Get(index int) (interface{}, error)

	// 修改
	Set(index int, newval interface{}) error

	// 插入
	Insert(index int, newval interface{}) error

	// 追加
	Append(newval interface{})

	// 清空
	Clear()

	// 刪除
	Delete(index int) error

	// 輸出字符串
	String() string
}

// 數據結構
type ArrayList struct {

	// 數據
	data []interface{}

	// 數組大小
	TheSize int
}

// 初始化
func NewArrayList() *ArrayList {

	// 初始化結構體
	list := new(ArrayList)

	// 開闢內存空間
	list.data = make([]interface{}, 0, 10)

	// 數組大小設爲0
	list.TheSize = 0

	return list
}

// 數組大小
func (list *ArrayList) Size() int {
	return list.TheSize
}

// 獲取數據
func (list *ArrayList) Get(index int) (interface{}, error) {

	// 傳入無效下標
	if index < 0 || index >= list.TheSize {
		return nil, errors.New("索引越界")
	}

	// 正常返回
	return list.data[index], nil
}

// 修改數據
func (list *ArrayList) Set(index int, newval interface{}) error {

	// 傳入無效下標
	if index < 0 || index >= list.TheSize {
		return errors.New("索引越界")
	}

	// 設置值
	list.data[index] = newval

	return nil
}

// 追加
func (list *ArrayList) Append(newval interface{}) {
	list.data = append(list.data, newval)
	list.TheSize++
}

// 插入
func (list *ArrayList) Insert(index int, newval interface{}) error {

	// 傳入無效下標
	if index < 0 || index >= list.TheSize {
		return errors.New("索引越界")
	}

	// 檢測是否內存分配空間是否滿了
	list.checkIsFull()

	// 內存移動一位
	list.data = list.data[:list.TheSize+1]

	// 從後往前移動,大於index的元素將值賦值給下一位
	for i := list.TheSize; i > index; i-- {
		list.data[i] = list.data[i-1]
	}

	// 直接插入數據
	list.data[index] = newval

	// 數組大小加上1
	list.TheSize++

	return nil
}

// 判斷是否滿了
func (list *ArrayList) checkIsFull() {

	// cap可以獲取切片分配的空間大小
	if list.TheSize == cap(list.data) {

		// 開闢兩倍內存空間,注意第二個參數指定的是切片的長度,第三個參數是用來指定預留的空間長度
		newData := make([]interface{}, 2*list.TheSize, 2*list.TheSize)

		// 拷貝內容
		copy(newData, list.data)

		// 重新賦值
		list.data = newData
	}
}

// 刪除
func (list *ArrayList) Delete(index int) error {

	// 將刪除點前後的元素連接起來
	list.data = append(list.data[:index], list.data[index+1:]...)

	// 數組大小減去1
	list.TheSize--

	return nil
}

// 清空
func (list *ArrayList) Clear() {

	// 重新開闢內存空間
	list.data = make([]interface{}, 0, 10)

	// 數組大小設爲0
	list.TheSize = 0
}

// 返回字符串
func (list *ArrayList) String() string {
	// Sprint將內容生成字符串
	return fmt.Sprint(list.data)
}



時間複雜度

線性表的順序存儲結構,在讀數據時,時間複雜度爲O(1);

而在進行插入、刪除操作時,時間複雜度爲O(n)。



優缺點

優點:

  • 無需爲表中元素間的邏輯關係增加額外的存儲空間
  • 可以快速存取表中任一位置的元素

缺點:

  • 插入和刪除操作需要移動大量元素
  • 當線性表長度變化較大時,難以確定最大存儲容量
  • 造成存儲空間“碎片”





線性表的鏈式存儲結構


定義

用一組任意的存儲單元存儲線性表的元素,這組存儲單元可以存在內存未被佔用的任意位置。


關鍵詞:


  • 結點

存儲數據元素的域,稱爲數據域

存儲直接後續位置的域,稱爲指針域;指針域中存儲的信息稱爲指針或者

數據域指針域這兩部分信息組成數據元素的存儲映像,稱爲結點


  • 頭指針

鏈表中的第一個結點的存儲位置稱爲頭指針,最後一個結點指針爲空(Null)。

如下圖所示:

在這裏插入圖片描述





頭結點與頭指針的異同:


  • 頭指針
  1. 鏈表指向第一個結點的指針,若鏈表有頭結點,則頭指針爲指向頭結點的指針!!!

    所以上面的2張圖,其實是錯誤的,正確的圖應該是下面這樣的:

在這裏插入圖片描述

  1. 頭指針具有標示作用,所以常常冠以鏈表的的名字。

  2. 空鏈表的頭指針指向Null。

  3. 無論鏈表是否爲空,頭指針均不爲空。頭指針是鏈表的必要元素!


  • 頭結點
  1. 頭結點是爲了操作的統一和方便而設立的,放在第一個元素的結點前面,其數據域一般無意義。

  2. 有了頭結點,對第一元素的結點前的插入和刪除操作,就和其他結點一致了。

  3. 頭結點不是鏈表的必要元素!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章