如何实现队列

854 阅读4分钟

如何实现队列

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

笔者最近在学习《数据结构与算法之美》,正好借着这个机会边练习边记录一下自己学习的知识点。今天带来的是队列。

一、什么是队列

  • 队列和栈类似,也是操作受限的线性表数据结构。队列只允许在一端添加数据,另一端移除数据,也造就队列先进先出的特点。可以把队列想象成排队检票,且不能插队,排在前面的先检查通过离开,后面来的只能排在队尾。
  • 队列的基本操作也和栈类似,栈的基本操作时入栈 push() 和出栈 pop(),类似的队列也有两个基本操作,入队 enQueue () 和 出队 deQueue() 。

栈和队列.png

二、队列的常见类型

队列有两种实现方式,一是使用数组实现称为顺序队列,二是使用链表实现称为链式队列

2.1 顺序队列

数组实现队列,首先需要 head 记录队头索引和 tail 记录队尾索引。出队时将数组 head 位置的数据移除,同时 head 向后移动一位。入队操作类似,将数据添加到数组 tail 位置,同时 tail 向后移动一位。

入队和出队1.png 因为数组大小是有限的,当数据添加到数组的最后一个位置后,再添加一个新数据,就存在两种情况:

一是队列所有位置都满了,无法再添加数据,此时告诉用户添加失败即可。

二是队列的头部还存在空余位置,此时需要进行数据搬移到队列头部,再添加新数据。

入队和出队2.png 下面是具体实现代码:

/**
 * @author xuls
 * @date 2021/11/20 21:45
 * 数组实现顺序队列
 */
public class ArrayQueue <T>{
	//存储数组
	private Object[] dataArray;
	//容量
	private int capacity;
	//队头索引
	private int head;
	//队尾索引
	private int tail;

	public ArrayQueue(int capacity) {
		dataArray = new Object[capacity];
		head=0;
		tail=0;
		this.capacity = capacity;
	}

	//入队
	public boolean enQueue(T data){
		//如果 tail 索引超过数组最大长度
		if (tail == capacity){
			//队满 tail - head = capacity
			if (head ==0){
				return false;
			}
			//数据搬移
			int length = tail - head;
			for (int i = 0; i < length; i++) {
				dataArray[i] = dataArray[head+i];
			}
			head=0;
			tail=length;
		}
		//入队的同时 , tail ++ 向后移动一位
		dataArray[tail++]=data;
		return true;
	}

	//出队
	public T deQueue(){
		if (isEmpty()){
			throw new IndexOutOfBoundsException("queue is empty");
		}
		//出队的同时 head ++ 向后移动一位
		return (T)dataArray[head++];
	}

	public boolean isEmpty(){
		return head == tail;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		for (int i = head; i < tail; i++) {
			builder.append(dataArray[i]).append(",");
		}
		return "ArrayQueue={"+builder.toString()+"}";
	}
}

2.2 链式队列

队列使用链表实现,同样的需要一个 head 引用指向队头,一个 tail 引用指向队尾。入队将 tail.next 指向新结点,再把新结点设置为 tail;出队将 head.next 设置为 head。

链式队列出队出队.png

下面是具体实现代码:

/**
 * @author xuls
 * @date 2021/11/20 23:35
 * 链式队列
 */
public class LinkedListQueue<T> {
	private Node<T> head;
	private Node<T> tail;

	public LinkedListQueue() {
		head=tail=null;
	}

	//入队
	public void enQueue(T data){
		Node<T> node = new Node<>(data,null);
		if (head == null){
			head = tail =node;
		}else {
			tail.next = node;
			tail = node;
		}
	}

	//出队
	public T deQueue(){
		if (head==null){
			throw new IndexOutOfBoundsException("queue is empty");
		}
		T data = head.data;
		head = head.next;
		//出队之后,队空时将 tail 置为 null
		if (isEmpty()){
			tail = null;
		}
		return data;
	}

	public boolean isEmpty(){
		return head == null;
	}

	private class Node<T>{
		private T data;
		private Node<T> next;

		public Node(T data, Node<T> next) {
			this.data = data;
			this.next = next;
		}
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		Node<T> tempNode = head;
		while (tempNode != null){
			builder.append(tempNode.data).append(",");
			tempNode = tempNode.next;
		}
		return "LinkedListQueue{"+builder.toString()+"}";
	}
}

2.3 循环队列

在实现顺序队列时,当数据添加到数组的最后一个位置后,再添加一个新数据时,可能需要进行数据搬移。大量的数据搬移是非常耗时的,循环队列可以避免数据搬移操作,循环队列把队列的队头和队尾连接在一起,循环队列的实现思路和顺序队列基本相同,最大的不同点是循环队列如何判断队满?下图列举了一些情况,可以试着从里面总结一下规律。

循环链表.png 从图里可以总结出来:循环队列队满时 (tail+1) % capaticy = head ,也可以看过循环队列是浪费了一个存储空间的。

下面是具体实现代码:

/**
 * @author xuls
 * @date 2021/11/21 11:03
 * 循环队列
 */
public class CircleQueue<T> {
   private Object[] dataArray;
   private int capacity;
   private int head;
   private int tail;

   public CircleQueue(int capacity) {
      dataArray = new Object[capacity];
      this.capacity = capacity;
      head=tail=0;
   }

   public boolean enQueue(T data){
      if ((tail+1)%capacity == head){
         return false;
      }
      dataArray[tail]=data;
      //取模 % ,可以避免数组访问越界
      tail = (tail+1) % capacity;
      return true;
   }

   public T deQueue(){
      if (isEmpty()){
         throw new IndexOutOfBoundsException("queue is empty");
      }
      T t = (T) dataArray[head];
      //取模 % ,可以避免数组访问越界
      head = (head+1) % capacity;
      return t;
   }

   public boolean isEmpty(){
      return tail == head;
   }

   @Override
   public String toString() {
      StringBuilder builder = new StringBuilder();
      int temp = head;
      while (temp != tail){
         builder.append(dataArray[temp]).append(",");
         temp = (temp+1) % capacity;
      }
      return "CircleQueue={"+builder.toString()+"}";
   }
}

三、总结

  • 队列的特点是先进先出
  • 队列的基本操作的是入队 enQueue() 和 出队 deQueue() 。
  • 使用数组实现的叫顺序队列,使用链表实现的叫链式队列。顺序队列的数据搬移问题可以使用循环队列解决,循环队列和顺序队列最大的不同点是队满的判断条件,循环队列会浪费一个存储空间。