小碼哥資料結構與演演算法(一): 動態陣列
阿新 • • 發佈:2020-06-24
本篇是戀上資料結構與演演算法(第一季)的學習筆記,使用JAVA語言
一、陣列(Array)
- 陣列是一種
順序儲存
的線性表,所有元素的記憶體地址都是連續的
int[] array = new int[]{11,22,33}
複製程式碼
在很多程式語言中,陣列有個致命的缺點,無法動態修改容量
實際開發中我們希望陣列的容量是動態變化的
二、動態陣列
- 可以通過陣列實現一個動態陣列,動態陣列的容量是動態變化的
- 可以對動態陣列進行
增刪改查
操作
// 元素的數量
int size();
// 是否為空
boolean isEmpty();
// 是否包含某個元素
boolean contains(E element) ;
// 新增元素到最後面
void add(E element);
// 返回index位置對應的元素
E get(int index);
// 設定index位置的元素
E set(int index,E element);
// 往index位置新增元素
void add(int index,E element);
// 刪除index位置對應的元素
E remove(int index);
// 檢視元素的位置
int indexOf(E element);
// 清除所有元素
void clear();
複製程式碼
三、動態陣列的設計
- 建立類
ArrayList
如下圖,建立size
屬性來管理陣列中元素的個數,建立elements
public class ArrayList<E> {
private int size;
private E[] elements;
}
複製程式碼
- 新增初始化方法,建立
elements
陣列,並指定elements
預設的容量
public class ArrayList<E> {
private int size;
private E[] elements;
// 設定elements陣列預設的初始化空間
private static final int CAPACITY_DEFAULT = 10;
public ArrayList (int capacity) {
capacity = capacity < CAPACITY_DEFAULT ? CAPACITY_DEFAULT : capacity;
elements = (E[]) new Object[capacity];
}
// 預設情況
public ArrayList() {
this(CAPACITY_DEFAULT);
}
}
複製程式碼
四、動態陣列的實現
1、新增元素
- 我們知道,每當陣列新增新元素時,都會在陣列最後一個元素的後面新增新元素
- 這個
新元素需要新增到的索引
等於當前陣列元素的個數
,在ArrayList
中size
屬性就是當前陣列元素的個數
,所以就是將新元素新增到陣列的size
位置上,然後size
加1
public void add(E element) {
elements[size] = element;
size++;
}
複製程式碼
2、陣列擴容
- 由於陣列
elements
最大的容量只有10
,所以當陣列存滿元素時,就需要對陣列進行擴容 - 因為陣列是無法動態擴容的,所以需要建立一個新的陣列,這個陣列的容量要比之前陣列的容量大
- 然後在將原陣列中的元素存放到新陣列中,這樣就實現了陣列的擴容
private void ensureCapacity() {
// 獲取陣列當前容量
int oldCapacity = elements.length;
// 如果 當前儲存的元素個數 < 當前陣列容量,直接返回
if (size < oldCapacity) return;
// 新陣列的容量為原陣列容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 建立新陣列
E[] newElements = (E[]) new Object[newCapacity];
// 原陣列中的元素儲存到新陣列中
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
// 引用新陣列
elements = newElements;
}
複製程式碼
- 此時,
add
方法的實現如下,在新增新元素之前,先判斷是否需要擴容
public void add(E element) {
// 新增新元素之前,先判斷是否需要擴容
ensureCapacity();
elements[size] = element;
size++;
}
複製程式碼
3、刪除元素
- 刪除元素,實際上就是去掉
指定位置的元素
,並將後面的元素向前移動
- 如下圖,當刪除索引為
3
的元素時,只需要將後面的元素向前移動,然後在去掉最後一個元素,size
減1
即可
public E remove(int index) {
// 取出需要刪除的元素
E element = elements[index];
// 通過迴圈,將index後面的所有元素向前移動一位
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
// 刪除最後一個元素
elements[--size] = null;
// 將刪除的元素返回
return element;
}
複製程式碼
- 注意: 刪除元素時傳入的索引不能越界,即
不能小於0,也不能大於等於size
- 所以我們在刪除元素之前需要先進行索引檢查
private void rangeCheck(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
}
}
複製程式碼
- 此時,
remove
方法實現如下
public E remove(int index) {
// 判斷索引是否越界
rangeCheck(index);
// 取出需要刪除的元素
E element = elements[index];
// 通過迴圈,將index後面的所有元素向前移動一位
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
// 刪除最後一個元素
elements[--size] = null;
// 將刪除的元素返回
return element;
}
複製程式碼
4、陣列縮容
- 當陣列中的元素刪除後,陣列剩餘的空間可能會很大,這樣就會造成記憶體的浪費
- 所以當陣列中元素刪除後,我們需要對陣列進行縮容
- 實現方法類似於擴容,當陣列中容量小於某個值時,建立新的陣列,然後將原有陣列中的元素存入新陣列即可
public void trim() {
// 獲取當前陣列的容量
int capacity = elements.length;
// 當size大於等於容量的一半,或則容量已經小於預設容量(10)時,直接返回
if (size >= capacity >> 1 || capacity < CAPACITY_DEFAULT) return;
// 計算新的容量 = 原有容量的一半
int newCapacity = capacity >> 1;
// 建立新陣列
E[] newElements = (E[]) new Object[newCapacity];
// 將原陣列元素存入新陣列
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
// 引用新陣列
elements = newElements;
}
複製程式碼
- 每次刪除元素後,判斷是否需要縮容
public E remove(int index) {
// 判斷索引是否越界
rangeCheck(index);
// 取出需要刪除的元素
E element = elements[index];
// 通過迴圈,將index後面的所有元素向前移動一位
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
// 刪除最後一個元素
elements[--size] = null;
// 判斷陣列是否需要縮容
trim();
// 將刪除的元素返回
return element;
}
複製程式碼
5、清空元素
- 清空元素,只需要將所有的元素置為
null
,然後size
置為0
public void clear() {
// 清空儲存的資料
for (int i = 0; i < size; i++) {
elements[i] = null;
}
// 將size置為0
size = 0;
}
複製程式碼
6、修改元素
- 修改元素時,只需要將原有位置的元素替換掉即可,只是需要注意一下索引是否越界
public E set(int index,E element) {
// 判斷索引是否越界
rangeCheck(index);
// 取出被替換元素
E oldElement = elements[index];
// 替換元素
elements[index] = element;
// 返回被替換元素
return oldElement;
}
複製程式碼
7、 查詢元素
- 查詢元素,只需要將指定索引的元素返回,注意索引是否越界即可
public E get(int index) {
rangeCheck(index);
return elements[index];
}
複製程式碼
8、插入元素
- 插入元素類似於刪除元素,只需要將插入位置後面的元素向後移動即可
- 注意: 需要從後向前移動元素,如果從前向後移動元素,那麼會進行元素覆蓋,最後出錯
public void add(int index,E element) {
// 從後向前的順序,將元素向後移動
for (int i = size - 1; i >= index; i--) {
elements[i + 1] = elements[i];
}
// 插入元素
elements[index] = element;
// size+1
size++;
}
複製程式碼
- 需要注意的是,插入元素的索引也不能越界,不過不同於刪除和設定元素時,插入的位置可以是所有元素的最後,即size的位置
public void rangeCheckForAdd(int index) {
// 當索引小於0 或 大於 size時 丟擲異常
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index:" + index + ",add
方法如下
public void add(int index,E element) {
// 檢查索引是否越界
rangeCheckForAdd(index);
// 從後向前的順序,將元素向後移動
for (int i = size - 1; i >= index; i--) {
elements[i + 1] = elements[i];
}
// 插入元素
elements[index] = element;
// size+1
size++;
}
複製程式碼
9、檢視元素位置
- 可以通過迴圈,查詢元素在陣列中的位置
- 注意: 陣列中可以儲存
null
,而null
是不能呼叫equals
方法的,所以需要對傳入的元素進行判斷,如果查詢的元素是null
,需要單獨處理
- 當元素存在時返回索引,否則返回變數
ELEMENT_ON_FOUND
的值
private static final int ELEMENT_ON_FOUND = -1;
public int indexOf(E element) {
if (element == null) {
for (int i = 0; i < size; i++) {
if (elements[i] == null) return i;
}
}else {
for (int i = 0; i < size; i++) {
if (element.equals(elements[i])) return i;
}
}
return ELEMENT_ON_FOUND;
}
複製程式碼
10、是否包含某個元素
- 當元素存在時,只需要判斷索引是否等於
ELEMENT_ON_FOUND
即可
public boolean contains(E element) {
// 檢視元素的索引是否為ELEMENT_ON_FOUND即可
return indexOf(element) != ELEMENT_ON_FOUND;
}
複製程式碼
11、元素的數量
- 陣列元素的數量,就是size的值
public int size() {
return size;
}
複製程式碼
12、陣列是否為空
- 判斷
size
的值是否為0
即可
public boolean isEmpty() {
return size == 0;
}
複製程式碼
13、動態陣列列印
- 可以重寫
toString
方法,來列印ArrayList
中的元素
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size = ").append(size).append(",[");
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(",");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
複製程式碼