問題:爲何數組下標是從0開始,而不是從1開始;java
數組是一種線性表數據結構,它用一組連續的內存空間來存儲一組相同類型的數據。算法
線性表結構:數據排成一條線同樣的結構,每一個線性表上最多隻有前和後兩個方向,除數組外,鏈表、隊列、棧也是線性結構。編程
非線性結構:好比:二叉樹、圖、堆等,在非線性結構中,數據之間並非簡單的先後關係。數組
連續的內存空間和相同類型的數據:正式由於這兩個特性,纔有了它的一大特性:「隨機訪問」;缺點:在數組中要進行刪除或者插入操做須要進行大量的遷移工做,效率低下;數據結構
關於數組的隨機訪問:例如一個長度爲10
的int
類型的數組 int a[] = new int[10];
在下圖中,計算機給數組 a[10]
分配了一起連續的內存空間 1000~1039;其中,內存的首地址 base_address=1000
編程語言
咱們知道,計算機會給每個內存單元分配一個地址,經過地址來訪問到內存中存儲的數據;當計算機須要隨機訪問數組中的元素時,會根據以下的公式找出該元素存儲的內存地址:優化
a[i]_address = base_address + i * data_type_size
複製代碼
其中 data_type_size
表示數據中每一個元素的大小。在當前例子中存儲的爲 int
類型,因此 data_type_size
的大小爲 4
。spa
假設數組arr
的長度爲n
,如今咱們要在數組arr
的第k
個位置插入一個元素,爲了把 k
這個位置騰出來給新的元素,那麼咱們須要把 k~n
這部分元素都順序日後移一位。code
若是是插入到數組的末尾,那麼咱們就不須要對移動數據,這時候時間複雜度爲O(1)
。若是在數組的開頭插入元素,那全部的數據都要日後移一位,這時候的時間複雜度爲O(n)
。那麼平均複雜度則爲O(n)
。
若是咱們插入的元素是有序的,那麼咱們就必須按照上邊的操做依次將數組日後移動一位。可是若是數組中的元素沒有任何規律,數組只是被當作一個存儲數據的集合。在這種狀況下,若是想要將某個數據插入到k
這個位置,爲了不大量的數據遷移,最簡單的方法就是,直接將第k
位的元素放到最後,把要插入的元素直接放到k
的位置。
例如:現有數組 a[10]
存儲了五個元素:a、b、c、d、e
;咱們如今要將元素x
插入到第三個位置,咱們只須要把c
放到a[5]
,將a[2]
賦值爲x
便可;最終獲得的數組中的元素爲:a、b、x、d、e、c
;利用這種處理技巧,在特定場景下,在第 k 個位置插入一個元素的時間複雜度就會降爲 O(1)
。
和插入操做相似,當咱們要刪除數組中的某個元素時,要對數據進行搬移操做,否則數據中間會出現空洞,內存就不連續了;
和插入相似,若是刪除數組末尾的數據,則最好狀況時間複雜度爲 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
這幾個數據會被搬移三次,咱們能夠先記錄下已經刪除的數據。每次的刪除操做並非真正地搬移數據,只是記錄數據已經被刪除。當數組沒有更多空間存儲數據時,咱們再觸發執行一次真正的刪除操做,這樣就大大減小了刪除操做致使的數據搬移。
這是 JVM 標記清除垃圾回收算法的核心思想
如今咱們來思考開篇的問題:爲何大多數編程語言中,數組要從 0
開始編號,而不是從 1
開始呢?
從數組存儲的內存模型上來看,「下標」最確切的定義應該是「偏移(offset)」
。前面也講到,若是用 a 來表示數組的首地址,a[0]
就是偏移爲 0
的位置,也就是首地址,a[k]
就表示偏移 k
個 data_type_size
的位置,因此計算 a[k]
的內存地址只須要用這個公式:
a[k]_address = base_address + k * data_type_size
複製代碼
可是,若是數組從 1 開始計數,那咱們計算數組元素 a[k] 的內存地址就會變爲:
a[k]_address = base_address + (k - 1) * data_type_size
複製代碼
對比兩個公式,咱們不難發現,從 1
開始編號,每次隨機訪問數組元素都多了一次減法運算,對於 CPU 來講,就是多了一次減法指令。
數組做爲很是基礎的數據結構,經過下標隨機訪問數組元素又是其很是基礎的編程操做,效率的優化就要儘量作到極致。因此爲了減小一次減法操做,數組選擇了從 0 開始編號,而不是從 1 開始。
思考: