1. 程式人生 > >自定義陣列及簡單時間複雜度分析

自定義陣列及簡單時間複雜度分析

前言:

作為java的一種容器,陣列的優缺點同樣明顯

優點:使用簡單 ,查詢效率高,記憶體為連續的區域 

缺點:大小固定,不適合動態儲存,不方便動態新增

一、自定義實現陣列

1、Java中定義陣列的三種形式

    // 第一種:陣列格式 型別[] 陣列名 = new 型別[陣列長度]
        int[] arr = new int[10];

    // 第二種:定義陣列,直接賦值
        int[] arr = new int[]{11, 22, 33, 44};

    // 第三種:定義陣列,直接賦值
        int[] arr = {11, 22, 33, 44};

提示:第三種定義方式最常用

2、自定義陣列

為了加深對陣列的理解,實現自定義陣列,定義陣列的動態擴減容,且自動維護陣列的大小size,從而節省了記憶體空間資源。

 自定義陣列的完整程式碼

package com.theone.arrays;

/**
 * @Description TODO 自定義陣列
 * @Author Pureman
 * @Date 2018/9/1 13:08
 **/
public class MyArray<E> {

    // 定義陣列型別
    private E[] data;
    // 特別提示一點,這裡的陣列容量預設是10,因此使用data.length不是陣列真實的長度
    // 定義陣列大小,實現動態管理陣列長度
    private int size;

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

    // 空參構造,預設陣列容量為10
    public MyArray() {
        this(10);
    }

    // 獲取陣列大小
    public int getSize() {
        return this.size;
    }

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

    // 判斷陣列是否為空
    public Boolean isEmpty() {
        return this.size == 0;
    }

    // 定義方法,向陣列的最後新增元素
    public void addLast(E e) {
        add(size, e);
    }

    // 定義方法,向陣列的0索引位置新增元素
    public void addFirst(E e) {
        add(0, e);
    }

    // 定義方法向陣列中的任意位置新增元素
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add failed,index is not expected");
        }
        if (size == data.length) {
            this.resize(2 * data.length);
        }
        // 遍歷資料,將原來資料從index向後移動一位
        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        // 覆蓋原下標的元素
        data[index] = e;
        // 陣列大小加一
        size++;
    }

    // 獲取陣列中指定下標的元素
    public E get(int index) {
        if (index == size) {
            throw new IllegalArgumentException("Get failed ,because required index is not legal");
        }
        return data[index];
    }

    // 更新陣列中指定角標的元素
    public void setIndex(int index, E e) {
        if (index == size) {
            throw new IllegalArgumentException("update failed ,because required index is not legal");
        }
        data[index] = e;
    }

    // 判斷陣列中是否包含指定元素
    public Boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            // ==比較的是物件的引用,equeals比較內容
            if (data[i].equals(e)) {
                return true;
            }
        }
        return false;
    }

    // 獲取元素的索引
    public int search(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }

    // 指定索引,刪除陣列中的元素,並返回被刪除的元素值
    public E remove(int index) {
        if (0 > index || size <= index) {
            throw new IllegalArgumentException("Remove failed.Index is illegal");
        }
        // 獲取待被待刪除元素
        E ret = data[index];
        // 不需要關注data[size-1]的元素是否還有值,陣列封裝對外部不暴露,因此呼叫時並不知道該陣列下標為size-1的位置還有元素
        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }
        size--;
        // loitering object(遊蕩的物件) != memory leak(記憶體洩漏)
        data[size] = null;
        // 判斷陣列長度是否是容量的1/4,如果是,實現動態減容
        if (size == data.length / 4 && data.length / 2 != 0) {
            this.resize(data.length / 2);
        }
        // 返回刪除的元素
        return ret;
    }

    // 移除陣列中的第一個元素
    public E removeFirst() {
        return this.remove(0);
    }

    //移除陣列中的最後一個元素
    public E removeLast() {
        return this.remove(this.getSize() - 1);
    }

    // 移除陣列中指定元素
    public void removeElement(E e) {
        int index = this.search(e);
        if (1 != index) {
            this.remove(index);
        }
    }

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

    // 動態拓展陣列容量
    private void resize(int capacity) {
        E[] newArr = (E[]) new Object[capacity];
        for (int i = 0; i < size; i++) {
            newArr[i] = data[i];
        }
        data = newArr;
    }
}

二、簡單介紹時間複雜度

簡單時間複雜度分為
O(1)、O(n)、O(lgn)、O(nlogn)、O(n^2)
注意:大O描述的是演算法的執行時間和輸入資料之間的關係

public static int sum(int[] nums){
		int sum = 0 ;
		for(int num : nums){
		sum += num ;
		}
		return sum ;
    }

sum(int[] nums)方法的時間複雜度為O(n),實際複雜度公式為:T = c1*n + c2

引數解釋:

  • n: nums中元素的個數
  • c2:指sum初始化的時間,及返回值sum返回的時間
  • c1:for迴圈內遍歷num,與sum累加的時間

為什麼要使用大O,叫做O(n)?    

在時間複雜度分析過程中,都會假設n趨勢於無窮,此時c2所消耗的時間遠遠小於c1*n的時間,因此分析時忽略常數,此時演算法和n呈線性關係

提示:計算時間複雜度都是趨向於最壞情況

另外兩個簡單的時間複雜度概念:均攤複雜度分析、防止複雜度震盪

均攤複雜度分析(amortized time complexity):resize()

當size == data.length時,自動呼叫方法resize(2 * data.length)實現陣列的動態擴容,擴容後陣列的容量是原來的二倍,因此不是每次新增元素都會呼叫resize()方法進行擴容,所以當自定義陣列中的方法addLast()執行所消耗的時間,因由陣列arr中的每個元素均應分攤。例如:陣列arr的大小為9時,其每個元素所執行的基本操作為15/9次操作

   public void test02() {
        // 定義陣列容量為6
        MyArray<Integer> arr = new MyArray<Integer>(6);
        // 向陣列新增8個元素
        for (int i = 0; i < 8; i++) {
            arr.addLast(i);
        }
        System.out.println("arr = " + arr);
        // 向陣列的最後碎銀新增元素100,此時陣列大小為9
        arr.addFirst(100);
        System.out.println("arr = " + arr);
    }

防止複雜度震盪:在resize()擴容後,立刻刪除陣列中的元素,觸發陣列自動減容

下面程式碼演示了複雜度震盪,陣列動態擴容後,立即刪除元素,陣列動態減容,這樣會造成時間複雜度增加

   public void test03() {
        // 定義陣列容量為6
        MyArray<Integer> arr = new MyArray<Integer>(5);
        for (int i = 0; i < 5; i++) {
            arr.addLast(i);
        }
        // 新增元素,此時陣列需要動態擴容
        arr.addLast(100);
        // 動態擴容後,立即刪除元素,陣列需要動態減容
        arr.removeLast();
    }

為了防止複雜度震盪,避免刪除元素後,造成複雜度震盪,採用延遲動態減容,程式碼如下

   public E remove(int index) {
        if (0 > index || size <= index) {
            throw new IllegalArgumentException("Remove failed.Index is illegal");
        }
        // 不需要關注data[size-1]的元素是否還有值,陣列封裝對外部不暴露,因此呼叫時並不知道該陣列下標為size-1的位置還有元素
        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }
        size--;
        // loitering object(遊蕩的物件) != memory leak(記憶體洩漏)
        data[size] = null;
        // 判斷陣列長度是否是容量的1/4,如果是,實現動態減容
        if (size == data.length / 4 && data.length / 2 != 0) {
            this.resize(data.length / 2);
        }
        return data[index];
    }