数据结构与算法-队列

1,444 阅读4分钟

1,什么是对队列?

  • 队列是一种特殊的线性表,只能在头尾两端进行操作
  • 队尾:只能从队尾添加元素,一般叫做入队
  • 对头:只能从队头移除元素,一般叫做出队
  • 队列遵循先进先出的原则,First In First Out,FIFO

2,队列的接口设计和实现

  • 队列的内部实现可以使用 动态数组和链表实现

  • 我们优先使用双向链表,因为队列主要是往头尾操作元素

    public class Queue {
    //创建双向链表
    private List list = new LinkedList<>();
    //元素的数量
    public int size() {
    //链表内元素的数量
    return list.size();
    }
    //是否为空
    public boolean isEmpty() {
    //调用链表方法,判断是否为空
    return list.isEmpty();
    }
    //清除元素
    public void clear() {
    //和链表一样清除元素
    list.clear();
    }
    //入队
    public void enQueue(E element) {
    //就是链表在队尾添加元素
    list.add(element);
    }
    //出队
    public E deQueue() {
    //删除链表的第一个元素,就是出队
    return list.remove(0);
    }
    //获取队头
    public E front() {
    //获取链表的第一个元素
    return list.get(0);
    } }

2.1,力扣练习-232. 用栈实现队列

面试题链接:leetcode-cn.com/problems/im…

面试题解析:

  • 准备两个栈:instack保存入栈的元素。outStack从依保存次从inStack出栈的元素
  • 入栈是push到instack中
  • 出栈时:队列是先进先出
  • 如果outStack为空,将instack所有元素逐一弹出,push到outstack,outstack弹出栈顶元素
  • 如果outstack不为空时,outStack弹出栈顶元素(即队头元素)

面试题代码实现

class MyQueue {     
    private Stack<Integer> inStack;    
    private Stack<Integer> outStack;     
    /** Initialize your data structure here. */    
    public MyQueue() {        
        //初始化inStack,outStack        
        inStack = new Stack<>();        
        outStack = new Stack<>();    
    }        
    /** 入队 */    
    public void push(int x) {        
        //inStack直接push        
        inStack.push(x);    
    }        
    /** 出队 */    
    public int pop() {        
        //先检查outStack是否为空        
        if(outStack.isEmpty()){            
            //inStack不为空,遍历inStack           
             while(!inStack.isEmpty()){                
                //inStack一次pop出来,压栈进入outStack                
                outStack.push(inStack.pop());            
            }        
        }        
        //outStack出栈就是,操作队头元素        
        return outStack.pop();    
    }        
    /** 获取队头元素 */    
    public int peek() {        
        //先检查outStack是否为空        
        if(outStack.isEmpty()){            
            //inStack不为空,遍历inStack            
            while(!inStack.isEmpty()){                
                //inStack一次pop出来,压栈进入outStack                
                outStack.push(inStack.pop());            
            }        
        }        
        //获取栈顶元素        
        return outStack.peek();    
    }        
    /** 队列是否为空 */    
    public boolean empty() {        
        //inStack和outStack同时为空,说明队列为空        
        return inStack.isEmpty() && outStack.isEmpty();    
    }
}

3,双端队列

  • 双端队列就是在队头和队尾都可以进行添加和删除的队列

  • 双端队列的实现:

    public class Deque {
    //初始化链表
    private List list = new LinkedList<>();
    //获取元素数量
    public int size() {
    //调用链表元素数量
    return list.size();
    }
    //判断是否为空
    public boolean isEmpty() {
    //调用链表方法判断
    return list.isEmpty();
    }
    //清除元素
    public void clear() {
    //调用链表方法
    list.clear();
    }
    //队尾入队
    public void enQueueRear(E element) {
    //list直接添加元素
    list.add(element);
    }
    //对头出队
    public E deQueueFront() {
    //删除索引为0的元素
    return list.remove(0);
    }
    //对头入队
    public void enQueueFront(E element) {
    //在索引为0时添加元素
    list.add(0, element);
    }
    //队尾出队
    public E deQueueRear() {
    //找到队尾元素的索引,删除对应元素
    return list.remove(list.size() - 1);
    }
    //获取对头
    public E front() {
    //获取索引为0的元素
    return list.get(0);
    }
    //获取队尾
    public E rear() {
    //获取队尾索引的元素
    return list.get(list.size() - 1);
    } }

