1. 程式人生 > >玩轉資料結構(02)--陣列_2

玩轉資料結構(02)--陣列_2

1.動態陣列(解決陣列空間不夠用的情況)

Java 中的靜態陣列,當插入的值數量 > 陣列的size 時就會報錯,使用動態陣列可以解決這個問題,

設定動態陣列的思路:再建立一個新的陣列newData,它要比之前的陣列空間大一些;將data 中的資料放入到 newData 中,迴圈遍歷陣列data中所有的元素,將他們依次賦值到 newData 中;

想讓 newData 取代原來的 data ,對陣列而言,容量(capacity)已經變為 8 ;size 在newData中還是 4 ,但陣列可以裝入更多的元素;

將陣列data的引用改為指向 新的有8個空間的陣列,與newData的引用相同,指向同樣的空間;整個過程封裝在函式中,當函式執行完成後,newData 就會失效了;而 data 是整個類的成員變數,和整個類的成員變數是相同的,只要類在使用則data就是有效的;

之前的4個空間的陣列,因為已經沒有引用了,垃圾回收器會將其銷燬;陣列完成擴容

示例程式碼:Array.java


public class Array<E> {

    private E[] data;
    private int size;

    // 建構函式,傳入陣列的容量capacity構造Array
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size = 0;
    }

    // 無引數的建構函式,預設陣列的容量capacity=10
    public Array(){
        this(10);
    }

    // 獲取陣列的容量
    public int getCapacity(){
        return data.length;
    }

    // 獲取陣列中的元素個數
    public int getSize(){
        return size;
    }

    // 返回陣列是否為空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在index索引的位置插入一個新元素e
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");

        if(size == data.length)
            resize(2 * data.length);	//(新增程式碼)進行陣列擴容,變為原來的2倍

        for(int i = size - 1; i >= index ; i --)
            data[i + 1] = data[i];

        data[index] = e;

        size ++;
    }

    // 向所有元素後新增一個新元素
    public void addLast(E e){
        add(size, e);
    }

    // 在所有元素前新增一個新元素
    public void addFirst(E e){
        add(0, e);
    }

    // 獲取index索引位置的元素
    public E get(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Index is illegal.");
        return data[index];
    }

    // 修改index索引位置的元素為e
    public void set(int index, E e){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Index is illegal.");
        data[index] = e;
    }

    // 查詢陣列中是否有元素e
    public boolean contains(E e){
        for(int i = 0 ; i < size ; i ++){
            if(data[i].equals(e))
                return true;
        }
        return false;
    }

    // 查詢陣列中元素e所在的索引,如果不存在元素e,則返回-1
    public int find(E e){
        for(int i = 0 ; i < size ; i ++){
            if(data[i].equals(e))
                return i;
        }
        return -1;
    }

    // 從陣列中刪除index位置的元素, 返回刪除的元素
    public E remove(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        E ret = data[index];
        for(int i = index + 1 ; i < size ; i ++)
            data[i - 1] = data[i];
        size --;
        data[size] = null; // loitering objects != memory leak

        if(size == data.length / 2)			//新增程式碼,當陣列中實際使用的空間為一半時[size 是 capacity 的一半]
            resize(data.length / 2);		//使陣列變為當前設定陣列容量的一半
        return ret;
    }

    // 從陣列中刪除第一個元素, 返回刪除的元素
    public E removeFirst(){
        return remove(0);
    }

    // 從陣列中刪除最後一個元素, 返回刪除的元素
    public E removeLast(){
        return remove(size - 1);
    }

    // 從陣列中刪除元素e
    public void removeElement(E e){
        int index = find(e);
        if(index != -1)
            remove(index);
    }

    @Override
    public String toString(){

        StringBuilder res = new StringBuilder();
        res.append(String.format("Array: size = %d , capacity = %d\n", size, data.length));
        res.append('[');
        for(int i = 0 ; i < size ; i ++){
            res.append(data[i]);
            if(i != size - 1)
                res.append(", ");
        }
        res.append(']');
        return res.toString();
    }

    // (新增程式碼)將陣列空間的容量變成newCapacity大小(2倍)
    private void resize(int newCapacity){

        E[] newData = (E[])new Object[newCapacity];	//new 一個新的E型陣列newData
        for(int i = 0 ; i < size ; i ++)
            newData[i] = data[i];	//將原來的陣列內容放入到新的newData中
        data = newData;	//讓 data 指向 newData 的空間
    }
}

