概念及类型
概念
- 队列是一种数据结构,可以用顺序结构或链式结构来实现
- 队列遵循先进先出的规则
类型
- 单向队列:即最原始的队列。满足队尾进元素,从队头出元素的特点。对于单向队列而言,当用顺序结构(即数组)去实现时,存在内存浪费的现象。如图,当确定了队列的最大空间时,即使队首元素出队列,队首元素所占用的位置仍不能被队列中其他元素使用,会造成数组的浪费。
因此我们可以用链式结构来实现队列。我们应该思考这样一个问题:用单链表实现队列时如何才能让出队入队的操作变得更加便捷。根据队列先进先出的特点,我们可以考虑用头插法添加元素,这样队首即为链表的头节点,即出队的时间复杂度为O(1)。 - 双端队列:即队列元素的进出不局限于队列的某一端,在队列的两端均可以实现元素的进出。对于双端队列而言,我们通常使用双向链表来实现。
- 循环队列:在上面我们提到利用数组去实现队列时存在内存浪费的现象。因此我们可以考虑让队列首尾衔接构成一个循环。但循环队列存在这样一个问题:即队满和队空时判断条件一致:
因此我们需要添加其他的条件来让区分队空和队满。以下提供三种方法:
1. 添加useSize来描述队列的大小。当useSize为0时即为队空,为队列最大数量时即为队满。
2. 添加一个flag标志区分队满和队空。队满时flag为true,队空时flag为false,我们将flag初始化为false,当有元素入队时,flag置为true;当有元素出队时,flag置为false。这样队空的条件即为:flag == false&&front == rear;队满的条件为:flag == true&&fornt == rear。
3. 单独让出一个空间。我们规定队空时为front == rear;队满时为(rear+1%MaxSize(队列最大数量) == front。
对于循环队列,我们只剩下一个问题:如何实现循环?这里我们利用取模来设计循环。设队列的最大数量为MAXSIZE,这时出队的操作为:rear = (rear+1) % MAXSIZE;出队的操作为:front = (front + 1) % MAXSIZE; 队列中有效元素的个数位:(MAXSIZE + rear - front) % MAXSIZE。
相关题目(简单)
用栈实现队列
-
题目介绍
-
思路分析
- 我们知道栈的特点是先进后出,队列的特点是先出后进。两种数据结构特点的矛盾决定了用一个栈是无法实现队列的。因此我们需要考虑用多个栈去实现队列。
- 我们可以用两个栈实现队列,一个栈主要负责实现队列元素入,一个栈主要负责实现队列元素的出。对于第一个栈,只需要将元素压入栈中即可。对于第二个栈,当队列需要元素出时,我们需要将第一个栈中的所有元素按顺序压入第二个栈,然后弹出第二个栈的栈顶元素即可。
-
相关代码片段
public 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(!stack2.empty()){
return stack2.pop();
}else{
//当出栈为空栈时将入栈中的所有元素压入出栈中然后弹出出栈中的栈顶元素
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek(){
//与弹出元素类似
if(!stack2.empty()){
return stack2.peek();
}else{
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty(){
//当两个栈中元素均为空时说明队列为空
return stack1.empty()&&stack2.empty();
}
}
用队列实现栈
-
题目介绍
-
思路分析
- 和上一个题一样,我们考虑用两个队列实现栈。
- 与上一个题不太一样的地方在于,两个栈实现队列时两个栈的分工是十分明确的,而用两个队列实现栈时,两个队列的分工是不明确的,因为你将一个队列中的所有元素放入另一个队列中,元素在两个队列中的排放位置是一致的。因此,我们在考虑当栈要弹出元素时,我们将非空队列中的除最后一个元素之外的所有元素放入另外一个空队列,然后弹出该队列中剩下的唯一一个元素;在向栈中压入元素时,我们要将元素加入两个队列当中非空的那一个队列。
-
相关代码片段
public Queue<Integer> queue1;
public Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
//将元素放入非空的队列中
if(queue1.isEmpty()&&queue2.isEmpty()){
queue1.offer(x);
}else if(!queue1.isEmpty()){
queue1.offer(x);
}else{
queue2.offer(x);
}
}
public int pop() {
//将非空队列中除去最后一个元素的所有元素放入零一个空队列,然后弹出最后一个元素
if(!queue1.isEmpty()){
while(queue1.size()!=1){
queue2.offer(queue1.poll());
}
return queue1.poll();
}else{
while(queue2.size()!=1){
queue1.offer(queue2.poll());
}
return queue2.poll();
}
}
public int top() {
if(!queue1.isEmpty()){
//和pop类似,但是在对最后一个元素的处理时,不仅要弹出,还要将最后一个元素也放入另外一个队列
while(queue1.size()!=1){
queue2.offer(queue1.poll());
}
int res = queue1.poll();
queue2.offer(res);
return res;
}else{
while(queue2.size()!=1){
queue1.offer(queue2.poll());
}
int res = queue2.poll();
queue1.offer(res);
return res;
}
}
public boolean empty() {
//当两个队列均为空时说明栈为空
return queue1.isEmpty()&&queue2.isEmpty();
}
总结
本文主要介绍了数据结构中的队列,详细介绍了队列的三种常见类型:单向队列、双端队列以及循环队列,并且重点介绍了适合实现三种队列的逻辑结构以及循环队列是如何处理队空和队满的矛盾的,最后列举了两道比较简单的题目来巩固队列这一数据结构。希望大家能够有所收获!