Java数据结构与算法——队列(queue)

一、定义

队列(queue)—— 是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。先进先出(First In First Out),简称 FIFO。允许插入的一端叫队尾(rear),允许删除的一端叫队头(front)

二、队列的顺序存储结构(循环队列)

1、单队列

(1)与栈不同的是,队列中的数据不总是从数组的 0 下标开始的,移除一些队头 front 的数据后,队头指针会指向一个较高的下标位置,如下图:
在这里插入图片描述
(2)同样,添加数据时,队尾的指针 rear 也会向下标大的方向移动。

(3)添加多个数据后,队尾指针很快就移动到数组的最末端,这时候可能移除过数据,队头会有空闲的位置;此时,再添加数据的话,由于队尾不能再向上移动(再向后加,就会产生数组越界的错误),可实际上数组还有空闲的位置,这种现象叫做 “假溢出”,如下图:
在这里插入图片描述
为了避免上述情况,我们可以让队尾指针绕回到数组开始的位置,也就是循环队列。如下图:
在这里插入图片描述

2、循环队列

把队列的头尾相接的顺序存储结构称为循环队列。为了方便,我们让队头 front 指向队列的第一个元素,队尾 rear 指向队列的最后一个元素的后一个位置。如下图:
在这里插入图片描述
(1)队列空的条件:

front = rear;

(2)队列满的条件:

当数组中仅剩一个空闲单元的时候,就认为队列已经满了(此时,rear指向的就是剩余的那个空闲单元)。所以队列满的条件为(rear + 1)% QueueSize == front;

(3)队列的长度(数组中实际的元素个数):

  • 当 rear > front 时,此时队列的长度为 rear - front;
  • 当 rear < front 时,队列的长度分为两段,一段是 QueueSize - front,另一段是 0 + rear,加在一起,队列的长度为 rear + QueueSize - front;

因此,通用的计算队列长度的公式为:(rear - front + QueueSize)% QueueSize。

循环队列代码实现:

public class CircularQueue {
	private Object[] array;
	private int maxSize;// 最大容量
	private int front;// 队头——指向队列的第一个元素
	private int rear;// 队尾——指向队列的最后一个元素的后一个位置
	
	// 创建指定大小的队列
	public CircularQueue(int size){
		maxSize = size;
		array = new Object[maxSize];
		front = 0;
		rear = 0;
	}
		
	//判断队列是否已满(当数组中仅剩一个空闲单元的时候,就认为队列已经满了)
	public boolean isFull(){
		return (rear + 1) % maxSize == front;
	}
		
	//判断队列是否为空
	public boolean isEmpty(){
		return front == rear;
	}
		
	//返回队列的大小
	public int size(){
		return (rear + maxSize - front) % maxSize;
	}
	
	// 添加元素
	public void insert(Object element){
		if(isFull()){
			expand();// 满了,扩容
		}
		array[rear] = element;// 添加数据
		rear = (rear + 1) % maxSize;// rear后移
	}
	
	//返回队头数据
	public Object peek(){
		if(isEmpty()){
			throw new RuntimeException("队列为空!");
		}
		return array[front];
	}
	
	// 出队
	public Object pop(){
		if(isEmpty()){
			throw new RuntimeException("队列为空!");
		}
		Object data = array[front];
		front = (front + 1) % maxSize;
		return data;
	}
	
	/*
	 * 扩容,扩大一倍(满了才扩容):
	 * 		队列满有两种情况:一种是 rear > front,一种是 rear < front;
	 * 		扩容后要调整rear和front的指向
	 */
	private void expand(){
		Object[] newArray = new Object[maxSize * 2];
		if(front == 0){// rear > front 
			array = Arrays.copyOf(array, maxSize * 2);
		}else{// rear < front
			// 复制元素
			for (int i = front; i < maxSize; i++) {
				newArray[i] = array[i];
			}
			int j = maxSize;
			for (int i = 0; i < rear; i++) {
				newArray[j++] = array[i];
			}
			// 恢复front和rear的指向(front没变)
			rear = maxSize + rear;
			array = newArray;
		}
	}
	
	public static void main(String[] args) {
		CircularQueue queue = new CircularQueue(10);
		System.out.println(queue.isEmpty());
		System.out.println(queue.isFull());
		
		for (int i = 0; i < 9; i++) {
			queue.insert(i + 10);
		}
		System.out.println(queue.isEmpty());
		System.out.println(queue.isFull());
		System.out.println(queue.size());
		
		System.out.println(queue.peek());
		System.out.println(queue.pop());
		System.out.println(queue.peek());
	}

}

