數據結構與算法-day2-數組

個人理解

數組是一種線性表結構,下標從0開始,用來存儲 同一類型 的數據,而且 在內存空間上是連續的編程

正是由於他的內存空間是連續的,因此增刪時纔會須要整塊整塊的移動

線性表就是數據排成像一條線同樣的結構。每一個線性表上的數據最多隻有前和後兩個方向

數據在非線性表裏不是簡單先後關係


數組如何實現隨機訪問

數組支持隨機訪問,根據下標隨機訪問的時間複雜度爲 O(1)。數組

咱們知道,計算機會給每一個內存單元分配一個地址,計算機經過地址來訪問內存中的數據。當計算機須要隨機訪問數組中的某個元素時,它會首先經過下面的尋址公式,計算出該元素存儲的內存地址:數據結構

a[i]_address = base_address + i * data_type_size性能

其中 data_type_size 表示數組中每一個元素的大小。咱們舉的這個例子裏,數組中存儲的是 int 類型數據,因此 data_type_size 就爲 4 個字節。優化

注意:在這裏i指的是下標,更準確的說是偏移量,a[0] 就是偏移爲 0 的位置,也就是首地址,a[k] 就表示偏移 k 個 type_size 的位置,因此計算 a[k] 的內存地址只須要用這個公式:3d

a[i]_address = base_address + i * data_type_sizecode

可是,若是數組從 1 開始計數,那咱們計算數組元素 a[k] 的內存地址就會變爲:cdn

a[k]_address = base_address + (k-1)*type_sizeblog

對比兩個公式,咱們不難發現,從 1 開始編號,每次隨機訪問數組元素都多了一次減法運算,對於 CPU 來講,就是多了一次減法指令內存

數組做爲很是基礎的數據結構,經過下標隨機訪問數組元素又是其很是基礎的編程操做,效率的優化就要儘量作到極致**。因此爲了減小一次減法操做,數組選擇了從 0 開始編號,而不是從 1 開始。**

低效的「插入」和「刪除」

前面概念部分咱們提到,數組爲了保持內存數據的連續性,會致使插入、刪除這兩個操做比較低效。如今咱們就來詳細說一下,究竟爲何會致使低效?又有哪些改進方法呢?

咱們先來看插入操做。

假設數組的長度爲 n,如今,若是咱們須要將一個數據插入到數組中的第 k 個位置。爲了把第 k 個位置騰出來,給新來的數據,咱們須要將第 k~n 這部分的元素都順序地日後挪一位。那插入操做的時間複雜度是多少呢?

  • 若是在數組的末尾插入元素,那就不須要移動數據了,這時的時間複雜度爲 O(1)。但若是在數組的開頭插入元素,那全部的數據都須要依次日後移動一位,因此最壞時間複雜度是O(n)。 由於咱們在每一個位置插入元素的機率是同樣的,因此平均狀況時間複雜度爲 (1+2+…n)/n=O(n)

  • 可是,若是數組中存儲的數據並無任何規律,數組只是被看成一個存儲數據的集合。在這種狀況下,若是要將某個數組插入到第 k個位置,爲了不大規模的數據搬移,咱們還有一個簡單的辦法就是,直接將第 k 位的數據搬移到數組元素的最後把新的元素直接放入第 k 個位置

爲了更好地理解,咱們舉一個例子。假設數組 a[10] 中存儲了以下 5 個元素:a,b,c,d,e。 咱們如今須要將元素 x 插入到第 3 個位置。咱們只須要將 c 放入到 a[5],將 a[2] 賦值爲 x 便可。最後,數組中的元素以下: a,b,x,d,e,c。

咱們再來看刪除操做。

跟插入數據相似,若是咱們要刪除第 k 個位置的數據,爲了內存的連續性,也須要搬移數據,否則中間就會出現空洞,內存就不連續了。

和插入相似,若是刪除數組末尾的數據,則最好狀況時間複雜度爲 O(1);若是刪除開頭的數據,則最壞狀況時間複雜度爲** O(n)**;平均狀況時間複雜度也爲 O(n)

實際上,在某些特殊場景下,咱們並不必定非得追求數組中數據的連續性。若是咱們將屢次刪除操做集中在一塊兒執行,刪除的效率是否是會提升不少呢?

咱們繼續來看例子。數組 a[10] 中存儲了 8 個元素:a,b,c,d,e,f,g,h。如今,咱們要依次刪除 a,b,c 三個元素。 爲了不 d,e,f,g,h這幾個數據會被搬移三次,咱們能夠先記錄下已經刪除的數據。每次的刪除操做並非真正地搬移數據,只是記錄數據已經被刪除。當數組沒有更多空間存儲數據時,咱們再觸發執行一次真正的刪除操做,這樣就大大減小了刪除操做致使的數據搬移。

容器可否徹底替代數組?

針對數組類型,不少語言都提供了容器類,好比 Java 中的 ArrayList。在項目開發中,何時適合用數組,何時適合用容器呢?

ArrayList 最大的優點就是能夠將不少數組操做的細節封裝起來。好比前面提到的數組插入、刪除數據時須要搬移其餘數據等。另外,它還有一個優點,就是支持動態擴容

數組自己在定義的時候須要預先指定大小,由於須要分配連續的內存空間。若是咱們申請了大小爲 10 的數組,當第 11 個數據須要存儲到數組中時,咱們就須要從新分配一塊更大的空間,將原來的數據複製過去,而後再將新的數據插入。

若是使用 ArrayList,咱們就徹底不須要關心底層的擴容邏輯,ArrayList 已經幫咱們實現好了。每次存儲空間不夠的時候,它都會將空間自動擴容爲 1.5 倍大小。

那麼何時用數組更合適呢

  1. Java ArrayList 沒法存儲基本類型,好比 int、long,須要封裝爲 Integer、Long 類,而 自動裝拆箱則有必定的性能消耗,因此若是特別關注性能,或者但願使用基本類型,就能夠選用數組。

  2. 若是數據大小事先已知,而且對數據的操做很是簡單,用不到 ArrayList 提供的大部分方法,也能夠直接使用數組。

  3. 當要表示多維數組時,用數組每每會更加直觀。好比 Object[][] array;而用容器的話則須要這樣定義:ArrayList<ArrayList> array

相關文章
相關標籤/搜索