線性表
定義
線性表:零個或多個數據元素的有限序列。
序列
元素之間是有順序
,
若存在多個元素,則第一個元素無前驅元素,最後一個元素無後繼元素,每個元素有且僅有一個前驅和後繼元素
。
而且元素類型也相同!
有限
線性表強調錶裏的元素有限。
長度
線性表元素的個數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)。
如下圖所示:
頭結點與頭指針的異同:
- 頭指針
-
鏈表指向第一個結點的指針,若鏈表有頭結點,則頭指針爲指向頭結點的指針!!!
所以上面的2張圖,其實是錯誤的,正確的圖應該是下面這樣的:
-
頭指針具有標示作用,所以常常冠以鏈表的的名字。
-
空鏈表的頭指針指向Null。
-
無論鏈表是否爲空,頭指針均不爲空。
頭指針是鏈表的必要元素!
- 頭結點
-
頭結點是爲了操作的統一和方便而設立的,放在第一個元素的結點前面,其數據域一般無意義。
-
有了頭結點,對第一元素的結點前的插入和刪除操作,就和其他結點一致了。
-
頭結點不是鏈表的必要元素!