我們的陣列

K戰神發表於2018-11-22

上一篇,我們講完演算法複雜度,接下來我們來見一見我們非常熟悉的朋友--陣列。

我們平時使用的陣列是資料型別,但是陣列不僅僅是資料型別更是一種基礎的資料結構。

 

陣列的定義

我們來看看陣列定義:分類連續的記憶體空間來儲存相同型別集合的線性表資料結構。

線性表+連續記憶體+相同型別 著三個特性合併出了陣列的必殺技:隨機訪問。

那麼陣列是怎麼實現下標隨機訪問的呢?

 

隨機訪問

根據頭節點和固定型別具體長度,就可以實現隨機訪問。

search[i]_address = base_address + i*data_type_size

雖然對於訪問來講,下標隨機訪問的複雜度為O(1)

 

插入和刪除

但是對於插入和刪除來說日子就不是那麼好過了。

因為陣列要保證記憶體的連續性這個特性,所以插入和刪除都是比較低效的,我們具體來看看插入和刪除。

 

插入:我們在任意節點前插入,那麼後面的元素必須後移一位來保證連續性

假設我們有一個陣列為 int[n] =[1,2,3,4,5,......,n]

我們在左側頂頭插入呢?後面的全部都移動,所以算個複雜度為O(n),我們上面一篇提到過的最差情況時間複雜度

與此對應的是末尾插入,完美,啥都不用幹,這就不用說了就是 最好情況時間複雜度。

那麼如果在中間任意位置出現呢?多種情況,所以就是平均情況時間複雜度

歸納一下:

陣列左側頂頭插入元素,最壞情況時間複雜度 O(n)

陣列末尾追加插入元素:最好情況時間複雜度 O(1)

陣列中間情況插入元素,平均情況時間複雜度 O(n)

 

插入平均時間複雜度

我們這裡來驗證一下,陣列下標隨機訪問的平均時間複雜度為什麼是O(n):

對於一個  int[n] = [1,2,3,4,5,......,n]

我從頭依次進行插入,那麼對應的移動陣列次數是

(n-1) 

(n-2) 

...

0

 

對於平均時間複雜度來講,可以插入的空前後兩端2個+中間n-1,即 n+1 種情況

這裡的每個插入點發生的情況概率是一樣的都是  1 / (n+1)

所以,平均時間複雜度為移動次數*發生的概率

n * [1/(n+1)]

(n-1) * [1/(n+1)]

...

1 * [1/(n+1)]

0 * [1/(n+1)]

簡化一下:(0+1+2+3...n)  /  (n+1)   => [(n-1)/2] * n + n / (n+1)

演算法複雜度去除係數、常量、低階,這裡的平均情況時間複雜度是O(n)

日常開發中,如果我們遇到有序陣列,那我們必須在插入的同時,後移後面的所有位置。

但是如果陣列對排序不敏感,那麼我們的插入可以在後面追加,這樣避免了移動陣列,複雜度就是O(1)

 

刪除:如果我們刪除陣列中的元素,為了保持陣列的連續性,依然需要搬運陣列元素。

所以刪除操作和插入操作的最好、最壞、平均複雜度是對應的。

其實我們也可以加一個刪除標記,在空閒的時候進行刪除重排序。

 

訪問越界問題

 

對於以上這段C程式來說,上面這段程式碼,我們在書寫的時候,沒有仔細檢查,使得<寫成了<=,這樣就會產生了越界問題。

前提條件:

1、因為不同的CPU架構不同的編譯器會有不同的記憶體分配策略:從高地址向低地址增長,或者從低地址向高地址增長。也就是大小端問題。

2、首先壓棧的是 i ,之後是陣列arr。所以 i 的記憶體地址比arr中的元素高,並且 i 和arr中的元素相鄰,並且型別相同,也就是子節是對齊的。

 

所以當 arr [ 3 ] 這個地址越界訪問到了 相鄰的同型別的 i 這個變數的記憶體地址。arr[3] =0,其實也就是 i = 0,好吧,又從頭開始了,無限迴圈。

 

那麼為什麼我們平時使用的語言即便是越界也會程式異常終止。其實這是編譯器做的工作,編譯器不同,記憶體申請方式也不同。

我們平時的編譯器已經幫我們做好了程式碼檢查等工作。所以避免了越界的情況。

 

C# []、Array、ArrayList、List<T>

首先 [] ,就是我們宣告簡單的陣列,因為陣列是連續的線性資料結構,所以很多操作微軟又給做了封裝。

Array ,就是微軟對陣列進行的封裝類。下面又進一步衍生出了動態陣列。

ArrayList,就是動態陣列,裡面封裝了陣列很多的操作。尤其是動態擴容。泛型之後,又出了泛型列表。

List<T>是動態陣列的泛型版本,避免了頻繁的裝箱拆箱,效率較高,也是我們平時使用最頻繁的一種。

其實微軟已經開源了.NET Framework 原始碼,詳細可檢視此地址

還有一種方式,可以使用遠端除錯原始碼,遠端下載原始碼到本地,進行原始碼除錯:

工具  -》 除錯 -》勾選 啟用原始碼單步除錯

當然速度上肯定會比原生程式碼慢。

以上就是今天的內容。

相關文章