1. 队列的概念
队列(Queue)是一种线性表,只允许在一段进行数据的插入,在另一端进行数据的删除操作。具有先进先出的特点,简称FIFO(First In First Out)。在队尾进行数据的插入,叫做入队;在队首进行数据的删除,叫做出队。在java中,Queue是一个接口,必须用它的实现类来实例化,底层用链表实现。
Queue的常用方法:
(1)入队列:offer(E e)
(2)出队列: poll()
(3)获取队首元素: peek()
(4)获取队列中的有效元素个数: int size()
(5)判断队列是否为空: boolean isEmpty()
2. 队列的模拟实现
使用单链表模拟实现队列:
2.1 定义Queue接口
public interface Queue<E> {
void offer(E element);
E poll();
E peek();
}
2.2 定义MyQueue类实现Queue接口
public class MyQueue<E> implements Queue<E> {
//定义链表的头,尾
private Node head;
private Node tail;
private int size;
class Node{
E val;
Node next;
public Node(E val) {
this.val = val;
}
}
/**
* /入队列:链表的尾插
*/
@Override
public void offer(E element) {
//产生新节点
Node node = new Node(element);
//如果链表为空,此时新节点就是头和尾
if(head==null){
head = tail = node;
}
//如果链表不为空,此时
tail.next = node;
tail = node;
size++;
}
/**
* /出队列:出的是链表中的头结点
*/
@Override
public E poll() {
E val = head.val;
head = head.next;
size--;
return val;
}
/**
* 查看队首元素
*/
@Override
public E peek() {
if (size == 0) {
throw new NoSuchElementException("queue is empty!cannot peek");
}
return head.val;
}
/**
* 重写ToString方法
*/
@Override
public String toString() {
//遍历链表
StringBuilder sb = new StringBuilder();
sb.append("front[");
for (Node x = head; x !=null; x=x.next) {
sb.append(x.val);
if(x.next != null){
sb.append(", ");
}
}
sb.append("]tail");
return sb.toString();
}
}
2.3 测试类
public class MyQueueTest {
public static void main(String[] args) {
Queue<Integer> queue = new MyQueue<>();
//入队列
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
queue.offer(5);
System.out.println(queue);
//查看队首元素
System.out.println(queue.peek());
//出队列
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue);
}
}
测试结果:
3. 循环队列
除了我们上述使用到的普通队列之外,还有一种叫做循环队列,循环队列一般使用数组实现。
关键:循环队列怎么判断元素已满?
如果我们将循环队列展平就是这个样子:入队是从队尾也就是数组尾部插入,然后更新tail索引值;出队就是从队首出,然后更新head值。
我们判断循环队列最简单的做法是:
- 多创建一个空间,让tail指向最后一个有效元素的下一个位置
- 判断为空:head == tail;
- 判断为满: ( tail+1) % arr.length == head;
注意:在循环队列中,我们不能将索引简单的++或者--,索引的计算采用的都是取模操作;
4. 队列与栈的相互实现
4.1 单队列实现栈
(1)主要思路:让新元素先入队列,然后将原先存储的旧元素依次出队列再重新入队列,此时的顺序就符合栈的出栈特点 。其中要注意的是:这个旧元素有几个就要出几次栈。所以用一个值记录queue队列中的值,之后的出栈次数也就是该值。
(2)代码实现:
private Queue<Integer> queue = new LinkedList<>();
public void push(int x) {
// 记录队列中的元素个数
int num = queue.size();
// 新元素先入队列
queue.offer(x);
// 将旧元素依次入队再出队,新元素就在了队首
for (int i = 0; i < num; i++) {
queue.offer(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
4.2 双队列实现栈
(1)主要思想:交换引用:创建两个队列q1和q2,让新元素入q2,q1保存所有的元素;如果q1为空,则直接交换两者的名字(也就是此时只有一个元素入队列);如果q1中不为空,则此时现将q1的所有元素弹出再入q2,再交换两者的名字。此时q1中的弹出顺序就与栈的顺序一致了。
(2)代码实现
//创建两个队列
//q1用来存储所有的元素值,q2作为辅助
Queue<Integer> q1 = new LinkedList<>();
Queue<Integer> q2 = new LinkedList<>();
//入队列
public void push(int x) {
//让所有的新元素入q2
q2.offer(x);
//判断q1是否有元素:有元素则要弹出并添加到q2
while (!q1.isEmpty()){
q2.offer(q1.poll());
}
//交换两者的名字
Queue<Integer> temp = q1;
q1 = q2;
q2 = temp;
}
//弹出
public int pop() {
return q1.poll();
}
//查看队首元素
public int top() {
return q1.peek();
}
//判断队列是否为空
public boolean empty() {
return q1.isEmpty();
}
4.3 双栈实现队列
(1)主要思想:s1存放所有的元素,s2作为辅助。我们的目标是希望将新元素存在s1的栈底,如果s1中没有元素,那直接存;但是如果s1中有元素,就将s1中的所有元素全部弹出先存到s2中,再将新元素存入s1,此时新元素就处于s1的栈底。 然后,我们再将s2中的元素全部弹出压入s1中。即符合出队列的特点。
(2)代码实现
//先创建两个栈:s1存储所有元素,s2出栈
Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();
//入栈
public void push(int x) {
//如果s1中有元素,先弹出将栈底空出来
while (!s1.isEmpty()){
s2.push(s1.pop());
}
//将x添加到s1的栈底
s1.push(x);
//将s2的值依次弹出来压入到s1中
while (!s2.isEmpty()){
s1.push(s2.pop());
}
}
public int pop() {
return s1.pop();
}
public int peek() {
return s1.peek();
}
public boolean empty() {
return s1.isEmpty();
}
关于栈的简单学习就到这里,明天也要加油呀~z