數據結構(一)

算法的時間複雜度

一般用O(1),O(n),O(lgn),O(nlogn),O(n^2)等表示算法時間複雜度,大O描述的是算法運行時間和輸入數據之間的關係。java

看一個對輸入數據進行求和的算法:算法

1 public static int sum(int[] nums) {
2     int sum = 0;
3     for(int num: nums) sum += num;
4     return sum;
5 }

第3行,對於nums中的每一個數,都要進行這種操做,執行時間咱們計爲常量c1;
第2行和第4行的執行時間計作常量c2;shell

得出該算法的運行時間與輸入數據(數組個數規模)之間是一種線性關係:數組

T = c1*n + c2

分析時間複雜度時,忽略常數。所以該算法的時間複雜度爲O(n)。數據結構

再看下面的關係:app

T1 = 2*n + 2          O(n)
T2 = 2000*n + 10000   O(n)
T3 = 1*n*n + 0        O(n^2)

咱們知道高階時間複雜度O(n^2)是大於低階時間複雜度O(n)的,可是當n等於10時,高階算法的執行時間T3=100,低階算法的執行時間T2=12000,T3 < T2,這不是矛盾了嗎?dom

其實,大O的表示的是漸進時間複雜度,描述的是n趨近於無窮時的狀況。在n趨於無窮時,T3 > T2。ide

對於n較小的狀況下,當高階算法的常數比較小的時候,有可能運行時間反而快於低階算法的

當n趨於無窮的狀況下,同時存在高階和低階時,低階是能夠被忽略的:函數

T1 = 300n + 10           O(n)
T2 = 1*n*n + 300n + 10   O(n^2)

數據結構---數組

數組就是把數據碼成一排進行存放,是一種線性數據結構:
clipboard.png
數組的最大優勢:快速查詢。scores[2]oop

咱們基於Java的靜態數組,封裝一個屬於本身的動態數組類Array,加深對於數組這種數據結構的理解。

clipboard.png

咱們基於Java靜態數組data來封裝咱們的動態數組Array類,capacity表示數組容量,能夠經過data.length得到。size表示數組元素個數,初始爲0(也能夠當作是下一個新增元素的索引位置)。

據此,設計Array類結構。

實現動態數組

初始數組類結構

public class Array<E> {

    private E[] data;
    private int size;

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

    // 無參數的構造函數,默認數組的容量capacity=10
    public Array() {
        this(10);
    }

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

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

    // 返回數組是否爲空
    public boolean isEmpty() {
        return size == 0;
    }
    
}

向數組末尾添加元素

添加元素前:
clipboard.png
添加元素後:
clipboard.png
分析得出,若是要向數組添加元素e,只要在size所在索引位置設置元素e,而後size向後移動(size++)便可。

據此,增長添加元素相關的代碼:

// 在全部元素後添加一個新元素
public void addLast(E e) {
    if(size == data.length) { // 數組容量已填滿,則不能再添加
        throw new IllegalArgumentException("AddLast failed. Array is full.");
    }
    data[size] = e;
    size++;
}

向指定位置添加元素

添加前:
clipboard.png
添加後:
clipboard.png

分析得出,只要把要插入元素的索引位置的當前元素以及其後的全部元素,都日後挪動一位,而後在指定索引位置設置新元素便可,最後size++。爲避免元素覆蓋,具體挪的時候,須要從後往前推動完成元素挪動的整個過程。

修改代碼:

// 在第index個位置插入一個新元素e
public void add(int index, E e) {
    if(size == data.length) {
        throw new IllegalArgumentException("Add failed. Array is full.");
    }
    
    if(index < 0 || index > size) {
        throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");
    }
    
    for(int i = size - 1; i >= index; i--) {
        data[i + 1] = data[i];
    }
    
    data[index] = e;
    size++;
}

調整addLast,複用add方法,同時增長一個addFirst:

// 在全部元素後添加一個新元素
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索引位置的元素
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;
}

從數組中刪除元素

刪除前:
clipboard.png

刪除後:
clipboard.png

分析得出,只要將要刪除位置以後的元素都往前挪動一位便可。而後size減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--;

    return ret;
}

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

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

// 從數組中刪除元素e(只刪除一個)
public boolean removeElement(E e) {
    int index = find(e);
    if (index != -1) {
        remove(index);
        return true;
    }
    return false;
}

調整爲動態數組
容量開太大,浪費空間,容量開小了,空間不夠用。因此須要實現動態數組。

