数据结构之队列(Queue)

什么是队列(Queue)

之前总结过相关的知识,队列可以类比栈来看。栈只能在一端进行操作(栈顶),添加元素或者删除等都只能在栈顶;而队列有两端可以操作,在一端添加元素,在另一端删除元素

我们把添加元素的一端称为队尾;删除元素的一端称为队首

比如生活中的排队:城市中基本哪里都有,这就是一个队列。在队伍最前面就是队首,也是最先完成离开队伍的。新来的只能在队尾加入。

queue_overview

队列的特点:

队列操作及实现概述

实现

用链表、数组都比较容易实现队列。

链表实现:比较简单,在链表首部删除,链表尾部插入即可。
链表没有长度限制,不会造成溢出,而且插入删除也很简单,是很适合的实现方式。

数组实现:普通数组方式,有着明显的缺陷,长度固定、出队时剩下所有元素都要向前挪动一位,这种出队方式时间复杂度就是O(n)了。在此基础上进行一些处理,形成一个环,成为一个循环队列

所以下面主要介绍的就是两种队列实现,用链表实现的队列 和 用数组实现的循环队列

操作

队列主要两个操作,入队和出队。
入队(enQueue): 即在队尾插入新元素。
出队(deQueue): 即在队首删除一个元素。
如果自己实现,可以添加更多公共操作,如队长、是否队空或队满、查找等等。

下面实现中,为了更直观的显示出队、入队操作,都添加了一个displayQueue()的方法。

单链表队列实现

单链表队列实现很简单,链表首部即队首,链表尾部即队尾,定义两个引用分别指向队首和队尾,出队、入队直接删除和插入即可。时间复杂度为O(1)。
优势,没有额外的操作,没有长度的限制 不需担心溢出。

示意图大致如下:
queue_linked

具体代码如下(可结合注释,应该比较清晰):

public class QueueLinkedList<E> {
	
	public static void main(String[] args) {
	    //入队1,2,3,4,5;然后出队2次
		QueueLinkedList<Integer> queueLinkedList = new QueueLinkedList<Integer>();
		queueLinkedList.displayQueue();
		for (int i = 1; i < 6; i++) {
			queueLinkedList.enQueue(Integer.valueOf(i));
		}
		queueLinkedList.displayQueue();
		queueLinkedList.deQueue();
		queueLinkedList.displayQueue();
		queueLinkedList.deQueue();
		queueLinkedList.displayQueue();
	}
	
	//定义队首引用,指向链表首部
	private QueueNode<E> front;
	//定义队尾引用,指向链表尾部
	private QueueNode<E> rear;
	
	//节点定义
	static class QueueNode<E> {
		E data;
		QueueNode<E> next;
		
		public QueueNode(E data) {
			this.data = data;
		}
	}
	
	//初始化,空的队列。
	public QueueLinkedList() {
		this.front = this.rear = null;
	}
	
	//入队,链表尾部插入
	public void enQueue(E value) {
		QueueNode<E> newNode = new QueueNode<E>(value);
		//空队列时
		if (this.rear == null) {
			this.front = this.rear = newNode;
			return;
		}
		this.rear.next = newNode;
		this.rear = newNode;
	}
	
	//出队,链表首部删除
	public E deQueue() {
		//队列为空时
		if (this.front == null) 
			return null;
		E result = this.front.data;
		this.front = this.front.next;
		//队列中只有一个元素时
		if (this.front == null) 
			this.rear = null;
		return result;
	}
	
	//打印队列所有元素,以及队首、队尾信息
	public void displayQueue() {
		if (this.front == null) {
			System.out.println("front:null<-...<-rear:null");
			System.out.println("This is an empty queue!");
			System.out.println();
			return;
		}
		System.out.println("front:" + this.front.data +"<-...<-rear:" + this.rear.data);
		QueueNode<E> tmpNode = this.front;
		while (tmpNode != null) {
			System.out.print(tmpNode.data + "<-");
			tmpNode = tmpNode.next;
		}
		System.out.println();
		System.out.println();
	}
}

实验结果为:

front:null<-...<-rear:null
This is an empty queue!

front:1<-...<-rear:5
1<-2<-3<-4<-5<-

front:2<-...<-rear:5
2<-3<-4<-5<-

front:3<-...<-rear:5
3<-4<-5<-