4,循环队列

  • 循环队列是把顺序队列首尾相连,就是对头和队尾相连的队列

  • 出队和入队同一班队列一样,先进先出

  • 循环队列的设计和实现:使用动态数组实现

    public class CircleQueue {
    private int front; //头节点的位置
    private int size; //元素数量
    private E[] elements;//动态数组
    private static final int DEFAULT_CAPACITY = 10;
    public CircleQueue() {
    //开辟队列内存
    elements = (E[]) new Object[DEFAULT_CAPACITY];
    }
    //获取元素数量
    public int size() {
    return size;
    }
    //判断是否为空
    public boolean isEmpty() {
    return size == 0;
    }
    //清除元素
    public void clear() {
    //遍历索引元素,置为空
    for (int i = 0; i < size; i++) {
    elements[index(i)] = null;
    }
    //front指向0
    front = 0;
    //size为0
    size = 0;
    }
    //入队
    public void enQueue(E element) {
    //查询容量
    ensureCapacity(size + 1);
    //算出下一个添加元素的位置,然后添加
    //size+对头front = index
    //如果index大于数组长度elements.length,索引为index - elements.length
    //如果index小于数组长度elements.length,索引为index - 0
    elements[index(size)] = element;
    //size加1
    size++;
    }
    //出队
    public E deQueue() {
    //获取头节点
    E frontElement = elements[front];
    //头节点指向null
    elements[front] = null;
    //计算下一个头节点的索引
    //front+1就是下一个头节点的位置
    //如果index大于数组长度elements.length,索引为index - elements.length
    //如果index小于数组长度elements.length,索引为index - 0
    front = index(1);
    //size减1
    size--;
    //返回头节点
    return frontElement;
    }
    //获取头节点
    public E front() {
    //根据头节点索引获取元素
    return elements[front];
    }

    //根据传入的索引,计算位置    
    private int index(int index) {        
        index += front;        
        return index - (index >= elements.length ? elements.length : 0);    
    }        
    /**     * 保证要有capacity的容量     * @param capacity     */    
    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;    
    }
    

    }

5,循环双端队列

  • 循环双端队列:就是循环队列的对头和队尾,都可以进行删除和添加操作

  • 循环双端队列其实就是多了:从对头入队,从队尾出队的实现

  • 循环双端队列的设计和实现:

    public class CircleDeque {
    private int front;
    private int size;
    private E[] elements;
    private static final int DEFAULT_CAPACITY = 10;
    public CircleDeque() {
    elements = (E[]) new Object[DEFAULT_CAPACITY];
    }
    public int size() {
    return size;
    } public boolean isEmpty() {
    return size == 0;
    } public void clear() {
    for (int i = 0; i < size; i++) {
    elements[index(i)] = null;
    }
    front = 0;
    size = 0;
    } /**
    * 从尾部入队
    * @param element
    /
    public void enQueueRear(E element) {
    ensureCapacity(size + 1);
    elements[index(size)] = element;
    size++;
    } /
    *
    * 从头部出队
    * @param element
    /
    public E deQueueFront() {
    E frontElement = elements[front];
    elements[front] = null;
    front = index(1);
    size--;
    return frontElement;
    } /
    *
    * 从头部入队
    * @param element
    /
    public void enQueueFront(E element) {
    ensureCapacity(size + 1);
    //头部入队,就是front索引-1
    //计算索引的位置,负值给front
    front = index(-1);
    elements[front] = element;
    size++;
    } /
    *
    * 从尾部出队
    * @param element
    */
    public E deQueueRear() {
    //队尾索引为size-1
    int rearIndex = index(size - 1);
    //获取队尾元素
    E rear = elements[rearIndex];
    //队尾置null
    elements[rearIndex] = null;
    //size减1
    size--;
    return rear;
    } public E front() {
    return elements[front];
    }
    //获取队尾
    public E rear() {
    //队尾索引为size-1
    //根据index方法计算出对应的队尾实现
    return elements[index(size - 1)];
    }

    private int index(int index) {        
        index += front;        
        //front索引为0是,从对头入队的实现        
        if (index < 0) {            
            return index + elements.length;        
        }        
        return index - (index >= elements.length ? elements.length : 0);    
    }        
    /**     
    * 保证要有capacity的容量     
    * @param capacity     
    */    
    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;    
    }}
    

