今天要介紹的主角就是-數組,數組也是數據呈線性排列的一種數據結構。與前一節中的鏈表不一樣,在數組中,訪問數據十分簡單,而添加和刪除數據比較耗工夫。這和什麼是數據結構那篇文章中講到的姓名按拼音順序排列的電話簿相似。程序員
如上就是數組的概念圖,Blue、Yellow、Red 做爲數據存儲在數組中,其中 a 是數組的名字,後面 [] 中的數字表示該數據是數組中的第幾個數據,該數字也就是數組下標,下標從 0 開始計數,好比 Red 就是數組 a 的第 2 個數據。算法
那麼爲何許多編程語言中的數組都從 0 開始編號的呢?先別急,能夠先本身思考下,將會在文末進行講解。編程
從圖中能夠看出來,數組的數據是按順序存儲在內存的連續空間內的。數組
因爲數據是存儲在連續空間內的,因此每一個數據的內存地址(在內存上的位置)均可以經過數組下標算出,咱們也就能夠藉此直接訪問目標數據,也就是隨機訪問。數據結構
好比如今咱們想要訪問 Red,若是是鏈表的話,只能使用指針就只能從頭開始查找,但在數組中,只須要指定 a[2],便能直接訪問 Red。編程語言
可是,若是想在任意位置上添加或者刪除數據,數組的操做就要比鏈表複雜多了。這裏咱們嘗試將 Green 添加到第 2 個位置上。ide
首先,在數組的末尾確保須要增長的存儲空間。學習
爲了給新數據 Green 騰出位置,要把已有數據一個個移開,首先把 Red 日後移。優化
而後把 Yellow 日後移。線程
最後在空出來的位置上寫入 Green。
添加數據的操做就完成了。
反過來,若是想要刪除 Green 呢?
首先,刪掉目標數據 Green。
而後把後面的數據一個個往空位移,先把 Yellow 往前移。
接下來移動 Red。
最後再刪掉多餘的空間,這樣一來 Green 便被刪掉了。
這裏講解一下對數組操做所花費的運行時間,假設數組中有 n 個數據,因爲訪問數據時使用的是隨機訪問(經過下標可計算出內存地址),因此須要的運行時間僅爲恆定的 O(1)。
經過數組下標計算出內存地址的尋址公式以下:
a[i]_address = base_address + i * data_type_size
其中 base_address 爲內存塊的首地址,data_type_size 表示數組中每一個元素的大小。
但另外一方面,想要向數組中添加新數據時,必須把目標位置後面的數據一個個移開。因此,若是在數組頭部添加數據,就須要 O(n) 的時間,刪除操做同理。
在鏈表和數組中,數據都是線性地排成一列。在鏈表中訪問數據較爲複雜,添加和刪除數據較爲簡單;而在數組中訪問數據比較簡單,添加和刪除數據卻比較複雜。
咱們能夠根據哪一種操做較爲頻繁來決定使用哪一種數據結構。
最後,讓咱們一塊兒來思考下剛開始提到的問題:爲何不少編程語言中數組都從 0 開始編號?
從數組存儲的內存模型上來看,「下標」最確切的定義應該是「偏移(offset)」。若是用 a 來表示數組的首地址,a[0] 就是偏移爲 0 的位置,也就是首地址,a[k] 就表示偏移 k 個 type_size 的位置,因此計算 a[k] 的內存地址只須要用這個公式:
a[k]_address = base_address + k * type_size
可是,若是數組從 1 開始計數,那咱們計算數組元素 a[k] 的內存地址就會變爲:
a[k]_address = base_address + (k-1)*type_size
對比兩個公式,能夠發現,從 1 開始編號,每次隨機訪問數組元素都多了一次減法運算,對於 CPU 來講,就是多了一次減法指令。
數組做爲很是基礎的數據結構,經過下標隨機訪問數組元素又是其很是基礎的編程操做,效率的優化就要儘量作到極致。因此爲了減小一次減法操做,數組選擇了從 0 開始編號,而不是從 1 開始。
除此以外還有歷史緣由,C 語言設計者用 0 開始計數數組下標,以後的 Java、JavaScript 等高級語言都效仿了 C 語言,或者說,爲了在必定程度上減小 C 語言程序員學習 Java 的學習成本,所以繼續沿用了從 0 開始計數的習慣。實際上,不少語言中數組也並非從 0 開始計數的,好比 Matlab。甚至還有一些語言支持負數下標,好比 Python。
這篇文章主要介紹了數據結構中經常使用的數組,數組用一塊連續的內存空間,來存儲相同類型的一組數據,最大的特色就是支持隨機訪問,但插入、刪除操做也所以變得比較低效,平均狀況時間複雜度爲 O(n)。有一種高效的查找算法是二分查找法,就是利用了數組隨機訪問的特性。
總得來講,數組適用於多操做多、寫操做少的場景,和咱們上一篇文章中的鏈表正好相反。
參考
《個人第一本算法書》
數據結構與算法之美
完
●什麼是數據結構?
●什麼是鏈表?
●實現線程的方式到底有幾種?
武培軒有幫助?在看,轉發走一波喜歡做者