Main.java

public class Main {

    public static void main(String[] args) {

        Array<Integer> arr = new Array<>();//修改程式碼,()中不填容量大小,使用預設容量
        for(int i = 0 ; i < 10 ; i ++)
            arr.addLast(i);
        System.out.println(arr);

        arr.add(1, 100);
        System.out.println(arr);

        arr.addFirst(-1);
        System.out.println(arr);

        arr.remove(2);
        System.out.println(arr);

        arr.removeElement(4);
        System.out.println(arr);

        arr.removeFirst();
        System.out.println(arr);
    }
}

輸出:

2.複雜度分析

時間複雜度分析:例:O(1)、O(n)、O(lgn)、O(nlogn)、O(n^2)

O:描述的是演算法的執行時間和輸入資料之間的關係

上圖中:c1指【for 迴圈中,將nums這個數取出來、將sum這個數取出來、將sum和nums 加在一起、最後將結果扔回給sum變數】這些操作花費的總時間;實際中無法具體取得

c2指【在整個程式中,開闢 Int 型空間 sum,並將0的初始化空間賦值給 sum,在運算結束後,還有返回sum】這些操作花費的總時間;實際中無法具體取得

前兩個忽略常數,都是O(n),雖然第三個的常數值很小,但它還是O(n^2),但並不代表:對於任意輸入來說,O(n)都要優於O(n^2),O 是漸進時間複雜度,【描述 n 趨近於無窮的情況來比較演算法的效能】;最後一箇中,低階的300n 相比 n^2(高階)可忽略,故為O(n^2);

3.動態陣列的時間複雜度分析

新增操作:通常情況下(按最壞的情況看)是 O(n) 

addLast(e) ---O(1)   :該操作所消耗時間與資料規模沒有關係,無論陣列中有多少元素,addLast都能在常數時間內完成。

addFirst(e) ---O(n)   :陣列頭新增元素需要將陣列每個元素向後移動一個單位,故為O(n)。

add(index,e) --O(n/2)=O(n)  :在陣列 index 索引的位置插入元素e,時間複雜度與 index 相關,index = 0時和addFirst(e)相同;index = size時和addLast(e)相同,分析:假設多種情況下的概率相同,運用概率論的知識,求出時間的期望。平均來看需要右移動 n/2 個元素,O需要忽略常數,故為 O(n)。

刪除操作:通常情況下(按最壞的情況看)是 O(n) 

removeLast(e) ---O(1) 

removeFirst(e) ---O(n) 

remove(index,e) --- O(n/2)=O(n)

resize  ---O(n)

修改操作:已知索引:O(1) 陣列最大優勢--支援隨機訪問,只要知道索引就可以馬上訪問到該資料

                 未知索引:O(n)  需要從頭遍歷陣列來找到該元素進行修改

set(index,e) ---O(1) 

查詢操作:需要從頭遍歷陣列來找到該元素

get(index) ---O(1)  知道所要查詢元素的索引,立馬可以拿到該值

contains(e) --- O(n)      不知道所要查詢元素的索引,檢視陣列中是否包含某個元素

find(e) ---O(n)        不知道所要查詢元素的索引,檢視陣列中 e 元素對應的索引是多少

4.均攤複雜度和防止複雜度的震盪

resize 的時間複雜度分析

因為最壞的情況(resize)要在很多次 addLast 操作後才會執行; 運用均攤複雜度比較合適;

