資料結構與演算法-學習筆記(二)

清水之靈發表於2018-10-22

陣列

定義:陣列(Array)是一種線性表資料結構。它用一組連續的記憶體空間,來儲存一組具有相同型別的資料(不同語言對陣列的定義標準不同,大多數語言要求同型別,而在JavaScript中,陣列中元素可以是不同型別)。

解釋:
1. 線性表
顧名思義,就是資料排成一條線一樣的結構。每個線性表上的資料最多有前後兩個方向。除了陣列之外,連結串列、佇列、棧等也是線性結構。與之對應的就是非線性表:資料之間不是簡單的前後關係。
複製程式碼

資料結構與演算法-學習筆記(二)

資料結構與演算法-學習筆記(二)

 2. 連續的記憶體空間和相同型別的資料
 陣列內的元素所佔用的記憶體空間相同且連續,正因為這兩點限制,陣列才有了——隨機訪問——的特性。我們知道計算機會給每個記憶體單元(1B)分配一個記憶體地址,通過地址來訪問。當計算機要隨機訪問陣列中的某個元素時,首先得找出該元素的記憶體地址,通過下面的定址公式來計算:a[i]_address = base_address + i * data_type_size
 要注意的點:陣列適合查詢,查詢的時間複雜度為O(1)。是不準確的。正確的說法:陣列支援隨機訪問,根據下標的隨機訪問時間複雜度是O(1),僅需執行定址一行定址公式就可找到。
複製程式碼

資料結構與演算法-學習筆記(二)

低效的插入刪除

插入:

為保證陣列資料的連續性,在第k個位置上插入資料,如要保證陣列元素順序不變,則k~n這部分資料都需要往後移一位,此時k=n-1,最好的時間複雜度O(1),k=0,最壞的時間複雜度O(n)。每個位置插入的概率是一樣的1/n,所以平均時間複雜度(1+2+...+n)/n = O(n)。 如果不需要保證順序:

資料結構與演算法-學習筆記(二)
此時O(1)。

刪除:

同理插入每刪除一次,保證順序不變,則平均時間複雜度O(n)。如果在某些場景中,不一定要保證陣列中資料的連續性。則可以將多次刪除操作集中一次執行。每次要刪除的資料都進行標記,當陣列沒有更多空間儲存資料時,觸發一次真正的刪除操作。

警惕陣列的訪問越界問題:

這段C語言程式碼:C中只要不訪問受限記憶體,所有記憶體空間都可以自由訪問。

資料結構與演算法-學習筆記(二)
這段程式碼可能由於編譯器的不同,造成結果也是不同的。1. 無限迴圈:根據陣列的定址公式可以知道陣列a[0]~a[n-1]地址是越來越大的,棧中是從高地址向低地址增長的,因此記憶體排布應該是i,a[2],a[1],a[0]。訪問a[3]也就等於i(前提都是相同型別資料)。2.列印4次(xcode):編譯器開啟堆疊保護(防止溢位漏洞如下圖,通過溢位修改記憶體中其他區域)
資料結構與演算法-學習筆記(二)
開啟保護後,可能會改變函式棧中變數的記憶體分配:陣列在其他變數之前,先分配
資料結構與演算法-學習筆記(二)
資料結構與演算法-學習筆記(二)
這樣陣列越界就不會覆蓋其他變數了。除了堆疊保護會影響結果外,CPU的位元組對其也會影響結果。 C語言需要開發者注意陣列越界問題,其他語言如OC,執行時會報錯。

陣列下標為什麼從0開始

解釋:

  1. 下標確切定義應該是‘偏移量’,a[1]:偏移了一個位置。這樣定址公式:a[k]_address = base_address + k * type_size 如果從1開始:a[k]_address = base_address + (k-1)*type_size多了一步計算k-1。
  2. 歷史原因,最開始就設計從0開始,後邊的語言就一直沿用了。

相關文章