队列:Queue是一个普通的队列,Deque是一个双端队列
普通的队列:只能是队尾进,队头出;
双端队列:可以从队头进队尾出,也可以从队尾进,队头出,也可以用作一个栈;
1)下面我们来介绍一下Queue的实现方法:
在有容量限制的情况下,add方法可能会抛出异常,但是offer方法不会抛出异常
offer() poll() peek() 容量满了都不会抛出异常 add() remove() element() 容量满了会抛出异常
1)add(E)
2)remove(E)
3)poll()
4)remove()
5)element()
6)peek()
2)下面我们来介绍一下Deque的常用方法:双端队列也是可以当作栈使用
2.1)删除双端队列中的队首元素
removeFirst() removeList() pollFirst() pollLast()
2.2)获取双端队列中的队首元素
getFirst() getLast() peekLast() peekFirst()2.3)新增元素
add() addFirst() addLast()offer() offerFirst() offerLast()
addFirst()和addLast()底层默认是调用LinkFirst(元素)和LinkLast(元素)
Queue queue=new LinkedList();
//1放元素
queue.add(1); offer()
queue.add(2);
//2获取队头元素
System.out.println(queue.peek());
System.out.println(queue.element());
//3出队列的元素
System.out.println(queue.poll());
System.out.println(queue.remove());
Deque<Integer> queue=new LinkedList<>();
//queue.add(1);//默认是从队尾进入的 offerFirst
queue.addFirst(1);//默认是队头入队的
queue.addLast(2);//默认是从队头入队的 offerLast
queue.addFirst(3);
queue.addLast(4);
System.out.println(queue.peekFirst());
System.out.println(queue.peekLast());
//上面打印的结果是3,4
1)对于LinkedList,它不仅可以作为普通的队列,还可以作为双端队列,还可以作为双向链表(里面有一个头指针和一个尾指针),还可以作为栈来进行使用,还有一个尴尬的方法
add(int index,E element)
2)ArrayList是一个顺序存储,物理内存是连续的,逻辑上也是连续的,但是LinkedList在物理内存上面是不连续的,但是逻辑上是连续的
3)说区别一定要从从增删改查和内存存储来说,新增有什么区别?删除有什么区别?修改有什么元素?查找有什么区别?
下面用一个单链表来实现一个队列
对于队列来说,如果说我们是使用数组来进行实现队列,那么出队列是从数组头上面出数据,那么效率会变得非常低
4)LinkedList实现了list接口,Deque接口,LinkedList重写了Deque的方法和Queue的方法
Queue<Integer> queue=new LinkedList<>();//这是把LinkedList当做一个普通的队列来进行使用 Deque<Integer> deque=new LinkedList<>();//这是把LinkedList当做一个双端队列来进行使用
那么现在问题就来了?如果是单链表,哪边是队头,那边是队尾呢?
1)head是队头,tail是队尾,按照尾插法,入队操作的时间复杂度是O(N)(每次都要找尾巴节点,从尾巴节点进行插入),出队的时间复杂度是O(1),那么这时只出队头元素就行了;
2)如果Head是队尾,tail是队头,头插法入队的时间复杂度是O(1),找尾巴出队的时间复杂度就是O(N)
3)如果说我们的head是队头,我们用一个last指针指向队尾节点,每一次使用尾插法入队,那么时间复杂度就是O(1),出队列的时间复杂度就是O(1)
4)我们再进行新增节点的时候,分两种情况,第一次进行入队列的操作,和不是第一次入队列的操作;
在进行出队列的时候,我们还要进行判断一下当前队列是不是空;
自己实现一个队列
结论:head是队头,last是队尾,此时入队的时间和出队的时间复杂度就是O(1)
出队:head=head.next
入队:tail.next=node,tail=tail.next
时间复杂度:新增元素,插入元素时间复杂度都是O(1)
class Node{
public int data;
public Node next;
public Node(int data) {
this.data = data;
}
}
class MyQueue{
public Node head;
public Node tail;
public void offer(int data)
{ //创建一个新的节点
Node node=new Node(data);
if(head==null)
{
head=node;
tail=node;
}else{
tail.next=node;
tail=tail.next;
}
}
public boolean isEmpty()
{
return this.head==null;
}
public int pop()
{ //1判断对头是否为空
if(isEmpty())
{
throw new RuntimeException("此时队列时空");
}
//2只需队头出去即可
int data= head.data;
head=head.next;
return data;
}
public int peek()
{
return head.data;
}
}
泛型+链表
class Node<T>{
public T data;
public Node next;
public Node(T data)
{
this.data=data;
}
}
class MyQueue<T>{
public Node head=null;
public Node tail=null;
public int count=0;
public boolean IsEmpty()
{
return count==0;
}
public void offer(T t)
{
Node node=new Node(t);
if(head==null)
{
head=node;
tail=node;
count++;
return;
}else{
tail.next=node;
tail=tail.next;
count++;
}
}
public T peek()
{
if(IsEmpty())
{
throw new RuntimeException("当前队列的元素是空");
}
return (T)head.data;
}
public T poll()
{
if(IsEmpty())
{
throw new RuntimeException("当前队列的元素是空");
}
T data= (T) head.data;
head=head.next;
count--;
return data;
}
public int size()
{
return count;
}
}
public class HelloWorld{
public static void main(String[] args) {
MyQueue<String> queue=new MyQueue<>();
queue.offer("生命在于运动");
queue.offer("生命在于吃饭");
queue.offer("生命在于潇洒");
System.out.println(queue.size());//3
String str1= queue.peek();//生命在于运动
System.out.println(str1);
String str2=queue.poll();//生命在于运动
System.out.println(str2);
System.out.println(queue.size());//2
String str3= queue.poll();//生命在于吃饭
System.out.println(str3);
String str4= queue.peek();//生命在于潇洒
System.out.println(str4);
String str5=queue.poll();//生命在于潇洒
System.out.println(str5);
String str6=queue.poll();//抛出异常
}
}
3.设计循环队列
能不能拿数组写一个队列呢?
可以,数组要写成循环,这样才可以让数组的空间利用率达到最高
1)当新增元素的时候,让tail++,count++如果发现tail已经加到了数组的末尾,就将tail=0;
2)出循环队列的时候,让head++,count--如果此时发现了head已经减到了数组的末尾,就将head=0;
在这里面我们要注意一些点:当我们设置循环队列的时候,head==tail的时候,有两种情况
1)还没有进行放元素的时候
2)当整个循环队列的内容就已经满了
这里面返回队尾元素的时候:
1)tail永远是比咱们的放的元素位置+1,返回队首元素的时候直接返回array[tail-1]
2)如果tail的下标变成0了,那么直接返回array[array.length-1]
class MyCircularQueue {
private int[] arr1;
int count=0;
int head=0;
int tail=0;
public MyCircularQueue(int k) {
this.arr1=new int[k];
}
public boolean enQueue(int value) {
if(isFull())
{
return false;
}
arr1[tail]=value;
tail++;
count++;
if(tail==arr1.length)
{
tail=0;
}
return true;
}
public boolean deQueue() {
if(isEmpty())
{
return false;
}
head++;
if(head==arr1.length)
{
head=0;
}
count--;
return true;
}
public int Front() {
if(isEmpty())
{
return -1;
}
return arr1[head];
}
public int Rear() {
if(count==0)
{
return -1;
}
if(tail==0) return arr1[arr1.length-1];
return arr1[tail-1];
}
public boolean isEmpty() {
return count==0;
}
public boolean isFull() {
return count==arr1.length;
}
}
拓展:当我们的tail下角标和head的下角标相等的时候,用数组实现的循环队列中的元素可能是空,也有可能是满的,那么当tail==head的时候,我们该如何进行判断里面的元素是否为空,或者是否满了呢?
1)第一种方法,我们用count来记录数据的个数
2)我们使用标志位的方式来进行判断,标识位初始值是false
if(tail==head&&flag==false)表示队列此时为空
if(tail!=head&&flag==false)表示队列没有满
if(tail==head&&flag==true)表示队列此时为满
每当我们新增元素,我们就将flag置为true,如果说我们每一次删除元素,我们就将flag置为false
class MyCircularQueue { public int head=0; public int tail=0; boolean flag; public int[] array; public MyCircularQueue(int k) { this.array=new int[k]; this.flag=false; } public boolean enQueue(int value) { if(isFull()){ return false; } array[tail]=value; flag=true; tail++; if(tail==array.length){ tail=0; } return true; } public boolean deQueue() { if(isEmpty()){ return false; } head++; flag=false; if(head==array.length){ head=0; } return true; } public int Front() { if(isEmpty()) return -1; return array[head]; } public int Rear() { if(isEmpty()) return -1; //这里面的条件不可以写成if(isFull()) if(tail==0) return array[array.length-1]; return array[tail-1]; } public boolean isEmpty() { return head==tail&&flag==false; } public boolean isFull() { return head==tail&&flag==true; } }
3)我们进行浪费一块空间tail永远指向空格子
每一次我们存放元素之前,我们都来进行检查一下看看tail的下一个是不是head,如果是那么说明就是满的,那么我们就不存放元素了
if((tail+1)%array.length==head)表示此时循环队列的元素已经满了
if(tail==head)说明此时队列为空
class MyCircularQueue { public int head=0;//队头下标 public int tail=0;//队尾下标 public int[] array; public MyCircularQueue(int k) { this.array=new int[k+1]; } public boolean enQueue(int value) { if(isFull()) return false; array[tail]=value; tail++; if(tail==array.length){ tail=0; } //tail=(tail+1)%array.length; return true; } public boolean deQueue() { if(isEmpty()){ return false; } head++; if(head==array.length) head=0; //head=(head+1)%array.length return true; } public int Front() { if(isEmpty()){ return -1; } return this.array[head]; } public int Rear() { if(isEmpty()){ return -1; } if(tail==0){ return array[array.length-1]; } return array[tail-1]; } public boolean isEmpty() { return this.head==this.tail; } public boolean isFull() { return (this.tail+1)%array.length==head; } }
练习题1:用队列来实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。题解:栈先进后出的数据结构,队列是先进先出的数据结构
刚一开始进栈的时候,两个队列都是空的,所以先进哪一个都可以,我们此时就约定先进第1个吧;
1)每次入栈的时候都入到不为空的队列
2)出栈,一个不为空队列的中的所有元素-1都出队列,放到另一个为空队列B里面,然后最后的出栈操作,剩下的元素就是出栈的元素
3)写OJ题的时候,Queue<Integer> queue=new LinkedList<>();
你进行获取栈顶元素的时候不能影响到(进行操作队列的时候,不能影响到原来的顺序,保存的元素一定是完整的)
class MyStack{
private Queue<Integer> queue1=null;
private Queue<Integer> queue2=null;
public MyStack()
{
this.queue1=new LinkedList<>();
this.queue2=new LinkedList<>();
}
public void push(int data)
{
if(!queue1.isEmpty())
{
queue1.add(data);
}else if(!queue2.isEmpty())
{
queue2.add(data);
}else{
//这种情况下两个队列都是空队列,我们默认放到第一个队列当中
queue1.add(data);
}
}
public int pop()
{
if(!queue1.isEmpty())
{
int size=queue1.size();
for(int i=0;i<size-1;i++)
{
int num=queue1.poll();
queue2.add(num);
}
return queue1.poll();
}else{
int size= queue2.size();
for(int i=0;i<size-1;i++)
{
int num= queue2.size();
queue1.add(num);
}
return queue2.poll();
}
}
}
练习题2:用栈来实现队列
题解:我们使用两个栈来进行实现一个队列
我们入队列指定到入到S1这个栈
我们出队列指定到出S2这个栈,我们如果发现S2这个栈是空的,那么就把S1中的队列中的元素全部放到S2中,再进行出栈顶元素
class MyQueue {
public Stack<Integer> stack1=null;
public Stack<Integer> stack2=null;
public MyQueue() {
this.stack1=new Stack<>();
this.stack2=new Stack<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if(!stack2.isEmpty())
{
int data=stack2.pop();
return data;
}else{
int count=stack1.size();
for(int i=0;i<count-1;i++)
{
int data=stack1.pop();
stack2.add(data);
}
return stack1.pop();
}
}
public int peek() {
if(!stack2.isEmpty())
{
int data=stack2.peek();
return data;
}else{
int count=stack1.size();
for(int i=0;i<count-1;i++)
{
int data=stack1.pop();
stack2.add(data);
}
int str=stack1.pop();
stack2.push(str);
return str;
}
}
public boolean empty(){
return stack1.isEmpty()&&stack2.isEmpty();
}
}