數組——數據結構與算法之美【基礎篇】

什麼是數組

線性表

數組是一種線性表數據結構,它用一組連續的內存空間,來存儲一組具有相同類型的數據
每個線性表上的數據最多隻有前後兩個方向。其實除了數組,鏈表、隊列、棧等也是線性表結構。

如圖所示

連續的內存空間和相同類型的數據

說到數據的訪問,那你知道數組是如何實現根據下標隨機訪問數組元素的嗎?我們拿一個長度爲 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 個字節。這個公式非常簡單,我就不多做解釋了。
這裏我要特別糾正一個“錯誤”。我在面試的時候,常常會問數組和鏈表的區別,很多人都回答說,“鏈表適合插入、刪除,時間複雜度 O(1);數組適合查找,查找時間複雜度爲 O(1)”。
實際上,這種表述是不準確的。數組是適合查找操作,但是查找的時間複雜度並不爲 O(1)。即便是排好序的數組,你用二分查找,時間複雜度也是 O(logn)。所以,正確的表述應該是,數組支持隨機訪問,根據下標隨機訪問的時間複雜度爲 O(1)。

爲什麼數組要從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 開始。
不過我認爲,上面解釋得再多其實都算不上壓倒性的證明,說數組起始編號非 0 開始不可。所以我覺得最主要的原因可能是歷史原因。
C 語言設計者用 0 開始計數數組下標,之後的 Java、JavaScript 等高級語言都效仿了 C 語言,或者說,爲了在一定程度上減少 C 語言程序員學習 Java 的學習成本,因此繼續沿用了從 0 開始計數的習慣。實際上,很多語言中數組也並不是從 0 開始計數的,比如 Matlab。甚至還有一些語言支持負數下標,比如 Python。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章