上一篇,咱們講完算法複雜度,接下來咱們來見一見咱們很是熟悉的朋友--數組。html
咱們平時使用的數組是數據類型,可是數組不只僅是數據類型更是一種基礎的數據結構。算法
咱們來看看數組定義:分類連續的內存空間來存儲相同類型集合的線性表數據結構。數組
線性表+連續內存+相同類型 着三個特性合併出了數組的必殺技:隨機訪問。數據結構
那麼數組是怎麼實現下標隨機訪問的呢?架構
根據頭節點和固定類型具體長度,就能夠實現隨機訪問。工具
search[i]_address = base_address + i*data_type_sizespa
雖然對於訪問來說,下標隨機訪問的複雜度爲O(1)3d
可是對於插入和刪除來講日子就不是那麼好過了。調試
由於數組要保證內存的連續性這個特性,因此插入和刪除都是比較低效的,咱們具體來看看插入和刪除。htm
插入:咱們在任意節點前插入,那麼後面的元素必須後移一位來保證連續性。
假設咱們有一個數組爲 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
(n-1)
(n-2)
...
1
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程序來講,上面這段代碼,咱們在書寫的時候,沒有仔細檢查,使得<寫成了<=,這樣就會產生了越界問題。
前提條件:
一、由於不一樣的CPU架構不一樣的編譯器會有不一樣的內存分配策略:從高地址向低地址增加,或者從低地址向高地址增加。也就是大小端問題。
二、首先壓棧的是 i ,以後是數組arr。因此 i 的內存地址比arr中的元素高,而且 i 和arr中的元素相鄰,而且類型相同,也就是子節是對齊的。
因此當 arr [ 3 ] 這個地址越界訪問到了 相鄰的同類型的 i 這個變量的內存地址。arr[3] =0,其實也就是 i = 0,好吧,又從頭開始了,無限循環。
那麼爲何咱們平時使用的語言即使是越界也會程序異常終止。其實這是編譯器作的工做,編譯器不一樣,內存申請方式也不一樣。
咱們平時的編譯器已經幫咱們作好了代碼檢查等工做。因此避免了越界的狀況。
首先 [] ,就是咱們聲明簡單的數組,由於數組是連續的線性數據結構,因此不少操做微軟又給作了封裝。
Array ,就是微軟對數組進行的封裝類。下面又進一步衍生出了動態數組。
ArrayList,就是動態數組,裏面封裝了數組不少的操做。尤爲是動態擴容。泛型以後,又出了泛型列表。
List<T>是動態數組的泛型版本,避免了頻繁的裝箱拆箱,效率較高,也是咱們平時使用最頻繁的一種。
其實微軟已經開源了.NET Framework 源碼,詳細可查看此地址。
還有一種方式,可使用遠程調試源碼,遠程下載源碼到本地,進行源碼調試:
工具 -》 調試 -》勾選 啓用源代碼單步調試
固然速度上確定會比本地代碼慢。
以上就是今天的內容。