文章目录
- 前言
- 队列
- 队列的概念
- 队列的实现
- 队列的链表实现
- 实现的方法与属性
- 内部类实现节点
- 入队列
- 出队列
- 获取队头元素但不删除
- 判空
- 获取队列元素个数
- 队列的数组实现
- 循环队列
- 方法属性实现:
- 构造方法
- 向循环队列插入一个元素,成功插入则为真。
- 从循环队列中删除一个元素,成功删除则为真
- 从队首获取元素,如果队列为空,返回-1
- 获取队尾元素。如果队列为空,返回 -1
- 检查循环队列是否为空
- 判断循环队列是否已满
- 双端队列(Deque)
- 队列Oj题
- 1. [用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues/)
- 2. [用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks/)
前言
队列
队列的概念
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表,一种先进先出的数据结构。
队尾:允许插入的一端。
队头:允许删除的一端。
队列的实现
队列的层次实现可以是链表,也可以是数组。
队列的链表实现
队列
如果采用单链表实现,若使用头插与尾删,则出队的时间复杂度为O(n),若使用头删与尾插,且具有last标记(可看作指针),则时间复杂度为O(1).
如果采用双链表实现,则时间复杂度一定为O(1).
实现的方法与属性
public class MyQueue {ListNode first; //创建指向队列首部的标记ListNode last; //创建指向队列尾部的标记//入队列public void offer(int val) {}//出队列public int poll() {}//获取队头元素 但是不删除public int peek() { }//判空public boolean isEmpty() { } //获取队列元素个数public int size(){}
}
内部类实现节点
static class ListNode{//设定一个节点对象int data ;ListNode prev ;ListNode next;public ListNode(int data) {this.data = data;}}
入队列
实现思想:采用头插法,如果为空,则使得头标记与尾标记均指向新节点
不为空,则进行头插的操作。
public void offer(int data) {//采用头插法ListNode cur = new ListNode(data);if(isEmpty()){first = last = cur;}else {cur.next = first;first.prev = cur;first = cur;}}
出队列
实现思路:
1 . 先判断队列是否为空,队列为空抛异常。
2. 队列不为空,将尾标记指向尾节点的前一个节点,然后将前一个节点的next指针置为空,删除尾结点。
因为我队入采取的是头插,所以队出采取尾删,如果大家采取尾插,则队出就采取头删了。
public int poll() {try {if (isEmpty()) {throw new EmptyException("空队列异常!!!");}}catch (EmptyException e){e.printStackTrace();}int ret = last.data;last = last.prev;last.next = null; // 删除尾结点return ret;}
获取队头元素但不删除
获取队头元素 但是不删除
实现思路:
1.先判断队列是否为空,队列为空抛异常。
2 .队列不为空,返回尾节点的数据。(因为我采取的是链表尾部的队首)
public int peek() {//获取队头元素try {if (isEmpty()) {throw new EmptyException("空队列异常!!!");}}catch (EmptyException e){e.printStackTrace();}return last.data;}
判空
思想:直接返回头是否为空就行。
public boolean isEmpty(){return first == null;
}
获取队列元素个数
思想:直接遍历链表即可。
public int size(){//遍历整个链表ListNode cur = first;int count = 0 ;while (cur!=null){count++;cur = cur.next;}return count;}
队列的数组实现
如果采用普通的数组实现队列,在下标大的一端进行插入数据,在下标小的一端删除数据,
我们可以在删除一个数据后,将所有的数据向左移动一位,但是时间复杂度就变成了O(N),能否用数组实现一个时间复杂度为O(1),则不浪费空间的队列呢?——即循环队列。
循环队列
循环队列图:
实现思想:我们有front标记指向队列的首部,用rear标记指向队列的尾部,用于指向要插入数据的位置,当下标的值超出数组长度时,便用求模计算找回对应的位置。
方法属性实现:
class MyCircularQueue {//实现的数组private int[] elements;//指向首元素的标记int first = 0;//指向尾元素的标记int last = 0 ;//有效数组元素的个数int size = 0;//构造器,设置队列长度为 kpublic MyCircularQueue(int k) {}// 向循环队列插入一个元素。如果成功插入则返回真。public boolean enQueue(int value) {}//从循环队列中删除一个元素。如果成功删除则返回真。public boolean deQueue() {}//从队首获取元素。如果队列为空,返回 -1 public int Front() {}//获取队尾元素。如果队列为空,返回 -1 。public int Rear() {}//检查循环队列是否为空。public boolean isEmpty() {}//检查循环队列是否已满。public boolean isFull() {}
};
构造方法
public MyCircularQueue(int k) {//用数组实现一个循环队列this.elements = new int[k];}
向循环队列插入一个元素,成功插入则为真。
实现思想:
- 判断队列是否已满,满了就返回false。
- 不满就在last处放。
- 在放完之后,将last的值+1然后模除队列的长度
public boolean enQueue(int value) {//向循环队列中插入一个元素//先判断循环队列是否满if(isFull()){return false;}//未满this.elements[last] = value;last = (last+1)% elements.length;size ++;return true;}
从循环队列中删除一个元素,成功删除则为真
实现思想:
- 判断队列是否为空,空就返回false。
- 不空就直接将front指向下一个位置。
- 因为是循环队列,所以front+1再对数组长度进行求模。
public boolean deQueue() {//从队列中删除一个元素,删除队首的元素if(isEmpty()){return false;}//如果队列不为空,删除队首的元素,first+1,first = (first +1)% elements.length ; //必须保证first值到最大值时,能够再返回起点size = size-1;return true;}
从队首获取元素,如果队列为空,返回-1
实现思路:
- 先判断队列是否为空,为空返回-1。
- 不为空,返回front下标对应值。
public int Front() {if(isEmpty()){return -1;}//如果队列不为空,返回队首元素return elements[first];}
获取队尾元素。如果队列为空,返回 -1
实现思想:
- 如果队列不为空,返回队尾元素
- 队尾元素的下标不一定为last -1 ,如果last 正好此时为0,那么last-1的结果为-1
按照求模的方法:(last+数组长度-1)%数组长度,获取当前last之前的值
public int Rear() {if(isEmpty()){return -1;}return elements[(last + elements.length-1 )%elements.length];}
检查循环队列是否为空
实现思想:通过size,判断size是否为0,来判断循环队列是否为空。
public boolean isEmpty() {//判断空,满足头标记与尾标记都相等,且无数值if( size ==0){return true;}return false;}
判断循环队列是否已满
实现思想:当front标记与last标记相同,且size不为0时,说明循环队列已满。
public boolean isFull() {if(first ==last && size>0){return true;}return false;}
}
双端队列(Deque)
双端队列如图:
指可以两边都可以插入,都可以删除的队列。
Deque是一个接口,在java中有两个类实现了此接口
- ArrayDeque类,此类并没有在集合框架中
- LinkedList类
ArrayDeque是双端队列的线性实现。
LinkedList是双端队列的链式实现。
这两个类的对象可以当做栈与队列使用,因为他们实现了栈与队列的方法。
Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现
队列Oj题
1. 用队列实现栈
实现思想:
1 . 队列是先进先出操作,栈是先进后出,所以一个队列不可能实现栈,我们采用两个队列实现栈
2. 入栈,则直接将数据插入队列1即可
3. 出栈:将队列1中的size-1个元素,全部放入到队列2中去,剩余的一个元素,则进行出队即可。
如果再进行出栈,则将队2中的size-1个元素,放入到队1中去,将剩余的一个元素再进行出队
class MyStack {Queue<Integer> qu1 = new LinkedList<Integer>();Queue<Integer> qu2 = new LinkedList<Integer>();public MyStack() {}public void push(int x) {//将数据压入有数据的队列中if(!qu1.isEmpty()){qu1.offer(x);}else if(!qu2.isEmpty()){qu2.offer(x);}else{qu1.offer(x);}}public int pop() {//执行出栈操作,先判断栈是否为空if (empty()) {return -1;}//如果不为空if (!qu1.isEmpty()) {int len = qu1.size()-1;for (int i = 0; i < len; i++) {int val = qu1.poll();qu2.offer(val);}//return qu1.poll();}int len = qu2.size() -1 ;for (int i = 0; i < len; i++) {int val = qu2.poll();qu1.offer(val);}return qu2.poll();}public int top() {// 获取栈顶元素//将所有的数据转移到另一个队列中,期间经过一个临时变量,最后临时变量中的值即为栈顶元素if(empty()){return -1;}int val = 0;if(!qu1.isEmpty()){// for(int i=0;i<qu1.size();i++){ //有问题,当qu1执行poll方法后,会出现判断条件发生改变的情况while (!qu1.isEmpty()){val = qu1.poll();qu2.offer(val);}}else{while (!qu2.isEmpty()){val = qu2.poll();qu1.offer(val);}}return val;}public boolean empty() {if(qu1.isEmpty()&&qu2.isEmpty()){return true;}return false;}
}
2. 用栈实现队列
实现思想:
- 通过两个栈来实现队列
- 对于入队操作,直接将所有的数据放入栈1中即可
- 对于出队操作,则先判断栈2中是否有数据,如果有则出栈2的数据,如果没有,则将栈1中的全部数据转入栈2,然后再出栈2中的数据即可。
class MyQueue {Stack<Integer> stack1 = new Stack<>();Stack<Integer> stack2 = new Stack<>();public MyQueue() {}public void push(int x) {//将所有的数据压入栈1stack1.push(x);}public int pop() {//先判断栈是否为空if(empty()){return -1;}if(!stack2.isEmpty()){//如果栈2不等于空,return stack2.pop();}//如果栈2为空while(!stack1.isEmpty()){//将栈1的元素全部压入栈2中去int val = stack1.pop();stack2.push(val);}return stack2.pop();}public int peek() {//获取栈顶的元素//先判断栈是否为空if(empty()){return -1;}if(!stack2.isEmpty()){//如果栈2不等于空,return stack2.peek();}//如果栈2为空while(!stack1.isEmpty()){//将栈1的元素全部压入栈2中去int val = stack1.pop();stack2.push(val);}return stack2.peek();}public boolean empty() {return stack1.isEmpty()==true && stack2.isEmpty() ==true ;}}