文章目录
队列 Queue
队列的 概念与 特点 先进先出
概念队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First
In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头
Queue 实现的两个接口
Deque 和 Queue
来看LinkedList(底层是一个双向链表) 实现了 两个接口 一个是
Deque 和 Queue
Queue的常用方法
1.添加元素 offer 与 add
public class Main {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.add(1);
queue.offer(2);
System.out.println(queue.peek());
System.out.println(queue.element());
}
}
2.获取队头元素 peek 和 element
public class Main {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.add(1);
queue.offer(2);
System.out.println(queue.peek()); 1
System.out.println(queue.element()); 1
}
}
3出队列 poll 和 remove
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.add(1);
queue.offer(2);
System.out.println(queue.peek());
System.out.println(queue.element());
System.out.println(queue.poll());
System.out.println(queue.remove());
System.out.println(queue);
}
4.添加 删除 查看 队头 元素 方法的区别
这里 添加元素 一般 用的比较多 的是 offer
双端队列 Deque
双端队列的部分方法
public class Main {
public static void main(String[] args) {
Deque<Integer> deque = new LinkedList<>();
deque.offerLast(1); // Last 尾部 入队
deque.offerFirst(2);// First 头入队
deque.offer(3);// 默认队尾入队
System.out.println(deque.peekFirst());
System.out.println(deque.peekLast());
System.out.println(deque);
}
}
看完了 双端队列,知道了 LinkedList,底层是一个双向链表那么 回顾一下一看是 我们学习的 ArrayList 顺序表
这里 我们 在这里做一个小总结 ,
ArrayList 与 LinkedList 的 区别
从增删查改 的 角度 来看 ,ArrayList 底层 是一个数组,每次 删一个元素 ,是通过移动元素 完成,而LinkedList 是 链表结构,删除元素只需要 让上一个节点指向
这个节点的下一个节点,在指定位置添加 同理 ,ArrayList 还是 通过 移动元素 来完成添加,而 LinkedList 只需要改变节点的指向即可。
另外 ArrayList 在 内存中是连续的,而LinekdList 在 内存中 不是连续的(看上去是连续的,但是每个节点代表一块空间,通过引用连接在一起,所以他不是连续的)
了解完了这么多 那么 我们 来 分 三种方式 来实现我们字节 的队列
实现自己的队列
1.单链表实现
class Node {
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
}
// 使用单链表实现队列
public class MyQueue {
// 标记头
public Node head;
// 标记 尾
public Node last;
public void offer(int val) {
Node node = new Node(val);
if(head == null) {
head = node;
last = node;
}else {
last.next = node;
last = last.next;
}
}
public int poll() {
if(isempty()) {
throw new RuntimeException("队列为空");
}
int oldVal = head.val;
this.head = head.next;
return oldVal;
}
public int peek() {
if(isempty()) {
throw new RuntimeException("队列为空");
}
int oldVal = head.val;
return oldVal;
}
public boolean isempty() {
return this.head == null;
}
@Override
public String toString() {
return "MyQueue{" +
"head=" + head +
", last=" + last +
'}';
}
}
测试用例
public class Test {
public static void main(String[] args) {
MyQueue queue = new MyQueue();
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println(queue.peek());//1
System.out.println(queue.poll());//1
System.out.println(queue.poll());//2
System.out.println(queue.poll());//3
System.out.println(queue.poll());//异常
}
}
2.双向链表实现
class Node {
public int val;
public Node next;
public Node prev;
public Node(int val) {
this.val = val;
}
}
// 使用双向链表实现栈
public class MyQueue2 {
// 标记头
public Node head;
public Node cur;
public void offer(int val) {
Node node = new Node(val);
if(cur == null) {
head = node;
cur = node;
}else {
cur.next = node;
node.prev = cur;
cur = cur.next;
}
}
// 删除队头元素
public int poll() {
if(isEmpty()) {
throw new RuntimeException("队列为空无法删除");
}
int oldVal = head.val;
head.prev = null;
this.head = head.next;
return oldVal;
}
public int peek() {
if(isEmpty()) {
throw new RuntimeException("队列为空无法删除");
}
int oldVal = head.val;
return oldVal;
}
public boolean isEmpty() {
return this.head == null;
}
}
测试用例
public class Test {
public static void main(String[] args) {
MyQueue2 queue = new MyQueue2();
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
System.out.println(queue.peek());//1
System.out.println(queue.poll());//1
System.out.println(queue.poll());//2
System.out.println(queue.poll());//3
System.out.println(queue.poll());//4
System.out.println(queue.poll());//异常
}
}
通过链表实现完 队列,有没有想过 使用 数组来实现 队列呢??
在 实现数组 实现 队列 的时候 我们 要先学习一个 循环队列 ,要不 我们很难实现 队列
引出 循环队列
先来看一下 环形队列
接下来看 一下我们的小手段
数组下标循环的小技巧
-
下标最后再往后(offset 小于 array.length):
index = (index + offset) % array.length
7 走 到 2 的偏移量 offset 为 4 通过 公式 (index+ offset)% arraylength = (7 + 4) % 9 = 11 % 9 = 2
循环队列判满的三种方法
第一种解决方式 使用 usedSize
第二种 解决方式 使用标志位
创建一个boolean 变量flag 先 赋值 为 false rear每次 走 一步 flag 赋值为 true 知道 front 与 rear 相遇如果 flag 为 true 数组就为 满 ,
front 每次 走一步 flag 赋值为 false 当front 与 rear 相遇 且 flag 为false 数组就为空
第三种 浪费一个格子判断空和满
了解 了 这么 多 循环队列的 知识 那么 我们直接 上oj题
那么 开始 吧
设计循环队列
入队 和 判断队列是否满
出队 和 判断 数组 是否为空,获取 队头 和 队尾下标
完成了 方法 我们 放到 oj 上来 跑
结果发现 错误 这里 不要机 我们 来 观察 输出 和 预计 结果
接下来我们 要观察一下 oj 上 给的 错误信息
class MyCircularQueue {
public int[] elem;
public int front; // 队头
public int rear; // 队尾
public MyCircularQueue(int k) {
this.elem = new int[k+1];
}
// 入队
public boolean enQueue(int value) {
if(isFull()) return false;
this.elem[rear] = value;
rear = (rear + 1) % elem.length;
return true;
}
public boolean deQueue() {
if(isEmpty()) return false;
front = (front + 1) % elem.length;
return true;
}
/**
* Front得到队头元素
* @Front
*/
public int Front() {
// 这里 oj 上 判断 数组是否为空不用抛异常 同一返回-1即可
if(isEmpty()) {
return -1;
}
return elem[front];
}
public int Rear() {
if(isEmpty()) {
return -1;
}
int index = -1;
if(rear == 0) {
index = elem.length - 1;
}else {
index = rear - 1;
}
return elem[index];
}
public boolean isEmpty() {
return front == rear;
}
public boolean isFull() {
if((this.rear+1)%elem.length == front) {
return true;
}
return false;
}
}
方法二 使用 suedSize 来 解题
// 使用 usedSize
public class MyCircularQueue {
public int[] elem;
public int front;
public int rear;
int usedSize;
public MyCircularQueue(int k) {
this.elem = new int[k];
}
public boolean enQueue(int value) {
if(isFull()) return false;
this.elem[rear] = value;
rear = (rear+1) % elem.length;
usedSize++;
return true;
}
这里每次添加 rear 都 会 向后 一步 入 先 0 下标 添加完 1 执行rear = (rear+1) % elem.length; 就会指向2 那么 查找尾节点,就可以 返回
elem[rear - 1] , 但是 有一个特里 就是 当 rear 为 0 时 就需要 返回下标为 elem。length 的值 这里加个if 语句就ok
public boolean deQueue() {
if(isEmpty()) return false;
front = (front + 1) % elem.length;
usedSize--;
return true;
}
public int Front() {
if(isEmpty()) return -1;
return elem[front];
}
// 获得队尾元素
public int Rear() {
if(isEmpty()) return -1;
int index = -1;
if(rear == 0) {
index = elem.length - 1;
}else {
index = rear - 1;
}
return elem[index];
}
public boolean isEmpty() {
return usedSize == 0;
}
public boolean isFull() {
return usedSize == elem.length;
}
}
最后 还有一种 通过 标志位 来判 满 的 可以 尝试一下 这就 不给 出答案 ,因为我没去写嘿嘿
完成了循环队列 ,差不多 用数组 来实现 队列 就是这样那么接下来 趁热打铁继续 冲 oj题
用队列实现栈
class MyStack {
// 通过队列来实现栈
//不为空的进 ,为空出
Queue<Integer> que1 ;
Queue<Integer> que2 ;
public MyStack() {
que1 = new LinkedList<>();
que2 = new LinkedList<>();
}
public void push(int x) {
if(!que1.isEmpty()) {
que1.offer(x);
}else if(!que2.isEmpty()) {
que2.offer(x);
}else {
que1.offer(x);
}
}
public int pop() {
if(empty()) return -1;
if(!que1.isEmpty()) {
int size = que1.size() - 1;
for(int i = 0;i<size;i++) {
int ret = que1.poll();
que2.offer(ret);
}
return que1.poll();
}else {
int size = que2.size() - 1;
for(int i = 0;i<size;i++) {
int ret = que2.poll();
que1.offer(ret);
}
return que2.poll();
}
}
public int top() {
if(empty()) return -1;
int ret = 0;
if(!que1.isEmpty()) {
int size = que1.size();
for(int i = 0;i<size;i++) {
ret = que1.poll();
que2.offer(ret);
}
return ret;
}
if(!que2.isEmpty()) {
int size = que2.size();
for(int i = 0;i<size;i++) {
ret = que2.poll();
que1.offer(ret);
}
return ret;
}
return -1;
}
public boolean empty() {
return que1.isEmpty()&&que2.isEmpty();
}
}
队列实现了 栈 那么 用栈来实现 队列 不是一个道理 吗 同样使用
两个 栈 来实现 队列
我们先来了解一下逻辑
了解了 逻辑 那么直接上 oj 干就行了
用栈实现队列
class MyQueue {
public Stack<Integer> stack1;
//入队列的栈
public 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.empty()) {
int ret = stack2.pop();
return ret;
}else {
int size = stack1.size();
for(int i = 0;i<size;i++) {
int ret = stack1.pop();
stack2.push(ret);
}
return stack2.pop();
}
}
public int peek() {
if(empty()) return -1;
if(!stack2.empty()) {
int ret = stack2.peek();
return ret;
}else {
int size = stack1.size();
for(int i = 0;i<size;i++) {
int ret = stack1.pop();
stack2.push(ret);
}
return stack2.peek();
}
}
public boolean empty() {
return stack1.empty() && stack2.empty();
}
}
其实 队列 与 栈 的 要学习的 东西 不多 多的 事 如何 去使用他,向 栈 还有 单调栈 ,就是 栈顶 到 栈 低 成一个 单调递增或单调递减的趋势,我们 要知道 栈和队列的 特点 ,栈 : 先进 后出 队列 : 先进先出 ,在 去 使用栈和 队列 解决 些 练习题 就能很好的熟知他们。