6,力扣练习-225. 用队列实现栈

面试题链接:leetcode-cn.com/problems/im…

方法一:两个队列

为了满足栈的特性,即最后入栈的元素最先出栈,在使用队列实现栈时,应满足队列前端的元素是最后入栈的元素。可以使用两个队列实现栈的操作,其中 queue 1 ​ 用于存储栈内的元素, queue 2 ​ 作为入栈操作的辅助队列。 

 入栈操作时,首先将元素入队到 queue 2 ​ ,然后将 queue 1 ​ 的全部元素依次出队并入队到  queue 2 ​ ,此时  queue 2 ​ 的前端的元素即为新入栈的元素,再将  queue 1 ​ 和  queue 2 ​ 互换,则  queue 1 ​ 的元素即为栈内的元素, queue 1 ​ 的前端和后端分别对应栈顶和栈底。

 由于每次入栈操作都确保  queue 1 ​ 的前端元素为栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。出栈操作只需要移除  queue 1 ​ 的前端元素并返回即可,获得栈顶元素操作只需要获得  queue 1 ​ 的前端元素并返回即可(不移除元素)。 由于  queue 1 ​ 用于存储栈内的元素,判断栈是否为空时,只需要判断 queue 1 ​ 是否为空即可。

class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;

    /** Initialize your data structure here. */
    public MyStack() {
        queue1 = new LinkedList<Integer>();
        queue2 = new LinkedList<Integer>();
    }
    
    /** 入栈 */
    public void push(int x) {
        queue2.offer(x);
        while (!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }
    
    /** 出栈 */
    public int pop() {
        return queue1.poll();
    }
    
    /** 产看栈顶元素 */
    public int top() {
        return queue1.peek();
    }
    
    /** 判断是否为空 */
    public boolean empty() {
        return queue1.isEmpty();
    }
}

方法二:一个队列

 使用一个队列时,为了满足栈的特性,即最后入栈的元素最先出栈,同样需要满足队列前端的元素是最后入栈的元素。 

 入栈操作时,首先获得入栈前的元素个数 n n,然后将元素入队到队列,再将队列中的前 n n 个元素(即除了新入栈的元素之外的全部元素)依次出队并入队到队列,此时队列的前端的元素即为新入栈的元素,且队列的前端和后端分别对应栈顶和栈底。

 由于每次入栈操作都确保队列的前端元素为栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。出栈操作只需要移除队列的前端元素并返回即可,获得栈顶元素操作只需要获得队列的前端元素并返回即可(不移除元素)。

 由于队列用于存储栈内的元素,判断栈是否为空时,只需要判断队列是否为空即可 

class MyStack {
    //初始化队列
    Queue<Integer> queue;

    /** Initialize your data structure here. */
    public MyStack() {
        queue = new LinkedList<Integer>();
    }
    
    /** 入栈 */
    public void push(int x) {
        //获取队列元素数量
        int n = queue.size();
        //入队
        queue.offer(x);
        //遍历队列,压栈
        for (int i = 0; i < n; i++) {
            //队列先进先出跟栈相反,所以反转队列,改为先进后出
            queue.offer(queue.poll());
        }
    }
    
    /** chu zhan */
    public int pop() {
        return queue.poll();
    }
    
    /** Get the top element. */
    public int top() {
        return queue.peek();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue.isEmpty();
    }
}