具體作法以下:
clipboard.png
clipboard.png
就是在方法中開闢一個更大容量的數組,循環遍歷原來的數組元素,設置到新的數組上。而後將原數組的data指向新數組。

修改代碼:

// 數組容量擴容/縮容
public void resize(int newCapacity) {
    E[] newData = (E[])new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newData[i] = data[i];
    }
    data = newData;
}

修改添加元素的代碼,添加時自動擴容:

// 在第index個位置插入一個新元素e
public void add(int index, E e) {
    if (index < 0 || index > size) {
        throw new IllegalArgumentException("AddLast 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++;
}

修改刪除元素的代碼,必要時自動縮容:

// 從數組中刪除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 && data.length / 2 != 0) {
        resize(data.length / 2); // 縮容爲原來的一半
    }
    return ret;
}

測試咱們的數組

@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();
}

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);

}

console輸出:

Array: size = 10, capacity = 10
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array: size = 11, capacity = 20
[0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array: size = 12, capacity = 20
[-1, 0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array: size = 11, capacity = 20
[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array: size = 10, capacity = 20
[-1, 0, 1, 2, 3, 5, 6, 7, 8, 9]
Array: size = 9, capacity = 20
[0, 1, 2, 3, 5, 6, 7, 8, 9]

動態數組的時間複雜度

  • addLast(e):O(1)
    與數組元素規模沒有關係,在末尾增長元素,都能在常數時間內完成
  • addFirst(e):O(n)
    涉及到全部數組元素要挪位
  • add(index, e):O(n)
    按照機率論,平均要挪1/2*n個位置,O(1/2*n),n趨於無窮,忽略常量
  • resize(capacity):O(n)
    涉及到把原來的每一個元素要複製一遍
  • removeLast(e):O(1)
    與數組元素規模沒有關係,在末尾刪除元素,都能在常數時間內完成
  • removeFirst(e):O(n)
    涉及到全部數組元素要挪位
  • remove(index, e):O(n)
    按照機率論,平均要挪1/2*n個位置,O(1/2*n),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(e)和removeLast(e),有可能會涉及到resize,因此仍是O(n)。可是,對於這種相對比較耗時的操做,若是能保證它不是每次都會觸發的話,能夠用均攤複雜度更爲合理。

均攤時間複雜度

假設capacity=n,n+1次addLast操做後,觸發resize操做,resize操做對n個元素進行復制,因此總共進行2n+1次操做。平均,每次addLast操做,進行2次基本操做。這種均攤計算,時間複雜度是O(1)。
同理removeLast(),均攤時間複雜度也是O(1)

複雜度震盪
上面,咱們按均攤時間複雜度來分析,addLast()和removeLast()操做的時間複雜度都是O(1)。

可是當咱們同時關注addLast()和removeLast()操做的時候,存在這麼一種狀況:假設capacity=n,當程序操做addLast()添加第n+1個元素的時候,觸發resize擴容,此時,時間複雜度爲O(n)。而後很不幸的,接着立刻又是removeLast()刪除第n+1個元素,又觸發了resize縮容,時間複雜度仍是O(n)。更不幸的是,這時候addLast()、removeLast()操做一直在反覆進行,那麼每次都是O(n)了。

對於這個問題,就是複雜度震盪,問題的關鍵在於removeLast()的時候縮容的有些着急(Eager)。能夠優化成延遲縮容,在數組元素只有容量的1/4的時候再進行縮容。

修改代碼以下:

// 從數組中刪除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

    // 縮容數組使用lazy方式(避免複雜度震盪),在1/4的時候才縮容
    if (size == data.length / 4 && data.length / 2 != 0) { 
        resize(data.length / 2); // 縮容爲原來的一半
    }

    return ret;
}

動態數組的完整代碼

public class Array<E> {

    private E[] data;
    private int size;

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

    // 無參數的構造函數,默認數組的容量capacity=10
    public Array() {
        this(10);
    }

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

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

    // 返回數組是否爲空
    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索引位置的元素
    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
        
        // 縮容數組使用lazy方式(避免複雜度震盪),在1/4的時候才縮容
        if (size == data.length / 4 && data.length / 2 != 0) {
            resize(data.length / 2); // 縮容爲原來的一半
        }

        return ret;
    }

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

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

    // 從數組中刪除元素e(只刪除一個)
    public boolean removeElement(E e) {
        int index = find(e);
        if (index != -1) {
            remove(index);
            return true;
        }
        return false;
    }

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

    @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();
    }

    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);

    }

}

