一丶队列
什么是队列?
上篇博客我们说了,栈是一种后进先出的数据结构,而在这里,同为数据结构的队列又是怎么的表现形式呢?
我们用一张图来进行查看:
(1)队列的概念
队列是只允许在一端进行插入,另外一端进行删除的特殊的线性表。进行插入操作的一端叫做队尾,进行删除操作的一端叫做队头。因为这种特殊性,所以队列具有先进先出的性质。
(2)队列的分类
因为队列也是顺序结构,那么它的构造其实也分两种,顺序队列和链式队列。
1>顺序队列
顺序队列大致情况如下图所示:
在这里,关于顺序队列的操作里面,有一点需要说明。就是删除操作,
在这里删除操作有两种。
第一种:front不动
front不动进行删除的话,那么每删除一次,队列所有元素前移一位。
那么这意味着什么?每一次删除的时间复杂度都是O(N),所以这种的话就会很麻烦。
第二种:rear不动
还有一种就是尾指针rear不动,那么如果是这种方式。就会有一个很严重的问题,具体如下:
可以看到,如果是rear不动,front往后走,那么此时的时间复杂度就是O(1),但是此时有一个问题:就是当删除为空的时候,此时rear和front都在队列尾,这个时候还能插入嘛?那空间满了吗?
这个时候当然不能插入,但是空间没有满,甚至可以说都没有用到。所以就会造成假溢出。
那么为了解决这种情况,我们怎么办呢?
头尾相连,用循环链表
(看后面)
2>链式队列
这种的话,就是只在头部和尾部进行插入和删除的特殊单链表。
//先定义节点
public static class Node<E>{
private E value;//节点值
private Node<E> next;//节点的next属性
public Node(E val){
value = val;
next = null;
}
}
Node<E> front; // 标记队头
Node<E> back; // 标记队尾
int size;//记录有效节点
//插入操作
public boolean offer(E e){
Node<E> node = new Node<>(e);
if(null == front){
front = node;
}else{
back.next = node;
}
back = node;
size++;
return true;
}
//删除操作
public E poll(){
if(0 == size){
throw new RuntimeException("队列为空,无法出队列");
}
Node<E> delNode = front;
front = front.next;
if(null == front){
back = null;
}
size--;
return delNode.value;
}
//查看队列头
public E peek(){
if(0 == size){
throw new RuntimeException("队列为空,无法获取队头元素");
}
return front.value;
}
//查看是否为空
public boolean isEmpty(){
return 0 == size;
}
public int size(){
return size;
}
二丶循环队列
循环队列是用来干嘛的呢?就是为了解决我上述所说的顺序队列rear不动进行删除操作的时候所造成的的假溢出问题。
具体图示如下:
具体的代码用一道LeetCode题来进行说明,题目来源:
设计循环队列
具体的解答如下:
class MyCircularQueue {
int[] array;
int front;//定义头结点
int rear;//定义尾结点
int count;//定义有效节点
int N;//定义数组长度
//定义数组
public MyCircularQueue(int k) {
array = new int[k];
N = k;
}
//入队列
public boolean enQueue(int value) {
if(isFull()){
return false;
}
array[rear] = value;
rear++;
if(rear == N){
rear = 0;
}
count++;
return true;
}
//出队列
public boolean deQueue() {
if(isEmpty()){
return false;
}
front++;
front %= N;
count--;
return true;
}
//返回队列头
public int Front() {
if(isEmpty()){
return -1;
}
return array[front];
}
//返回队列尾
public int Rear() {
if(isEmpty()){
return -1;
}
return array[(rear + N - 1)% N];
}
//检查是否满了
public boolean isEmpty() {
return 0 == count;
}
//检查是否为空
public boolean isFull() {
return count == array.length;
}
}
这里的话有一个点需要特别说明一下。
关于返回队列尾
这里的话如果返回队列尾有一个地方需要特别说明一下。因为我们的rear指针指向最后一个元素的下一位,也就是说如果是队列满的话就是这种情况。
那么如果此时要返回队列尾的话是返回多少呢?
很明显此时rear指针下标是0,那么返回前一位就是 -1,数组越界了呀
为了解决这种问题怎么办呢?
(rear - 1 + array.length) % array.length
三丶双端队列
所谓双端队列是指可以在两端都进行插入和删除的特殊队列。这种队列在这里我们不进行过多说明,用双向链表实现一下。
public static class ListNode<E>{
ListNode<E> next;
ListNode<E> pre;
E val;
ListNode(E val){
this.val = val;
}
}
ListNode<E> front;
ListNode<E> rear;
int size = 0;
//判断是否为空
public boolean isEmpty(){
return 0 == size;
}
public int size(){
return size;
}
//入队列
public void offer(E e){
ListNode<E> newNode = new ListNode<>(e);
if(isEmpty()){
front = newNode;
}else{
rear.next = newNode;
newNode.pre = rear;
}
rear = newNode;
size++;
}
//出队列
public E poll(){
E key = null;
if(isEmpty()){
return null;
}
if(size == 1){
rear = null;
front = null;
}
key = rear.val;
rear = rear.pre;
rear.next.pre = null;
rear.next = null;
size--;
return key;
}
//获取队头
public E peek(){
return front.val;
}
四丶总结
刷题刷题!