1. 程式人生 > 程式設計 >小碼哥資料結構與演演算法(一): 動態陣列

小碼哥資料結構與演演算法(一): 動態陣列

本篇是戀上資料結構與演演算法(第一季)的學習筆記,使用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、新增元素

  • 我們知道,每當陣列新增新元素時,都會在陣列最後一個元素的後面新增新元素
  • 這個新元素需要新增到的索引等於當前陣列元素的個數,在ArrayListsize屬性就是當前陣列元素的個數,所以就是將新元素新增到陣列的size位置上,然後size1

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的元素時,只需要將後面的元素向前移動,然後在去掉最後一個元素,size1即可

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();
}
複製程式碼