算法與數據結構之美-數組

想說的話

寫博客的意義,是爲了分享自己學到的知識與大家共同進步。下面的內容,都是我在極客時間上學習的一門 數據結構與算法之美——王爭老師的課,如果感興趣的話,大家可以去購買,我也不是完全照搬內容,不是爲了發博客而發,也是相當於自己的學習筆記,留言區可以留下問題,我們共同探討!

開篇思考

爲什麼許多編程語言中數組都是從0開始編號?

如何實現隨機訪問

數組(Array)是一種線性表數據結構,用一組連續的內存空間,來存儲一組相同類型的數據。線性表(LinearList):數組,鏈表,隊列,棧等數據結構
線性表
由於數組是需要連續的內存空間和相同類型的數據,才能夠支持“隨機訪問”,帶來的弊端就是爲了保證數據的連續性,使得數據的增刪需要進行大量的數據搬移。

創建一個長度爲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個字節;

二維數組內存尋址:
對於 m * n 的數組,a [ i ][ j ] (i < m,j < n)的地址爲:
address = base_address + ( i * n + j) * type_size

數組只有按照下標進行隨機訪問的時候時間複雜度爲O(1);

低效的插入和刪除

先看看插入操作:
如果在數組的末尾插入新的數據,時間複雜度恰好爲O(1),如果在數組的開頭插入一個元素,時間複雜度就是O(n).因爲每個位置的插入概率是相同的,所以平均時間複雜度是(1+2+…n)/n=O(n)。

對於刪除操作:
如果刪除第K個位置的數據,爲了保證內存的連續性,也需要搬移數據,保證內存的連續性,其平均時間複雜度也是O(1)。實際上,在某些特定的場景下,並不需要追求數組的連續性,每次刪除操作就是對要刪除的數據做標定,可以將多次刪除操作集中在一起執行,類似於JVM中的標記-清除算法,這樣可以提高刪除的效率。

容器能否完全替代數組?

Java中對於數組類型提供了容器類-ArrayList

ArrayList 基於動態數組實現,RandomAccess接口標識着其支持快速隨機訪問;

public  class ArrayList<E> extends AbstractList<E>
	implements List<E> , RandomAccess,Cloneable,java.io.Serializable
//數組的默認大小爲10
private static final int DEFAULT_CAPACITY = 10;

ArrayList的優勢是將許多對於數組的操作封裝起來,例如:數組插入、刪除時數據搬移等,還有一個優點是支持動態擴容。

添加元素時使用 ensureCapacityInternal() 方法來保證容量足夠,如果不夠時,需要使用 grow() 方法進行擴容,新容量的大小爲 oldCapacity + (oldCapacity >> 1),也就是舊容量的 1.5 倍。

擴容操作需要調用 Arrays.copyOf() 把原數組整個複製到新數組中,這個操作代價很高,因此最好在創建 ArrayList 對象時就指定大概的容量大小,減少擴容操作的次數。

public boolean add(E e){
	ensureCapacityInternal(size+1); //increments modcount!!
	elementData[size++] = e;
	return true;
}
private void ensureCapacityInternal(int minCapacity){
	if(elementData == DEFAULT_EMPTY_ELEMENTDATA){
		minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);
	}
	ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity){
	modCount++;
	if(minCapacity - elementData.length>0)
		grow(minCapacity);
}

private void grow(int minCapacity){
	int oldCapacity = elementData.length;
	int newCapacity = oldCapacity+(oldCapacity >>1 );
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	if (newCapacity - MAX_ARRAY_SIZE > 0)
		newCapacity =  hugeCapacity(minCapaciy);
	elementData = Arrays.copyOf(elementData,new Capacity);	
}

至於Array和ArrayList的選擇,根據實際需要:
對於業務開發,直接使用容器,省時省力,即便損耗一丟丟性能,完全不影響整體性能;但是對於底層框架的開發,性能需要做到極致,數組就會優於容器。

解答開篇

從數組的內存模型來看,“下標”最爲確切的定義是“偏移(offset)”,如果a來表示數組的首地址,a[0]就是偏移量爲0的位置,a[k]就表示偏移k個type_size的位置,所以計算a[k]內存地址只需要使用上面的尋址公式即可。

參考

[1]: 極客時間-數據結構與算法之美-數組

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