1

算法的时间复杂度

通常用O(1),O(n),O(lgn),O(nlogn),O(n^2)等表示算法时间复杂度,大O描述的是算法运行时间和输入数据之间的关系。

看一个对输入数据进行求和的算法:

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;

得出该算法的运行时间与输入数据(数组个数规模)之间是一种线性关系:

T = c1*n + c2

分析时间复杂度时,忽略常数。因此该算法的时间复杂度为O(n)。

再看下面的关系:

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,这不是矛盾了吗?

其实,大O的表示的是渐进时间复杂度,描述的是n趋近于无穷时的情况。在n趋于无穷时,T3 > T2。

对于n较小的情况下,当高阶算法的常数比较小的时候,有可能运行时间反而快于低阶算法的

当n趋于无穷的情况下,同时存在高阶和低阶时,低阶是可以被忽略的:

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

数据结构---数组

数组就是把数据码成一排进行存放,是一种线性数据结构:
clipboard.png
数组的最大优点:快速查询。scores[2]

我们基于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

zhutianxiang
1.5k 声望327 粉丝

« 上一篇
SpringCloud笔记