文章目录
一、什么是队列?
只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特点,进行插入操作的一端称为
队尾,进行删除操作的一端称为
队头 。
二、队列的使用
在Java中,Queue是个接口,底层是通过链表实现的。
方法 | 功能 |
boolean offer(E e)
|
入队列
|
E poll()
|
出队列
|
peek()
|
获取队头元素
|
int size()
|
获取队列中有效元素个数
|
boolean isEmpty()
|
检测队列是否为空
|
注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
三、队列的模拟实现(单链表实现、用数组实现循环队列)
单链表模拟实现队列:
在java当中的队列,其实底层是使用双向链表开实现队列的。
在这里我使用单向链表来实现。但是如果这是一个单纯的单链表,那么会有以下两个问题:
1)从头入队:入队的时间复杂度为O(1),出队的时间复杂度为O(N)。
2) 从尾入队:入队的时间复杂度为O(N),出队的时间复杂度为O(1)。
但是我们可以给单链表新增一个tail 引用,永远指向尾节点。入队的时候,从tail 后面进行插入,相当于尾插法;出队的时候,从链表的头出队,相当于删除链表的头结点。
代码如下:
static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
}
}
public ListNode head;
public ListNode tail;
public int usedSize;
public void offer(int val){
ListNode node = new ListNode(val);
if (head == null){
head = node;
tail = node;
}else {
tail.next = node;
tail = node;
}
usedSize++;
}
public int poll(){
if (head == null){
return -1;
}
int ret = head.val;
head = head.next;
if (head == null){
tail = null;
}
usedSize--;
return ret;
}
public int peek(){
if (head == null)return -1;
return head.val;
}
public boolean isEmpty(){
return usedSize == 0;
}
public int getUsedSize(){
return usedSize;
}
用数组实现循环队列:
在一些场景时我们会用到循环队列,比如在使用生产者消费者模型的时候就会使用循环队列。
关键点是如何实现循环,代码如下:
rear = (rear+1) % elem.length;
front = (front+1)%elem.length;
完整代码如下:
class MyCircularQueue {
public int[] elem;
public int front;//头部
public int rear;//尾部
public MyCircularQueue(int k) {
elem = new int[k];
}
public boolean enQueue(int value) {
if (isFull()){
return false;
}
elem[rear] = value;
rear = (rear+1) % elem.length;
return true;
}
public boolean deQueue() {
if (isEmpty())return false;
front = (front+1)%elem.length;
return true;
}
public int Front() {
if (isEmpty())return -1;
return elem[front];
}
public int Rear() {
if (isEmpty())return -1;
int index = rear == 0 ? elem.length-1: rear-1;
return elem[index];
}
public boolean isEmpty() {
return front == rear;
}
public boolean isFull() {
return ((rear+1)%elem.length) == front;
}
}
四、双端队列
双端队列(
deque)是指允许两端都可以进行入队和出队操作的队列,
deque 是
“double ended queue” 的简称。 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。Deque是一个接口,使用时必须创建LinkedList的对象。
这种数据结构,在实际开发中使用的并不是非常多。
五、用队列实现栈
用队列实现栈:完整代码如下
class MyStack {
private Queue<Integer> queue1;
private Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
if (!queue1.isEmpty()){
queue1.offer(x);
}else if (!queue2.isEmpty()){
queue2.offer(x);
}else {
queue1.offer(x);
}
}
public int pop() {
if (empty())return -1;
if (!queue1.isEmpty()){
int len1 = queue1.size();
for (int i = 0; i < len1 -1; i++) {
queue2.offer(queue1.poll());
}
return queue1.poll();
}else {
int len2 = queue2.size();
for (int i = 0; i < len2-1; i++) {
queue1.offer(queue2.poll());
}
return queue2.poll();
}
}
public int top() {
if (empty())return -1;
if (!queue1.isEmpty()){
int len1 = queue1.size();
int tmp = -1;
for (int i = 0; i < len1; i++) {
tmp = queue1.poll();
queue2.offer(tmp);
}
return tmp;
}else {
int len2 = queue2.size();
int tmp = -1;
for (int i = 0; i < len2; i++) {
tmp = queue2.poll();
queue2.offer(tmp);
}
return tmp;
}
}
public boolean empty() {
return queue1.isEmpty() && queue2.isEmpty();
}
}
六、用栈实现队列
用栈实现队列:完整代码如下
class MyQueue {
private Stack<Integer> stack1;
private Stack<Integer> stack2;
public MyQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if (empty())return -1;
if (!stack2.isEmpty()){
return stack2.pop();
}else {
while (!stack1.isEmpty()){
stack2.push(stack1.pop());
}
return stack2.pop();
}
}
public int peek() {
if (empty())return -1;
if (!stack2.isEmpty()){
return stack2.peek();
}else {
while (!stack1.isEmpty()){
stack2.push(stack1.pop());
}
return stack2.peek();
}
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}