【Java】轻松掌握队列操作

3.1 如何实现一个排队系统

在日常生活中,处处都能看到排队。在银行、医院这种场景中还会加入排队系统,通过系统叫号的方式解决拥堵问题。在虚拟游戏世界中,为了服务器限流,也会加入一些排队策略。这样的排队列表用的就是数据结构中的 —— 队列(queue) 存储

队列和栈一样,都有约束条件,不同的约束条件决定它们的不同的使用场景

队列和排队非常类似,大家排成一排就像队列存储的元素,以食堂打饭举例,永远是排在最前面的人先离队进入影院,后来进入队列的人排在最后

在计算机中,用先进先出来总结这个特征,或者缩写为 FIFO(first in,first out)。约束条件为:

  1. 只能在末尾插入元素
  2. 只能读取开头的元素
  3. 只能移除开头的元素

3.2 队列的数组实现

队列可以从头部删除尾部插入元素,因此需要两个指针分别标记头部和尾部位置,分别定义为 frontrear

front 指向的是即将删除的头部元素

rear 指向的是即将插入元素的位置

最开始的时候,frontrear 都指向第一个空元素,如下所示:

图片选自优课达官网

每添加一个元素时,让 rear 指针向右移动一位。每删除一个元素时,让 front 指针向右移动一位

在添加 a, b, c, d, e 五个元素后,数组的存储如下图所示:

图片选自优课达

我们继续进行添加和删除操作,在某一个时刻,数组可能出现如下情况:

图片选自优课达

发现:随着 front 的右移,数组的左侧空间全部浪费了

那该如何解决这个问题呢?

第一个方案:数组调整

每删除一次后,将右侧的元素依次左移

但这是一个不靠谱的方案,这让每次删除操作的时间复杂度为 O(N)

第二个方案:循环数组

循环数组是指数组末尾下一个元素并不是越界,而是第一个元素

用图表示如下:

图片选自优课达官网

如果使用循环队列,可以完美的解决队列导致空间浪费问题

代码实现

第一步:加入基本对象和变量

public class OrangeArrayQueue<T> {
    
    // 前后两个指针
    private int front;
    private int rear;
    
    // 底层存储数组
    private T[] queue;
    
    public OrangeArrayQueue(int size) {
        this.front = 0;
        this.rear = 0;
        
        // 数组泛型的写法
    	this.queue = (T[]) new Object[size];
    }
}

OrangeArrayQueue 提供了一个创建队列指定大小的构造函数。需要注意数组泛型的写法

第二步:队尾添加元素

// 队尾添加元素
public void add(T o) {
    this.queue[this.rear] = o;
    // 因为是循环队列,所以处理数组长度取余
    int newRear = (rear + 1) % this.queue.length;
    // 指针相遇表示队列已满,暂不考虑扩容情况
    if (newRear == this.front) {
        // 如果加入元素以后指针碰撞,则抛出越界提示
        throw new IndexOutOfBoundsException("队列已满");
    }
    this.rear = newRear;
}

注意:

  • 因为是循环链表,所以用取余的方式获取指针位置
  • 使用头尾相碰作为队列已满条件
  • 利用Java异常机制,抛出队列已满异常

为了方便查看队列数据,在这里加入一个 toString 方法将队列数据连接成字符串返回

public String toString() {
    StringBuffer sb = new StringBuffer();
    int i = this.front;
    while (i != this.rear) {
        sb.append(this.queue[i]);
        sb.append(" ");
        i++;
    }
    return sb.toString();
}

第三步:获取队列长度

长度计算公式如下:

int size = this.rear - this.front;

循环数组有可能出现 rear < front,所以如果出现小于的情况,可以加上数组的长度再进行相减。代码如下:

// 获取队列的长度
public int size() {
    if (this.rear < this.front) {
        return this.rear + this.queue.length - this.front;
    }
    return this.rear - this.front;
}

队列的数组实现代码完整版如下:

public class OrangeArrayQueue<T> {

  	// 前后两个指针
  	private int front;
  	private int rear;

  	// 底层存储数组
 	private T[] queue;

  	public OrangeArrayQueue(int size) {
    	this.front = 0;
    	this.rear = 0;

    	// 特别注意此处的数组泛型的写法
    	this.queue = (T[]) new Object[size];
  	}

  	// 队列尾部添加元素
  	public void add(T o) {
    	this.queue[this.rear] = o;
    	// 因此是循环队列,所以处理数组长度取余
    	int newRear = (rear + 1) % this.queue.length;
    	// 暂不考虑扩容情况
    	if (newRear == this.front) {
      		// 如果加入元素以后指针碰撞,则抛出越界提示
      		throw new IndexOutOfBoundsException("队列已满");
    	}
    	this.rear = newRear;
    }