數據結構---棧

  • 棧也是一種線性數據結構
  • 相比數組,棧對應的操做是數組的子集
  • 只能從棧頂添加元素,也只能從棧頂取出元素
  • 是一種後進先出的數據結構,LIFO(Last In First Out)

clipboard.png
無處不在的撤銷操做、程序調用的調用棧,等等都是棧的常見應用。

實現棧

基於咱們實現的動態數組Array來實現棧

public interface Stack<E> {

    int getSize();
    boolean isEmpty();
    void push(E e);
    E pop();
    E peek();

}
public class ArrayStack<E> implements Stack<E> {

    Array<E> array;

    public ArrayStack(int capacity) {
        array = new Array<>(capacity);
    }

    public ArrayStack() {
        array = new Array<>();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    @Override
    public void push(E e) {
        array.addLast(e);
    }

    @Override
    public E pop() {
        return array.removeLast();
    }

    @Override
    public E peek() {
        return array.getLast();
    }

    public int getCapacity() {
        return array.getCapacity();
    }

    @Override
    public String toString() {
        StringBuffer res = new StringBuffer();
        res.append("Stack: ");
        res.append("[");
        for (int i = 0; i < array.getSize(); i++) {
            res.append(array.get(i));
            if (i != array.getSize() -1) {
                res.append(", ");
            }
        }
        res.append("] top");
        return res.toString();
    }

    public static void main(String[] args) {
        ArrayStack<Integer> stack = new ArrayStack<>();
        for (int i = 0; i < 5; i++) {
            stack.push(i);
            System.out.println(stack);
        }

        stack.pop();
        System.out.println(stack);
    }
}

輸出結果:

Stack: [0] top
Stack: [0, 1] top
Stack: [0, 1, 2] top
Stack: [0, 1, 2, 3] top
Stack: [0, 1, 2, 3, 4] top
Stack: [0, 1, 2, 3] top

因爲基於咱們的動態數組Array來實現的棧,因此該棧也具有了縮容和擴容的能力。

棧的時間複雜度

ArrayStack<E>

  • void push(E):可能觸發resize,均攤複雜度依然爲O(1)
  • E pop():可能觸發resize,均攤複雜度依然爲O(1)
  • E peek():O(1)
  • int getSize():O(1)
  • boolean isEmpty():O(1)

數據結構---隊列

  • 隊列也是一種線性數據結構
  • 相比數組,隊列對應的操做是數組的子集
  • 只能從一端(隊尾)添加元素,從另外一端(隊首)取出元素
  • 是一種先進先出的數據結構,FIFO(First In First Out)

clipboard.png

實現數組隊列

基於咱們實現的動態數組Array來實現隊列

public interface Queue<E> {

    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue();
    E getFront();

}
public class ArrayQueue<E> implements Queue<E> {

    private Array<E> array;

    public ArrayQueue(int capacity) {
        array = new Array<>(capacity);
    }

