1. 程式人生 > >資料結構 - 陣列

資料結構 - 陣列

1.陣列基礎

 

陣列最一種存放資料的線性資料結構 ,最原始的陣列是一種靜態陣列,需要宣告陣列的容量,一旦new出來陣列物件大小不可改變,可以通過索引來進行資料的增刪改查。我們可以通過對靜態陣列的二次封裝來進行改進成動態陣列。

*陣列最大的優點:快速查詢。

*陣列最好用於“索引有語意”的情況。

*但並非所有有有語意的索引都適用於陣列。 例如當用陣列存放人員時,用身份證號來充當索引,此時能夠區別人員,但身份證號太長,極大浪費記憶體空間得不償失。

*陣列也可以處理索引沒有語意的情況。

 

基於靜態陣列實現的動態陣列原始碼如下:


/**
 * 對靜態陣列的二次封裝,改進成一個動態陣列
 *
 * @author zhangtianci
 */

public class Array<E>{
    
    private E[] data;        //用來存放資料
    private int size;        //當前陣列中存放資料的個數
    
    /**
     * 構造方法
     *
     * @param capacity 陣列的容量
     */
    public Array(int capacity){
        data = (E[]) new Object[capacity];
        size = 0;
    }
    
    /**
     * 無參建構函式,預設陣列的容量為10
     */
    public Array(){
        this(10);
    }
    
    /**
     *獲取當前陣列中存放資料的個數
     *
     *@return size of array
     */
    public int getSize(){
        return size;
    }
    
    /**
     * 獲取陣列的大小
     *
     * @return capacity of array
     */
    public int getCapacity(){
        return data.length;
    }
    
    /**
     * 判斷陣列是否為空
     *
     * @return true is empty,false is not
     */
    public boolean isEmpty(){
        return size == 0;
    }
    
    /**
     * 向所有元素後新增一個新元素
     *
     * @param e a new element of array
     */
    public void addLast(E e){
        add(size, e);
    }
    
    /**
     * 向所有元素前新增一個新元素
     *
     * @param e
     */
    public void addFirst(E e){
        add(0, e);
    }
    
    
    /**
     * 在index個位置插入一個元素
     *
     * @param index
     * @param 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++;
    }
    
    /**
     * 獲取索引為index上的元素
     *
     * @param index
     * @return
     */
    public E get(int index){
        if (index < 0 || index >= size){
            throw new IllegalArgumentException("Get failed.Index is Illggal.");
        }
        
        return data[index];
    }
    
    /**
     * 修改索引為index上的元素
     *
     * @param index
     * @param e
     */
    public void set(int index,E e){
        if (index < 0 || index >= size){
            throw new IllegalArgumentException("Set failed.Index is Illggal.");
        }
        
        data[index] = e;
    }
    
    /**
     * 查詢該陣列中是否存在該元素,若存在則返回true,反之false
     *
     * @param e
     * @return
     */
    public boolean contains(E e){
        for(int i = 0; i < size; i++){
            if (data[i].equals(e)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 查詢陣列中該元素的索引,若不存在則返回-1
     *
     * @param e
     * @return
     */
    public int find(E e){
        for(int i = 0; i < size; i++){
            if (data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * 刪除陣列指定索引的元素
     *
     * @param index
     * @return
     */
    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; i < size - 1; i++){
            data[i] = data[i + 1];
        }
        size--;
        data[size] = null;    
        
        if(size == data.length / 4 && data.length / 2 != 0){
            resize(data.length / 2);
        }
        return ret;
    }
    
    /**
     * 刪除陣列第一個元素
     *
     * @return
     */
    public E removeFirst(){
        return remove(0);
    }
    
    /**
     * 刪除陣列最後一個元素
     *
     * @return
     */
    public E removeLast(){
        return remove(size -1);
    }
    
    /**
     * 刪除陣列中的元素e
     *
     * @param 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("Arrry: 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();
    }


    /**
     * 當陣列容量達到上限或者不足容量時,重新定義容量
     *
     * @param 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;
        
    }
    

}

簡單的時間複雜度分析

  *時間複雜度一般分為 O(1),O(n),O(lg),O(nlogn),O(n^2)

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

  *為什麼要用大O,叫做O(n)? 忽略常數,其實又叫做漸進時間複雜度,描述n趨近與無窮的情況下,所以常數幾乎可以忽略。

對封裝陣列的時間複雜度分析

  *新增操作

  addLast(e) : O(1) ,但一旦達到了陣列的容量進行擴容時,就需要進行resize()操作,所以在這種情況下時間複雜度也為O(n)

  addFirst (e) : O(n)

  addIndex(index,e) : O(n)

  綜上所述在最壞的情況下,新增操作的時間複雜度為O(n)

  *刪除操作

  removeLast() : O(1),但一旦達到了陣列的容量進行縮容時,就需要進行resize()操作,所以在這種情況下時間複雜度也為O(n)

  removeFirst() : O(n)

  remove(index) : O(n)

  綜上所述在最壞的情況下,新增操作的時間複雜度為O(n)

  *修改操作

  set(index,e) : O(1)

  *查詢操作

  get(index) : O(1)

  contains(e) : O(n)

  find(e) : O(n)

  綜上:增:O(n),刪:O(n),改:當已知索引時O(1),當不知索引時即要首先進行一次遍歷查到該元素再進行修改則為O(n),查:已知索引O(1),未知索引O(n)

    所以,當使用陣列時最好使用當陣列的索引具有語意的情況,那麼當進行查詢和修改時則會在效能上有非常大的優勢,對於增加和刪除時最好使用         addLast()和 removeLast() 操作,因為在這兩種操作的時間複雜度都為O(1)。

 

risize的時間複雜度分析 O(n)

  從均攤時間複雜度來講,在capacity為n時addLast()和remove()操作平均下來為(2n+1)/(n+1)=2次操作,所以從這個角度來看addLast()和removeLast()的複雜度為

  O(1)。

 

複雜震盪度

  但是,當我們操作addLast()和removeLast()過於著急時,就會出現震盪複雜度這種情況

  解決方案:Lazy,正如以上程式碼中removeLast()方法中當縮容時,當size==capacity/4時,才將capacity的容量減半。

 

 

  

 

 

 

  

 

 

 

  

 

 

擴容時