addLast 和 removeLast 的均攤複雜度均為O(1);

複雜度震盪

上面的那種所說的是大多數情況下,都不會執行resize,現在認為製造這樣的情景,讓 addLast 和 removeLast 依次執行多次,這樣就會每次都執行 resize ,addLast 和 removeLast 的時間複雜度均為O(n);從O(1)變為O(n),產生複雜度震盪

解決複雜度震盪問題:

出現原因:removeLast 時 resize 過於著急

解決方案:設定 resize == capacity/4 時,才將 capacity 減半

圖解過程:

1.新增元素超過 size ,容量擴大一倍

2.刪除元素後,先不著急縮小陣列容量

3.等到陣列長度size縮小到陣列容量capacity 的1/4 時

4.縮小陣列容量,但也只縮小到原來的一半,仍然預留了一半的陣列容量供陣列進行 addLast 操作

示例程式碼:Array.java


public class Array<E> {

    private E[] data;
    private int size;

    // 建構函式,傳入陣列的容量capacity構造Array
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size = 0;
    }

    // 無引數的建構函式,預設陣列的容量capacity=10
    public Array(){
        this(10);
    }

    // 獲取陣列的容量
    public int getCapacity(){
        return data.length;
    }

    // 獲取陣列中的元素個數
    public int getSize(){
        return size;
    }

    // 返回陣列是否為空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在index索引的位置插入一個新元素e
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");

        if(size == data.length)
            resize(2 * data.length);

        for(int i = size - 1; i >= index ; i --)
            data[i + 1] = data[i];

        data[index] = e;

        size ++;
    }

    // 向所有元素後新增一個新元素
    public void addLast(E e){
        add(size, e);
    }

    // 在所有元素前新增一個新元素
    public void addFirst(E e){
        add(0, e);
    }

    // 獲取index索引位置的元素
    public E get(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Index is illegal.");
        return data[index];
    }

    // 修改index索引位置的元素為e
    public void set(int index, E e){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Index is illegal.");
        data[index] = e;
    }

    // 查詢陣列中是否有元素e
    public boolean contains(E e){
        for(int i = 0 ; i < size ; i ++){
            if(data[i].equals(e))
                return true;
        }
        return false;
    }

    // 查詢陣列中元素e所在的索引,如果不存在元素e,則返回-1
    public int find(E e){
        for(int i = 0 ; i < size ; i ++){
            if(data[i].equals(e))
                return i;
        }
        return -1;
    }

    // 從陣列中刪除index位置的元素, 返回刪除的元素
    public E remove(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        E ret = data[index];
        for(int i = index + 1 ; i < size ; i ++)
            data[i - 1] = data[i];
        size --;
        data[size] = null; // loitering objects != memory leak

        if(size == data.length / 4 && data.length / 2 != 0)			
//(修改程式碼,陣列長度size為陣列容量1/4時才會縮小陣列容量)
            resize(data.length / 2);
        return ret;
    }

    // 從陣列中刪除第一個元素, 返回刪除的元素
    public E removeFirst(){
        return remove(0);
    }

    // 從陣列中刪除最後一個元素, 返回刪除的元素
    public E removeLast(){
        return remove(size - 1);
    }

    // 從陣列中刪除元素e
    public void removeElement(E e){
        int index = find(e);
        if(index != -1)
            remove(index);
    }

    @Override
    public String toString(){

        StringBuilder res = new StringBuilder();
        res.append(String.format("Array: size = %d , capacity = %d\n", size, data.length));
        res.append('[');
        for(int i = 0 ; i < size ; i ++){
            res.append(data[i]);
            if(i != size - 1)
                res.append(", ");
        }
        res.append(']');
        return res.toString();
    }

    // 將陣列空間的容量變成newCapacity大小
    private void resize(int newCapacity){

        E[] newData = (E[])new Object[newCapacity];
        for(int i = 0 ; i < size ; i ++)
            newData[i] = data[i];
        data = newData;
    }
}