    public ArrayQueue() {
        array = new Array<>();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    public int getCapacity() {
        return array.getCapacity();
    }

    @Override
    public void enqueue(E e) {
        array.addLast(e);
    }

    @Override
    public E dequeue() {
        return array.removeFirst();
    }

    @Override
    public E getFront() {
        return array.getFirst();
    }

    @Override
    public String toString() {
        StringBuffer res = new StringBuffer();
        res.append("Queue: ");
        res.append("front [");
        for (int i = 0; i < array.getSize(); i++) {
            res.append(array.get(i));
            if (i != array.getSize() -1) {
                res.append(", ");
            }
        }
        res.append("] tail");
        return res.toString();
    }

    public static void main(String[] args) {
        ArrayQueue<Integer> queue = new ArrayQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

輸出結果:

Queue: front [0] tail
Queue: front [0, 1] tail
Queue: front [0, 1, 2] tail
Queue: front [1, 2] tail
Queue: front [1, 2, 3] tail
Queue: front [1, 2, 3, 4] tail
Queue: front [1, 2, 3, 4, 5] tail
Queue: front [2, 3, 4, 5] tail
Queue: front [2, 3, 4, 5, 6] tail
Queue: front [2, 3, 4, 5, 6, 7] tail
Queue: front [2, 3, 4, 5, 6, 7, 8] tail
Queue: front [3, 4, 5, 6, 7, 8] tail
Queue: front [3, 4, 5, 6, 7, 8, 9] tail

因爲基於咱們的動態數組Array來實現的棧,因此該隊列也具有了縮容和擴容的能力。

數組隊列的時間複雜度

ArrayQueue<E>

  • void enqueue(E):隊尾入隊,可能觸發resize,均攤複雜度依然爲O(1)
  • E dequeue():出隊後涉及全部元素向前移動,時間複雜度爲O(n)
  • E getFront():O(1)
  • int getSize():O(1)
  • boolean isEmpty():O(1)

實現循環隊列

數組隊列的出隊時間複雜度爲O(n),主要問題是由於出隊後,隊伍中的元素都要往前移動。

過程大體以下:

隊列:
clipboard.png

出隊:
clipboard.png

移動:
clipboard.png

維護size:
clipboard.png

咱們考慮實現一種循環隊列,記錄隊頭head指向和隊尾tail指向。這麼一來入隊和出隊只要分別向後移動tail和head一個位置便可。該問題就能夠簡化成以下方式的一種操做:
clipboard.png

隨着入隊出隊的持續進行,爲了充分利用前方出隊後留下的空間,tail在7位置時,若是繼續入隊,tail將指向0位置推動,所謂循環隊列,就是這個意思。就像一個環形的傳送帶,只用移動頭尾標記,就能夠很方便地處理隊列元素。因此循環隊列在實現時要考慮按容量取模的處理狀況。

另外,再考慮循環隊列的另一種狀況:
關於循環隊列,在實現時,在head與tail處於相同位置的時候,咱們認爲隊列爲空:
clipboard.png

隨着持續入隊出隊,在循環移動tail和front的過程當中,tail可能會追上front:
clipboard.png

因爲tail==front不能即表達爲「隊列空」,又表達爲「隊列滿」。爲了解決這個問題,循環隊列有意浪費一個空間:

clipboard.png
所以,tail == front表明隊列空,(tail + 1) % capacity = front表明隊列滿。

據此,咱們實現循環隊列以下:

public class LoopQueue<E> implements Queue<E> {

    private E[] data;
    private int front, tail;
    private int size;

    public LoopQueue(int capacity) {
        // capacity是用戶指望的存儲元素數量,實現隊列時要浪費一個空間,因此apacity + 1
        data = (E[])new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue() {
        this(10);
    }

    public int getCapacity() {
        return data.length - 1;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    @Override
    public void enqueue(E e) {
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * 2);
        }

        data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;
    }

    // O(1) 相比數組隊列,由O(n)變成了O(1)
    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        }

        E ret = data[front];
        data[front] = null;
        front = (front + 1) % data.length;
        size--;
        if (size == getCapacity() / 4 && getCapacity() /2 != 0) {
            resize(getCapacity() / 2);
        }

        return ret;
    }

    private void resize(int newCapacity) {
        E[] newData = (E[])new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            // 將原來data中的元素放到newData中
            // front做爲第一個元素,因此有front的誤差,因爲是循環隊列,因此要取模運算
            newData[i] = data[(i + front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        }
        return data[front];
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d, capacity = %d\n", size, getCapacity()));
        res.append("front [");
        for (int i = front; i != tail; i = (i + 1) % data.length) {
            res.append(data[i]);
            if ((i + 1) % data.length != tail) {
                res.append(", ");
            }
        }
        res.append("] tail");
        return res.toString();
    }

    public static void main(String[] args) {
        LoopQueue<Integer> queue = new LoopQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

循環隊列的時間複雜度

LoopQueue<E>

  • void enqueue(E):O(1) 均攤
  • E dequeue():O(1) 均攤
  • E getFront():O(1)
  • int getSize():O(1)
  • boolean isEmpty():O(1)

測試數組隊列和循環隊列

import java.util.Random;

public class Main {


    // 測試使用q測試運行opCount個enqueue和dequeue操做所須要的時間,單位:秒
    private static double testQueue(Queue<Integer> q, int opCount) {
        long startTime = System.currentTimeMillis();

        Random random = new Random();
        for (int i = 0; i < opCount; i++) {
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        }

        for (int i = 0; i < opCount; i++) {
            q.dequeue();
        }

        long endTime = System.currentTimeMillis();
        return (endTime - startTime) / 1000.0;
    }

    public static void main(String[] args) {
        int opCount = 100000;

        ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
        double time1 = testQueue(arrayQueue, opCount);
        System.out.println("ArrayQueue, time: " + time1 + " s");

        LoopQueue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue, opCount);
        System.out.println("LoopQueue, time: " + time2 + " s");
        
    }
}

測試輸出:

ArrayQueue, time: 3.895 s
LoopQueue, time: 0.014 s
相關文章
相關標籤/搜索