算法與數據結構篇 - 數組

計算與數據結構篇 - 數組

數據結構中,從結構上來說,分爲線性表和非線性結構。

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

非線性表,是因爲,在非線性表中,數據之間並不是簡單的前後關係。

數組 - 爲什麼數組要從0的下標開始?

數組(Array)是一種線性表數據結構。它用一組連續的內存空間,來存儲一組具有相同類型的數據。

連續的內存空間和相同類型的數據。正是因爲這兩個限制,它纔有了一個堪稱“殺手鐗”的特性:“隨機訪問”。但有利就有弊,這兩個限制也讓數組的很多操作變得非常低效,比如要想在數組中刪除、插入一個數據,爲了保證連續性,就需要做大量的數據搬移工作。

數組和鏈表的區別?數組是適合查找操作,但是查找的時間複雜度並不爲 O(1)。即便是排好序的數組,你用二分查找,時間複雜度也是 O(logn)。所以,正確的表述應該是,數組支持隨機訪問,根據下標隨機訪問的時間複雜度爲 O(1)。

低效的插入和刪除功能

假設數組的長度爲 n,現在,如果我們需要將一個數據插入到數組中的第 k 個位置。爲了把第 k 個位置騰出來,給新來的數據,我們需要將第 0-k 這部分的元素都順序地往後挪一位。那插入操作的時間複雜度是多少呢?

爲了提高數組的插入效率,把數組的排序排成有序的排列,如果要將某個數據插入到第 k 個位置,爲了避免大規模的數據搬移,我們還有一個簡單的辦法就是,直接將第 k 位的數據搬移到數組元素的最後,把新的元素直接放入第 k 個位置。

所以最快的時間複雜度是O(1),最壞時間複雜度是 O(n)。

跟插入數據類似,如果我們要刪除第 k 個位置的數據,爲了內存的連續性,也需要搬移數據,不然中間就會出現空洞,內存就不連續了。

和插入類似,如果刪除數組末尾的數據,則最好情況時間複雜度爲 O(1);如果刪除開頭的數據,則最壞情況時間複雜度爲 O(n);平均情況時間複雜度也爲 O(n)。

在實際編程中,我們也有類似的場景,主鍵設置成 AUTO_INCREMENT ,保證了數組的有序,連貫,在列類型裏有isDel這種狀態字段,先記錄下已經刪除的數據。每次的刪除操作並不是真正地搬移數據,只是記錄數據已經被刪除。當數組沒有更多空間存儲數據時,我們再觸發執行一次真正的刪除操作,這樣就大大減少了刪除操作導致的數據搬移。

越界與ArrayList

ArrayList 最大的優勢就是可以將很多數組操作的細節封裝起來。比如前面提到的數組插入、刪除數據時需要搬移其他數據等。另外,它還有一個優勢,就是支持動態擴容。

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

需要注意一點,因爲擴容操作涉及內存申請和數據搬移,是比較耗時的。所以,如果事先能確定需要存儲的數據大小,所以在秒殺的業務場景裏常有購買預定,就是預留出ArrayList的空間,已達到程序處理的最優解。

開篇解答 爲什麼數組要從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 來說,就是多了一次減法指令。

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