数组循环队列实现

若用普通数组实现队列,就会发现每次删除队首时所有元素都需要向前移动一位,这样实现肯定是不合适的。
于是,在上述基础上做一些处理,便形成了循环队列:将数组队列看出一个首尾相连的环形。用front和rear表示队首和队尾相关元素的下标(不一定是front是队首下标,rear是队尾下表),这样就能很容易确定队列的元素是哪些了。出队、入队改变这两个值即可而不需要移动数组中元素了。

下面实现用变量front和rear标识队首、队尾的下标,入队和出队的方式基本一致:数组大小为capacity,即front=(font+1)%capacity 或 rear=(rear+1)%capacity。
也可以用front标识队首,rear标识队尾的下一个位置,只是不同的标识含义, 初始化队列为空队列为满 的判断和处理需要注意,是不同的。 (rear标识队尾下一个位置时,初始化front=rear=0,空即front==rear。而rear标识队尾即下面的实现,可以具体看下)

大致示意图如下(front和rear标识队首、队尾的下标):
queue_circle_array

实现代码(可结合注释查看):
注:实现中的判空与判满 使用了另一个变量queueCount表示队列元素个数,便于返回队列大小。也可以使用front和rear之间关系进行判断

public class QueueCircleArray {

	public static void main(String[] args) {
	    //队列入队1,2,3,4;然后两次出队;然后入队10,20
		QueueCircleArray queueCircleArray = new QueueCircleArray(4);
		queueCircleArray.displayQueue();
		for (int i = 1; i < 5; i++) {
			queueCircleArray.enQueue(Integer.valueOf(i));
		}
		queueCircleArray.displayQueue();
		queueCircleArray.deQueue();
		queueCircleArray.deQueue();
		queueCircleArray.displayQueue();
		queueCircleArray.enQueue(Integer.valueOf(10));
		queueCircleArray.enQueue(Integer.valueOf(20));
		queueCircleArray.displayQueue();
	}
	
	//数组及大小
	private Object[] array;
	private int capacity;
	//队首 队尾对应数组中的下标
	private int front, rear;
	//队列的大小
	private int queueCount;
	
	public QueueCircleArray() {
		this(10);
	}

	public QueueCircleArray(int capacity) {
		this.capacity = capacity;
		this.front = queueCount = 0;
		//注意这里初始化
		this.rear = this.capacity - 1;
		this.array = new Object[this.capacity];
	}
	
	public boolean isFull() {
		return this.queueCount == this.capacity;
	}
	
	public boolean isEmpty() {
		return this.queueCount == 0;
	}
	
	public void enQueue(Object value) {
		if (isFull())
			throw new RuntimeException("Queue is Full!");
		//队尾向后移动一位,即将插入数据的位置
		this.rear = (this.rear+1)%this.capacity;
		//插入数据,入队
		this.array[this.rear] = value;
		//队列大小加1
		this.queueCount++;
	}
	
	public Object deQueue() {
		if (isEmpty()) 
			throw new RuntimeException("Queue is Empty!");
		Object tmpObject = this.array[this.front];
		//队首向后移动一位,之前的位置即出队的元素。
		this.front = (this.front+1)%this.capacity;
		//队列大小减1
		this.queueCount--;
		return tmpObject;
	}
	
	public void displayQueue() {
		if (isEmpty()) {
			System.out.println("front:null<-...<-rear:null");
			System.out.println("This is an empty queue!");
			System.out.println();
			return;
		}
		int tmp = this.front;
		System.out.println("front:["+ this.front + "]" + this.array[this.front] 
				+"<-...<-rear:[" + this.rear + "]" + this.array[this.rear]);
		while(tmp != this.rear) {
			System.out.print(this.array[tmp] + "<-");
			tmp = (tmp + 1)%this.capacity;
		}
		if (tmp == this.rear)
			System.out.println(this.array[tmp]);
		System.out.println();
	}
}

实验结果:

front:null<-...<-rear:null
This is an empty queue!

front:[0]1<-...<-rear:[3]4
1<-2<-3<-4

front:[2]3<-...<-rear:[3]4
3<-4

front:[2]3<-...<-rear:[1]20
3<-4<-10<-20
posted @ 2020-06-13 21:29  流浪_归家  阅读(1337)  评论(0编辑  收藏  举报