三、队列的链式存储结构(链队列)

队列的链式存储结构其实就是线性表的单链表,只不过它只能尾进头出而已,简称链队列

为了方便,将队头指针指向链队列的头结点,队尾指针指向终端结点。空队列时,front 和 rear 都指向头结点。

完整代码实现:

public class LinkQueue<T> {
	// 内部类,结点类
	private class Node{
		private T data;// 数据域
		private Node next;// 指针域
		
		public Node(){ }
		
		public Node(T data, Node next){
			this.data = data;
			this.next = next;
		}
	}
	
	private Node front;// 队头(指链队列的头结点)
	private Node rear;// 队尾(指向终端结点)
	private int size;// 队列中元素的数目
	
	// 创建一个空队列,空队列时,front和rear都指向头结点
	public LinkQueue(){
		Node node = new Node();
		node.data = null;
		node.next = null;
		front = rear = node;
	}
	
	// 判空
	public boolean isEmpty(){
		return front == rear;
	}
	
	// 队列的大小
	public int size(){
		return size;
	}
	
	// 入队(插入)
	public void insert(T element){
		Node newNode = new Node(element, null);// 入队的结点没有后继结点
		rear.next = newNode;
		rear = newNode; //更改rear的指向
		size++;
	}
	
	// 出队(删除)
	public T remove(){
		if(isEmpty()){
			throw new RuntimeException("队列为空!");
		}else{
			Node temp = front.next;
			front.next = temp.next;
			// 如果队列中只有一个元素,出队后即为空队列,要让rear和front都指向头结点
			if(temp.next == null){
				rear = front;
			}
			size--;
			return temp.data;
		}
	}
	
	// 打印
	public void display(){
		Node node = front.next;
		while(node != null){
			System.out.print(node.data + " ");
			node = node.next;
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		LinkQueue<Integer> queue = new LinkQueue<Integer>();
		System.out.println(queue.isEmpty());
		
		for (int i = 0; i < 5; i++) {
			queue.insert(i + 100);
		}
		System.out.println(queue.isEmpty());
		System.out.println(queue.size());
		
		queue.display();
		System.out.println(queue.remove());
		queue.display();
	}
}

四、优先级队列

优先级队列(priority queue)是比栈和队列更专用的数据结构,在优先级队列中,数据项按照关键字进行排序,关键字最小(或者最大)的数据项往往在队列的最前面,而数据项在插入的时候都会插入到合适的位置以确保队列的有序。

优先级队列是 0 个或多个元素的集合,每个元素都有一个优先权。优先级队列常用的操作是插入和删除。对于优先权相同的元素,可按先进先出次序处理。

这里我们用数组实现优先级队列,声明为 int 类型的数组,关键字是数组里面的元素,在插入数据时按照从大到小的顺序排列,也就是越小的元素优先级越高(最小关键字的数据项总是在数据的最高下标值处,而最大关键字的数据项总是在下标值为 0 的位置上)。

代码如下:

public class PriorityQueue {
	private int maxSize;// 最大容量
	private int[] queueArray;
	private int nItems;// 队列中的数据项数
	
	// 创建指定大小的数组,用以存放队列元素(队列为空)
	public PriorityQueue(int size){
		maxSize = size;
		queueArray = new int[maxSize];
		nItems = 0;
	}
	
	// 插入
	public void insert(int data){
		if(nItems == 0){// 空队列,从 0 开始插入
			queueArray[nItems++] = data;
		}else{
			int i = nItems - 1;
			// 插入排序,按从大到小的顺序,数据越小对应的下标越大
			while(i >= 0 && data > queueArray[i]){
				queueArray[i + 1] = queueArray[i];
				i--;
			}
			queueArray[i + 1] = data;// 插入数据
			nItems++;
		}
	}
	
	// 删除
	public int remove(){
		return queueArray[--nItems];
	}
	
	// 打印
	public void display(){
		for (int i = 0; i < nItems; i++) {
			System.out.print(queueArray[i] + " ");
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		PriorityQueue queue = new PriorityQueue(10);
		queue.insert(13);
		queue.insert(33);
		queue.insert(56);
		queue.insert(24);
		queue.insert(8);
		queue.display();
		queue.remove();
  		queue.display();
	}
}
发布了56 篇原创文章 · 获赞 0 · 访问量 956

猜你喜欢

转载自blog.csdn.net/weixin_45594025/article/details/104353087