數組是程序中最多見的數據結構,它能夠存儲一個固定大小的相同類型元素的順序集合(強類型語言)。數組的元素都是由連續的內存位置組成。最低的地址對應第一個元素,最高的地址對應最後一個元素,經過索引能夠很是容易找到某一個元素。python
大多數時候咱們須要使用一個大小可變的數組(C#、Python中的list),本文就基於數組來實現一個動態數組,因爲在Python中的列表已經對數組封裝的很好,這裏咱們使用C#來實現一個List。在後續介紹數據結構文章中,我會使用python和C#分別來實現相應的數據結構。數組
動態數組和普通數組在用戶使用上沒有區別,咱們定義一個類MyArray,內部維護一個數組,並不須要實現太多方法,最核心的是提供擴容、索引和增刪功能。數據結構
class MyArray<T> { private T[] array; //存儲數組 private int count; //存儲數組大小 private int capacity; //數組容量 public int Count { get { return count; } } public int Capacity { get { return capacity; } } //重載構造函數 接收一個初始容量 public MyArray(int capacity){ } // 改變數組大小 private void resize(int capacity) { } //在指定索引處插入元素 public void Insert(int index,T item) { } //移除指定位置元素 public void RemoveAt(int index) { } //正序獲取元素位置 public int IndexOf(T item) { } //實現索引器 public T this[int index] { } }
下面咱們按上面的方法一點一點來實現咱們的MyArray函數
構造函數在功能上是爲了初始化數組,因此用戶能夠傳遞一個初始數組容量,固然考慮到咱們的數組自己是動態的,因此若是用戶不傳入初始容量時,咱們應該使用默認大小建立數組。this
public MyArray(int capacity) { //接收一個初始容量capacity if (capacity > 0) { this.capacity = capacity; array = new T[capacity]; } else throw new Exception("列表容量必須大於0"); } public MyArray() { //構造函數 初始化默認數組 capacity = 4; array = new T[capacity]; }
在這裏咱們設置若是用戶沒有傳入capacity,默認數組大小爲4。咱們還維護了一個變量this.capacity,其實這個變量並沒必要要,能夠直接經過array.length得到數組容量。spa
思路十分簡單,咱們實例化一個新數組,把舊數組的數據複製到新數組便可。code
private void resize(int capacity) { // 改變數組大小 T[] newarray = new T[capacity]; Array.Copy(array, newarray, count); array = newarray; this.capacity = capacity; }
把一個元素插入數組指定位置,能夠分兩種狀況討論:一是追加到數組末尾,二是插入到數組中。blog
第一種狀況處理起來很簡單,由於咱們定義的類維護了一個count變量,它記錄了數組的實際大小,因此咱們只要賦值array[count],count++就能夠實現功能。索引
第二種狀況表明原來的位置已經有元素了,那麼原位置以後的元素都應該集體向後挪一個位置,array[i+1] = array[i],咱們能夠用一個for循環來實現。內存
public void Insert(int index,T item) { //在指定索引處插入元素 if (index > count || index <0) { throw new Exception("索引超出範圍"); } if (capacity == count) { //數組擴容 resize(capacity * 2); } if (index == count) { //第一種狀況,在末尾追加元素追加元素 array[count] = item; count++; } else { for (int i = count - 1; i>=index; i--) { //最後一次循環應爲array[index+1] = array[index] array[i + 1] = array[i]; } array[index] = item; count++; } }
值得注意的是,插入元素可能會超出數組大小,因此咱們作了一層capacity==count的判斷,若是爲真,咱們就調用resize方法,將數組擴容至原來的兩倍。
刪除元素的思路和增長元素的思路相反,把索引爲i的元素刪除後,後面的元素應該前進一位。
public void RemoveAt(int index) { //移除指定位置元素 if (index >= count || index < 0) { throw new Exception("索引超出範圍"); } else { for (int i = index; i < count - 1; i++) { //只要實現array[index] = array[index+1] //若i=count-1;則i+1可能會出現超出索引的狀況,故條件爲i<count-1 array[i] = array[i + 1]; } count--; if (count < capacity / 4) { resize(capacity/2); } } }
在刪除元素中,咱們最後調用了resize方法,當元素個數小於數組的1/4時,咱們把數組縮小至原來的1/2。
十分簡單,循環數組便可
public int IndexOf(T item) { //正序獲取元素位置 若無返回-1 for (int i = 0; i < count; i++) { if (array[i].Equals(item)) { return i; } } return -1; }
public T this[int index] { get { return array[index]; } set { array[index] = value; } }
好了,到如今咱們的MyArray最核心的功能完成了,固然你能夠爲它添加其餘方法,讓它在用戶使用體驗上,和原生數組更爲相近。最後咱們來看看各項操做的時間複雜度。
Insert方法,在末尾添加元素時間複雜度爲O(1),在數組最前面添加元素爲O(n),均攤時間複雜度爲O(n)
RemoveAt方法,在末尾刪除元素時間複雜度爲O(1),在數組最前面添加元素爲O(n),均攤時間複雜度爲O(n)
IndexOf和resize方法,遍歷數組,時間複雜度爲O(n)
索引器,按索引訪問元素,時間複雜度爲O(1)