1. 數(shù)組:為什么很多編程語言中數(shù)組都是從0開始矫俺?
在大部分編程語言中,數(shù)組都是從0開始編號的,但你是否下意識想過厘托,為什么數(shù)組要從0開始編號友雳,而不是1開始呢? 從1開始不是更符合人類的思維習慣嗎铅匹?下面以這個問題來學習數(shù)組押赊。
數(shù)組的基本概念與特性
什么是數(shù)組?
什么是數(shù)組包斑?估計你心中已經(jīng)有了答案流礁。不過,這里還是總結(jié)一下舰始。數(shù)組(Array)是一種線性表數(shù)據(jù)結(jié)構(gòu)崇棠。它用一組連續(xù)的內(nèi)存空間,來存儲一組具有相同類型的數(shù)據(jù)丸卷。
這里定義里有幾個關(guān)鍵詞枕稀,理解了這幾個關(guān)鍵詞,就能徹底掌握數(shù)組的概念了谜嫉。
線性表(Linear List)萎坷。顧名思義,線性表就是數(shù)據(jù)排成像一條線一樣的結(jié)構(gòu)沐兰。每個線性表上的數(shù)據(jù)最多只有前和后兩個方向哆档。除了數(shù)組,鏈表住闯、隊列瓜浸、棧等也是線性表結(jié)構(gòu)。
而與它相對立的概念是非線性表,比如二叉樹比原、堆插佛、圖等。之所以叫非線性量窘,是因為在非線性表中雇寇,數(shù)據(jù)之間并不是簡單的前后關(guān)系。
總結(jié)下數(shù)組的特性
- 第一是線性表(Linear List)蚌铜。
- 第二是連續(xù)的內(nèi)存空間和相同類型的數(shù)據(jù)锨侯。
正是因為這兩個限制,它才有一個堪稱“殺手锏”的特性 “隨機訪問” 冬殃。但有利就有弊囚痴,這兩個限制也讓數(shù)組的很多操作變得非常低效,比如要想在數(shù)組中刪除审葬、插入一條數(shù)據(jù)深滚,為來保證連續(xù)性骂束,就需要左大量的數(shù)據(jù)搬移工作。
如何實現(xiàn)隨機訪問成箫?
數(shù)組到底是如何實現(xiàn)根據(jù)下標隨機訪問數(shù)組元素的展箱?
例如:長度為 10 的 int 類型的數(shù)組 int[] a = new int[10]
- 計算機給數(shù)組
a[10]
,分配了一塊連續(xù)內(nèi)存空間1000 ~ 1039
蹬昌; - 內(nèi)存塊的首地址為
base_address = 1000
混驰; - 計算機會給每個內(nèi)存單元分配一個地址,計算機通過地址來訪問內(nèi)存中的數(shù)據(jù)皂贩。
當計算機需要隨機訪問數(shù)組中的某個元素時栖榨,它會通過下面的尋址公式,計算出該元素存儲的內(nèi)存地址:
a[i]_address = base_address + i * data_type_size
arr[i]
首地址 = 數(shù)組內(nèi)存塊首地址 + 數(shù)據(jù)類型大小 * i明刷,其中 i 為偏移量婴栽,其中 data_type_size
表示數(shù)組中每個元素的大小。
上面這個例子里面:
base_address
:內(nèi)存塊的首地址辈末。
data_type_size
: 表示數(shù)組中每個元素的大小愚争,比如目前數(shù)組中存儲的是 int 類型數(shù)據(jù),所以 data_type_size
就為 4 個字節(jié)挤聘。
數(shù)組時間復雜度
數(shù)組(Array
)是一種線性表數(shù)據(jù)結(jié)構(gòu)轰枝。它用一組連續(xù)的內(nèi)存空間,來存儲一組具有相同類型的數(shù)據(jù)组去。
注意點:
- 數(shù)組是一種線性表鞍陨;
- 連續(xù)的內(nèi)存空間和相同類型的數(shù)據(jù)。
由于第二個性質(zhì)从隆,數(shù)組支持 “隨機訪問”诚撵,根據(jù)下標隨機訪問時間復雜度為O(1)
键闺,但是在數(shù)組中刪除寿烟、插入數(shù)據(jù)時需要做數(shù)據(jù)搬移工作艾杏。
低效的“插入”和“刪除”操作
1. 插入操作
假如數(shù)組的長度為 n
盅藻,我們需要將一個數(shù)據(jù)插入到數(shù)據(jù)的第 k
個位置购桑,則需要將 [k , n]
位元素都順序地往后挪動一位。
- 最好的情況:時間復雜度為
O(1)
氏淑,此時在數(shù)組末尾插入元素。 - 最壞的情況:時間復雜度為
O(n)
假残,此時在數(shù)組開頭插入元素炉擅。 - 平均的情況:時間復雜度為
O(n)
,因為在每個位置插入元素的概率相同阳惹,故(1+2+3+......+n)/ n = O(n)
谍失。
2. 刪除操作
和插入操作一樣,為了保證內(nèi)存的連續(xù)性莹汤,刪除操作也需要搬移數(shù)據(jù)。
- 最好的情況:時間復雜度為O(1)抹竹,此時刪除數(shù)組末尾的元素。
- 最壞的情況:時間復雜度為O(n)窃判,此時刪除數(shù)組開頭的元素喇闸。
- 平均的情況:時間復雜度為O(n),因為刪除每個位置的元素的概率相同燃乍,故(1+2+3+......+n)/ n = O(n)跨蟹。
更高級用法
在某些特殊場景下窗轩,在不追求數(shù)組中數(shù)組的連續(xù)時座咆,我們將多次刪除操作集中在一起執(zhí)行,會提高刪除的效率介陶?
例如:假設(shè)有一個數(shù)組 a , 長度為 10,存儲了 8 個元素哺呜,分別為 a,b,c,d,e,f,g,h
。現(xiàn)在我們依次刪除 a,b,c
三個元素:
a = [a,b,c,d,e,f,g,h];
為了避免 d,e,f,g,h
這個幾個數(shù)據(jù)會被搬移 3 次国撵,我們先記錄已經(jīng)刪除的數(shù)據(jù)(每次刪除操作并不是真正的搬移數(shù)據(jù)玻墅,只是記錄數(shù)據(jù)已經(jīng)被刪除)。當 a 數(shù)組沒有空間存儲數(shù)據(jù)時澳厢,這才觸發(fā)依次真正的刪除操作囚似,這樣就減少了刪除操作導致的數(shù)據(jù)搬移。
上述這個操作其實就是 JVM標記清除垃圾回收算法 的核心思想线得。
3. 警惕數(shù)組訪問越界
在 C 語言中,只要不是訪問受限的內(nèi)存搬素,所有的內(nèi)存空間都是可以自由訪問的。如果疏忽會造成嚴重的后果熬尺。當然谓罗,Java語言會自動檢測。
4. 總結(jié)
**
數(shù)組是最基礎(chǔ)檩咱、最簡單的數(shù)據(jù)結(jié)構(gòu)。數(shù)組用一塊連續(xù)的內(nèi)存空間刻蚯,來存儲相同類型的一組數(shù)據(jù),最大特點就是隨機訪問元素躬充,并且時間復雜度為 O(1)讨便。但是插入、刪除操作也因此比較低效霸褒,時間復雜度為O(n)。
數(shù)組和鏈表的區(qū)別
- 數(shù)組支持隨機訪問废菱,根據(jù)下標隨機訪問的時間復雜度為O(1),注意數(shù)組查找衰倦,即便是排好序的數(shù)組梳凛,使用二分查找時間復雜度為O(logn)梳杏。
- 鏈表適合插入淹接、刪除叛溢、時間復雜度為O(1)
最后總結(jié)一下:為什么大多數(shù)編程語言中,數(shù)組要從 0 開始編號楷掉,而不是從 1 開始呢厢蒜?
- 第一:歷史原因, c 語言設(shè)計者用 0 開始計數(shù)數(shù)組下標斑鸦,之后 Java草雕、JavaScript等高級語言都效仿 C 語言,因此繼續(xù)沿用從 0 開始計數(shù)的習慣墩虹。部分語言數(shù)組不是從 0 開始計數(shù)的,比如 Matlab旬昭,還有部分語言支持負數(shù)下標菌湃,如 Python问拘。
- 第二:從數(shù)組存儲的內(nèi)存模型上來看惧所,“下標”最確切的定義應該是 “偏移(offset)”。前面也講到或油,如果用 a 來表示數(shù)組的首地址驰唬,a[0] 就是偏移為 0 的位置,也就是首地址叫编,a[k] 就表示偏移 k 個 type_size 的位置,所以計算 a[k] 的內(nèi)存地址只需要用這個公式:
a[k]_address = base_address + k * type_size
但是卷谈,如果數(shù)組從1 開始計數(shù)霞篡,那我們計算數(shù)組元素 a[k] 的內(nèi)存地址就會變?yōu)椋?br>
a[k]_address = base_address + (k-1)* type_size
對比兩個公式端逼,不難發(fā)現(xiàn),從 1 開始編號顶滩,每次隨機訪問數(shù)組元素都多來一次減法運算寸爆,對于 CPU 來說,就多來一次減法指令赁豆。
數(shù)組作為非常基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)着憨,通過下標隨機訪問數(shù)組元素又是其非澄竦眨基礎(chǔ)的編程操作,效率的優(yōu)化就要盡可能做到極致心铃,所以為來減少一次減法操作,數(shù)組選擇來從 0 開始編號去扣,而不是 1 開始。
以上謝謝大家唆铐,求贊求贊求贊奔滑!