  	// 删除队列头部元素
  	public T remove() {
    	if (this.front == this.rear) {
      		throw new IndexOutOfBoundsException("队列为空,不允许remove");
    	}
    	T item = this.queue[this.front];
    	this.front = (this.front + 1) % this.queue.length;
    	return item;
  	}

  	// 获取队列中索引位置元素
  	public T get(int i) {
    	if (i < 0 || i >= this.size()) {
      		throw new IndexOutOfBoundsException("获取队列元素,越界");
    	}
    	int index = (i + this.front) % this.queue.length;
    	return this.queue[index];
  	}

  	// 获取队列的长度
  	public int size() {
    	if (this.rear < this.front) {
      		return this.rear + this.queue.length - this.front;
    	}
    	return this.rear - this.front;
  	}

  public String toString() {
    StringBuffer sb = new StringBuffer();
    int i = this.front;
    while (i != this.rear) {
      sb.append(this.queue[i]);
      sb.append(" ");
      i++;
    }
    return sb.toString();
  }


  	public static void main(String[] args) {
    	OrangeArrayQueue<Integer> queue = new OrangeArrayQueue<>(20);
    	queue.add(2);
    	queue.add(5);
    	queue.add(8);
    	System.out.println(queue.get(0));
    	System.out.println(queue.get(2));
    	System.out.println(queue.remove());
    	System.out.println(queue.remove());
    	System.out.println(queue.toString());
    	System.out.println(queue.size());
  	}
}

3.3 队列的链表实现

对于栈、队列这种类似的数据结构,用数组实现其实是一件非常繁琐的事情,主要因为两个原因:

  1. 数组天生对频繁的操作很不友好,每次插入删除操作都需要调整数组
  2. 数组连续空间存储特性,导致用数组实现的数据结构都存在越界或者扩容的问题

链表是栈、队列底层存储最好的选择

图片选自优课达官网

如图所示:

  1. 为了方便在头部删除节点,需要一个 front 指针指向链表的第一个节点
  2. 为了方便在尾部插入节点,需要一个 rear 指针指向链表的最后一个节点
  3. 链表只能遍历统计节点个数,会额外有时间的开销,所以增加 size 用于存储节点个数

接下来,add、remove、get、size的方法实现就比较容易了

add:类似于在列表尾部加入第一个节点

remove:类似于链表如何删除第一个节点

get:遍历链表返回对应索引的值

size:直接返回变量size即可

完整代码如下:

public class OrangeLinkedQueue<T> {

  	// 前后两个指针
  	private Node<T> front;
  	private Node<T> rear;
  	private int size = 0;

  	// 队列尾部添加元素
  	public void add(T o) {
    	Node<T> node = new Node<>(o);
    	if (this.front == null) {
      		this.front = node;
    	} else {
      		this.rear.setNext(node);
    	}
    	this.size++;
    	this.rear = node;
  	}

  	// 删除队列头部元素
  	public T remove() {
    	if (this.front == null) {
      		throw new IndexOutOfBoundsException("队列为空,不能删除");
    	}
    	Node<T> temp = this.front;
    	this.front = temp.getNext();
    	temp.setNext(null);
    	this.size--;
    	return temp.getContent();
  	}

  	// 获取队列中索引位置元素
  	public T get(int i) {
    	if (i < 0 || i >= this.size()) {
      		throw new IndexOutOfBoundsException("获取队列元素,越界");
    	}
    	Node<T> node = this.front;
    	while (i > 0){
      		node = node.getNext();
      		i--;
    	}
    	return node.getContent();
  	}

  	// 获取队列的长度
  	public int size() {
    	return this.size;
  	}

  	public String toString() {
    	StringBuffer sb = new StringBuffer();
    	Node<T> node = this.front;
    	while (node.getNext() != null) {
      		sb.append(node.getContent());
      		sb.append(" ");
      		node = node.getNext();
        }
        return sb.toString();
  	}


  	public static void main(String[] args) {
    	OrangeLinkedQueue<Integer> queue = new OrangeLinkedQueue<>();
    	queue.add(2);
    	queue.add(5);
    	queue.add(8);
    	System.out.println(queue.get(0));
    	System.out.println(queue.get(2));
    	System.out.println(queue.remove());
    	System.out.println(queue.remove());
    	System.out.println(queue.toString());
    	System.out.println(queue.size());
  	}
}
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值