[1]队列的定义
队列是一种操作受限制的线性表,其限制为仅允许在表的一端进行插入操作,而在表的另一端进行删除操作。
把进行插入的一端称为队尾,把进行删除的一端称为队头或队首,插入元素叫进队,删除元素叫出队,队列的特点和栈相反,队列是先进先出。
[2]队列的实现
- 队列的顺序存储结构基本算法实现
采用顺序存储结构的队列称为顺序队。
结构:这里用一个数组来存储元素,将队头/尾指针分别指向数组的头/尾。
private int size;//队列的最大容量
private int rear;//队尾指针
private int front;//队头指针
private Object[] data;//存放队列元素的数组
- 所以:
队空的条件:front == rear
队满的条件:rear == size-1
进队操作:先将rear增1,然后将元素放在data数组的rear位置
出队操作:先将front增1,然后取出data数组中front位置的元素
实现:
package club.leyvan.list.queue;
/**
* @Auther: http://www.leyvan.club
* @Date: 2019/9/10
* @Description: club.leyvan.list.queue
* @version: 1.0
*/
public class SqQueue {
private int size;//队列的最大容量
private int capacity;
private int rear;//队尾指针
private int front;//队头指针
private Object[] data;//存放队列元素的数组
public SqQueue(){
size = 0;
rear = front = -1;
data = new Object[16];
capacity = 16;
}
public SqQueue(int capacity){
size = 0;
rear = front = -1;
if(capacity>=0) {
data = new Object[capacity];
}else {
data = new Object[16];
}
this.capacity = capacity;
}
public boolean isEmpty(){
return rear == front;
}
public int size(){
return size;
}
/**
* 进队
*/
public boolean enQueue(Object obj){
if(rear==capacity-1){
return false;
}
rear++;
data[rear] = obj;
size++;
return true;
}
/**
* 出队
*/
public Object deQueue(){
if(rear==front){
return false;
}
front++;
size--;
return data[front];
}
public String toString(){
String retVal = "[";
if(rear<=front){
return retVal+"]";
}
for(int i = front+1;i<=rear;i++){
if(i==rear){
retVal+=data[i];
}else{
retVal+=data[i]+",";
}
}
return retVal+"]";
}
public static void main(String[] args) {
SqQueue queue = new SqQueue();
for(int i = 0;i<16;i++){
queue.enQueue(i);
}
System.out.println(queue);
System.out.println(queue.size());
for(int j = 0;j<16;j++){
queue.deQueue();
}
System.out.println(queue);
System.out.println(queue.size());
}
}
结果:
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
16
[]
0
存在问题:
在元素进队时队尾指针rear增1,元素出队时对头指针front增1,当队满的条件(即rear==capacity-1)成立时,表示此时队满了。实际上,当rear == capacity-1成立时,队列中可能还有空位置,这种因为队满条件设置不合理导致队满条件成立而队列中有空位置的情况称为假溢出。
解决方法: 环形队列
采用模运算:
队头指针front循环增1:front=(front+1)%capacity
队尾指针rear循环增1:rear=(rear+1)%capactiy
因此判断队空的条件是:rear == front,队满的条件(rear+1)%capacity == front。
改进后的进队出队方法:
public boolean enQueue(Object obj){
if((rear+1)%capacity==front){
return false;
}
rear=(rear+1)%capacity;
data[rear] = obj;
size++;
return true;
}
public Object deQueue(){
if(rear==front){
return false;
}
front=(front+1)%capacity;
size--;
return data[front];
}
但这种情况最少要有一个存储位置一定为空,当数组的容量为5的时候,只能存储4个元素。
解决办法: 用队列中元素的个数代替队尾指针。
判空:size == 0
判满:size==capacity
添加:(rear+1)%capacity
删除:(front+1)%capacity
rear=(front+size)%capacity
public boolean enQueue2(Object obj){
int rear;//通过计算后得到的尾指针
if(size==capacity) { //改动1
return false;
}else {
rear = (front+size)%capacity; //改动2
rear = (rear+1)%capacity;
data[rear] = obj;
size++;
return true;
}
}
public Object deQueue2(){
Object retVal = null;
if(count==0) { //改动3
return retVal;
}else {
front = (front+1)%capacity;
retVal = data[front];
size--;
return retVal;
}
}
public boolean isEmpty2(){
return size==0;
}
- 队列的链式存储结构基本算法实现
采用链式存储结构的队列称为链队
链表有多种,这里是采用单链表来实现链队。
因为链队只允许在表头进行删除操作,在表尾进行插入操作,所以需要使用队头指针front,队尾指针rear两个指针分别指向头尾结点。
实现如下:
当队空时:rear=null 或 front=null
当进入第一个元素时:rear=front=data
队满条件:不考虑
进队操作:新建一个结点存放元素,将结点插入作为尾结点
出队操作:取出队首结点的data值并删除
Node类:
package club.leyvan.list.queue;
/**
* @Auther: http://www.leyvan.club
* @Date: 2019/9/10
* @Description: club.leyvan.list.queue
* @version: 1.0
*/
public class Node {
private Object data;
private Node next = null;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
LinkedQueue类:
package club.leyvan.list.queue;
/**
* @Auther: http://www.leyvan.club
* @Date: 2019/9/10
* @Description: club.leyvan.list.queue
* @version: 1.0
*/
public class LinkedQueue {
private Node head;//头结点
private Node front;//头指针
private Node rear;//尾指针
private int size;
public LinkedQueue(){
size = 0;
head = null;
rear = front = null;
}
public boolean isEmpty(){
return rear == null;
// return size==0; 或 front == null;
}
public int size(){
return size;
}
public boolean enQueue(Object obj) {
Node node = new Node();
node.setData(obj);
node.setNext(null);
if (rear==null){
front = rear = node;
}else {
rear.setNext(node); //系绳子
rear = node;
}
size++;
return true;
}
public Object deQueue(){
Object retVal = null;
//size == 0
if(rear == null) {
return null;
}
//如果只有一个元素
if(front == rear){
retVal = front.getData();
front = rear = null;
}else{
Node p = front;
retVal = front.getData();
front = front.getNext();
p.setNext(null);
}
size--;
return retVal;
}
public String toString(){
String retVal = "[";
Node p = front;
while(p!=null){
if(p.getNext()==null){
retVal+=p.getData();
}else{
retVal+=p.getData()+",";
}
p = p.getNext();
}
return retVal+"]";
}
public static void main(String[] args) {
LinkedQueue queue = new LinkedQueue();
for(int i=0;i<10;i++){
queue.enQueue(i);
}
System.out.println(queue);
System.out.println(queue.size());
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.size());
System.out.println(queue);
}
}
结果:
[0,1,2,3,4,5,6,7,8,9]
10
0
1
8
[2,3,4,5,6,7,8,9]
出队和入队时是要注意只有一个元素的情况。
- 采用一个不带头结点只有尾结点指针的rear的循环单链表
队空条件:rear==null
队满条件:不考虑
进队操作:新建一个结点,将结点插入尾结点后,将尾结点指向新结点
出队操作:取出队尾结点的后继结点的值
public boolean enQueue2(Object obj) {
Node p = new Node();
p.setData(obj);
//只有一个元素
if(rear == rear.getNext()){
p.setNext(p);
rear = p;
}else{
p.setNext(rear.getNext());
rear.setNext(p);
rear = p;
}
size++;
return true;
}
public Object deQueue2(){
Object retVal;
Node p;
//判空
if(rear == null){
return null;
}
//如果只有一个元素
if(rear.getNext()==rear){
retVal = rear.getData();
rear.setNext(null);
rear = null;
}else{
retVal = rear.getNext().getData();
p = rear.getNext();
rear.setNext(p.getNext());
p.setNext(null);
p = null;
}
size--;
return retVal;
}
[3] 队列的应用
设有n个人站成一排,从左向右的编号为1~n,从左到右报数“1,2,1,2,1,2。。”,数到1的人出列,数到2的人到队列的最右端。直到队列空了为止。假设编号只有n可以改变。
例如当n=8时初始序列为:
1 2 3 4 5 6 7 8
出列顺序为:
1 3 5 7 2 6 4 8
算法思想:
1.出队一个元素,输出报数为1的编号
2.若队不空,再出队一个元素,并将刚出队的报数为2的编号入队
public static void number(int n){
LinkedQueue queue = new LinkedQueue();
for(int i=1;i<=n;i++){
queue.enQueue(i);
}
System.out.print("报数出列顺序:");
while(!queue.isEmpty()){
//一次循环中出队两次,第一次出队
System.out.print(queue.deQueue()+" ");
if(!queue.isEmpty()){
//出队第二次
//第二次一定是偶数2所以将出队再入队,将元素放到队尾
queue.enQueue(queue.deQueue());
}
}
System.out.println();
}
public static void main(String[] args) {
int n = 8;
System.out.print("初始序列: ");
for(int i=1;i<=n;i++){
System.out.print(i+" ");
}
System.out.println();
number(n);
}
结果:
初始序列: 1 2 3 4 5 6 7 8
报数出列顺序:1 3 5 7 2 6 4 8