Array動態數組
文章目錄
1、Array概述
同數組不用,數組的大小在定義時已經確定,而在實際過程中數組需要根據數據量的大小自動更改數組大小。底層實現仍是數組,只是將數組進行封裝,可以實現自適應的數組Array。
涉及的所有函數方法:
2、Array數組實現思路
數組無非涉及增、查、改、刪四種操作,查和改操作與平常數組操作相同。難點就在於增加元素和刪除元素上。對於一般數組當索引index + 1 超過數組大小是就會報錯。爲了可以根據數據大小改變數組大小,引入capacity(容器)的概念。其中capacity >= size。如圖
注: 實際上capacity纔是真正數組的大小,size只是capacity中存儲數據的多少。
下面分別說一下增刪改查的的實現思路
2.1、增加元素
如果增加後 size <= capacity 則直接將數據添加到數組中,如果增加後 size > capacity 則需要對數組進行擴容操作。擴容的方式也就是擴大爲原來的2倍。
下面以在索引index=2,元素未5爲例: 主要分爲有無擴容的請況
1)增加元素-未擴容:
- 複製元素向後移動
- 插入元素到索引位置
2)增加元素-擴容:
- 擴容爲原來的2倍,將原數據複製到新數組
- 將索引之後的元素向後移一位
- 插入元素
擴容的方式就是開闢一塊原capacity的大小2倍的空間,然後將原數組的數據拷貝到新開闢的數組當中。
在頭部和尾部增加元素對應 index = 0 和 index = size - 1
程序實現:
public void add(int index, E e) {
if (size == data.length)
resize(2 * data.length); //開闢新空間
if (index < 0 || index > size)
throw new IllegalArgumentException("addLast failed, Required index < 0 || index > size");
System.arraycopy(data, index, data, index + 1, size - index); // 向後移動一位
data[index] = e; // 增加元素
size ++; // 維護size
}
// 私有函數 開闢新空間
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
System.arraycopy(data, 0, newData, 0, size); // 複製數組數據到新數組
data = newData;
}
2.2、刪除元素
增加元素對應擴容,那麼刪除元素對應縮容,當刪除元素後 size < capacity / 2的時候進行縮容。否則直接讓對應位置等於 null 即可。
下面以刪除索引爲1的元素爲例 : 主要分爲有無縮容的請況
1)刪除元素-未縮容的情況 :
- 將要刪除元素後面的數據向前移一位
- 刪除數據中最後一位的元素,索引爲size(刪除後size已經減 1)
2)刪除元素-縮容的情況 :
- 將要刪除元素後面的數據向前移一位
- 刪除數據中最後一位的元素,索引爲size(刪除後size已經減 1)
- 進行縮容,將原數據複製到原數組大小一半的新數組中
程序實現 :
public E remove(int index) {
if (index < 0 || index >= size) { //越界判斷
throw new IllegalArgumentException("delete failed, Required index < 0 || index >= size");
}
E temp = data[index];
System.arraycopy(data, index+1, data, index, size-index-1); //向前移動一位
size--; //維護size的大小
data[size] = null; // 清空對應位置的數據
if (size == data.length / 2 && data.length / 2 != 0)
resize(data.length / 2); //進行縮容操作
return temp; //返回被刪除的元素
}
2.3、改變元素
改變元素不涉及擴容或者縮容,沒有特別的地方,直接修改即可。
下面以set(2, 5)爲例 :
代碼實現:
void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("get failed, Required index < 0 || index >= size");
}
data[index] = e;
}
2.4、查找元素
查找元素分爲三種,查找是否有這個元素,查找這個元素所在位置,根據索引返回元素這三種。
查找是否有這個元素和查找這個元素所在位置代碼實現相似,一個找到了返回 true,一個找到了返回 index。
1)查找是否有這個元素
public boolean contains(E e){
for (int i = 0; i < size; i++) {
if (data[i].equals(e))
return true;
}
return false;
}
2)查找這個元素所在位置
public int find(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e))
return i;
}
return -1;
}
3、時間複雜度分析
1)增加元素的複雜度
最好的情況是向最後的位置添加元素複雜度爲 O(1) ,最壞的情況是向頭添加元素,因爲需要數據整體向後移動,複雜度爲 O(N) ,根據均攤複雜度得出複雜度爲 O(N/2) = O(N),也就是說增加元素的複雜度爲 O(N)。但是這僅僅是考慮沒有擴容的情況產生。但是對於擴容的也是遍歷一邊數組進行復制的操作,所以複雜度也爲 O(N)。
綜上所述增加操作的時間複雜度爲 O(N)
方法 | 複雜度 |
---|---|
addFirst(e) | O(1) |
addLast(e) | O(N) |
add(index, e) | O(N/2) = O(N) |
resize(newCapacity) | O(N) |
2)刪除元素的複雜度
刪除元素的時間複雜度同增加元素完全相同。一個擴容一個縮容,實現機理相似。也就是說刪除操作的時間複雜度也是 O(N) 級別。
方法 | 複雜度 |
---|---|
removeFirst() | O(1) |
removeLast() | O(N) |
remove(index) | O(N/2) = O(N) |
resize(newCapacity) | O(N) |
3)修改元素複雜度
修改操作利用了數組的優勢,支持隨機訪問。也就是可以在 O(1)的複雜度的情況下完成操作。
方法 | 複雜度 |
---|---|
set(index, e) | O(1) |
3)修改元素複雜度
修改操作對應三種情況,對於支持索引查找的get方法複雜爲 O(1)。對於contain方法和find方法兩者類似,需要遍歷數組,最好的情況是在最開始就找到想要查找元素,最壞的情況是想要查找的元素在末尾,根據均攤複雜度,兩者的複雜度爲 O(N)。
方法 | 複雜度 |
---|---|
get(index) | O(1) |
contain(e) | O(N) |
find(e) | O(N) |
4、解決時間複雜度震盪的辦法
對於時間複雜度的震盪發生在擴容和縮容的邊界。addLast()導致擴容,removeLast()導致縮容,在邊界反覆添加和刪除操作會導致時間複雜度震盪。導致時間複雜度一直維持在O(N)級別。
產生的原因就是因爲我們在 removeLst 的時候過於 Eager (激進),導致與 addLast() 的擴容操作相沖突,其實我們避開 / 2 和 * 2 就可以避免震盪問題,因此我在代碼中將 removeLast 的 resize 觸發改爲 / 4.public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("delete failed, Required index < 0 || index >= size");
}
E temp = data[index];
System.arraycopy(data, index+1, data, index, size-index-1);
size--;
data[size] = null;
if (size == data.length / 4 && data.length / 2 != 0) //解決複雜度震盪問題
resize(data.length / 2);
return temp;
}
5、Array具體實現函數
公有方法
getSize() //獲取數組大小
getCapacity() //獲取數組容器
isEmpty() //判斷是否爲空
addFirst(E e) // 向數組頭添加元素
addLast(E e) //向數組後添加元素
add(int index, E e) //向index索引處增加元素
get(int index) // 獲取索引index處元素
set(int index, E e) // 修改索引index處爲e
remove(int index) // 刪除元素,返回刪除的元素
removeFirst() //刪除第一個元素,
removeLast() //刪除最後一個元素
removeElement(E e) //刪除某一個元素
removeAllElement(E e) //刪除所有元素e
contains(E e) //判斷數組是否包含元素e
find(E e) // 查找元素e所在位置
toString() //打印函數
私有方法
resize() //重新定義數組大小
最後
更多有關數據結構與算法的精彩內容,歡迎大家查看我的主頁:曲怪曲怪
也歡迎大家關注我的微信公衆號:TeaUrn,我在那裏等你哦。