小码哥《恋上数据结构与算法》笔记(六):队列

2,571 阅读2分钟

我的Github地址

小码哥《恋上数据结构与算法》笔记

极客时间《iOS开发高手课》笔记

iOS大厂面试高频算法题总结

iOS面试资料汇总

参考:小码哥数据结构与算法(六): 队列

数据结构和算法动态可视化

一、队列

  • 队列是一种特殊的线性表,只能在头尾两端操作。
  • 队尾(rear): 只能从队尾添加元素, 一般叫做enQueue, 入队
  • 对头(front): 只能从队头移除元素, 一般叫做deQueue, 出队
  • 先进先出的原则,First In First Out,FIFO。
  • 队列的内部实现可以使用动态数组双向链表实现。
  • 优先使用双向链表,因为队列主要是往头尾操作元素。

二、队列的接口设计

public class Queue<E> {
    // 使用双向链表实现队列
    private List<E> list = new LinkedList<>();
    // 元素的数量
    public int size();
    // 是否为空
    public boolean isEmpty();
    // 入队
    public void enQueue(E element);
    // 出队
    public E deQueue();
    // 获取队列的头元素
    public E front();
}

三、队列的实现

public class Queue<E> {
    private List<E> list = new LinkedList<>();
	
    public int size() {
        return list.size();
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }

    public void enQueue(E element) {
        list.add(element);
    }

    public E deQueue() {
        return list.remove(0);
    }

    public E front() {
        return list.get(0);
    }
}

四、leetcode算法题

用栈实现队列

public class _232_用栈实现队列 {    
    private Stack<Integer> inStack;
    private Stack<Integer> outStack;

    /** Initialize your data structure here. */
    public _232_用栈实现队列() {
        inStack = new Stack<>();
        outStack = new Stack<>();
    }
    
    /** 入队 */
    public void push(int x) {
        inStack.push(x);
    }
    
    /** 出队 */
    public int pop() {
    	checkOutStack();
    	return outStack.pop();
    }
    
    /** 获取队头元素 */
    public int peek() {
    	checkOutStack();
    	return outStack.peek();
    }
    
    /** 是否为空 */
    public boolean empty() {
    	return inStack.isEmpty() && outStack.isEmpty();
    }
    
    private void checkOutStack() {
    	if (outStack.isEmpty()) {
        	while (!inStack.isEmpty()) {
        		outStack.push(inStack.pop());
        	}
        }
    }
}

五、双端队列(Deque)

  • 双端队列是能在头尾两端添加删除的队列。

六、双端队列的接口设计&实现

public class Deque<E> {
    private List<E> list = new LinkedList<>();
	
    // 元素的数量
    public int size() {
        return list.size();
    }
    // 是否为空
    public boolean isEmpty() {
        return list.isEmpty();
    }
    // 从队头出队
    public E deQueueFront() {
        return list.remove(0);
    }
    // 从队头入队
    public void enQueueFront(E element) {
        list.add(0, element);
    }
    // 从队尾入队
    public void enQueueRear(E element) {
        list.add(element);
    }
    // 从队尾出队
    public E deQueueRear() {
        return list.remove(list.size() - 1);
    }
    // 获取队列的头元素
    public E front() {
        return list.get(0);
    }
    // 获取队列的尾元素
    public E rear() {
        return list.get(list.size() - 1);
    }
}

七、循环队列(Circle Queue)

  • 请阅读小码哥《恋上数据结构与算法》笔记(一):动态数组,第五条:ArrayList能否进一步优化。
  • 队列内部实现也可以用动态数组实现,并且将各项接口优化到O(1)的时间复杂度, 这个用数组实现并优化之后的队列就叫做: 循环队列
  • 使用一个索引变量front控制第0个元素所在位置。
  • 每一次出栈,就将front位置的元素取出并删除,然后front向后+1
  • 每一次入栈,都根据front和当前元素数量计算出入栈元素应该存入的索引,然后将元素存入到数组对应索引的位置上。

八、循环队列的接口设计

public class CircleQueue<E> {
    // 记录第0个元素的索引
    private int front;
    // 当前队列存储的元素个数
    private int size;
    // 用来存储元素的数组
    private E[] elements;
    // 当前队列存储的元素数量
    public int size();
    // 当前队列是否为空
    public boolean isEmpty();
    // 入队
    public void enQueue(E element);
    // 出队
    public E deQueue();
    // 查看索引为0的元素
    public E front();
}

九、循环队列的实现

1、构造方法

  • 如果构建的数组空间小于默认空间,则会以默认空间构建数组。
public class ArrayList<E> {
    private E[] elements;
    // 设置elements数组默认的初始化空间
    private static final int DEFAULT_CAPACITY = 10;
	
    public CircleQueue() {
        elements = (E[]) new Object[DEFAULT_CAPACITY];
    }
}

2、入队

  • 入队前需要考虑两个问题:队列是否需要扩容计算入队实际索引
2.1、数组扩容
private void ensureCapacity(int capacity) {
    int oldCapacity = elements.length;
    if (oldCapacity >= capacity) return;
		
    // 新容量为旧容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1); //位运算
    E[] newElements = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newElements[i] = elements[index(i)];
    }
    elements = newElements;
		
    // 重置front
    front = 0;
}
2.2、索引计算
  • 预期入队索引 = 第0个元素索引 + 当前队列元素个数
  • 如果预期入队索引大于等于数组长度实际入队索引 = 预期入队索引 - 数组长度
  • 如果预期入队索引小于数组长度实际入队索引 = 预期入队索引
private int index(int index) {
    index += front;
    return index - (index >= elements.length ? elements.length : 0);
}
  • 那么入队的代码如下:
public void enQueue(E element) {
    // 数组扩容判断
    ensureCapacity(size + 1);
    // 索引计算,并赋值
    elements[index(size)] = element;
    // size加一
    size++;
}

3、出队

  • 出队后需要更新front
public E deQueue() {
    // 获取出队元素
    E frontElement = elements[front];
    // 将索引位置致空
    elements[front] = null;
    // 更新font
    front = index(1);
    // size减一
    size--;
    // 返回出队元素
    return frontElement;
}

十、循环双端队列