java使用数组实现队列6,6、【数据结构】队列

1、概述

队列(Queue),线性数据结构。

队列这一数据结构来自于现实,现实中的“排队”抽象出来就是队列。

向队列中添加元素称为“入队”,从队列中移出元素称为“出队”。

队列的特点是“一端进,另一端出”并且“先进先出”。“入队”的一端称为“队尾”,“出队”的一端称为“队首”。

96271f82ed98

队列-动画演示

2、基于 Java 编写一个队列的接口

与栈一样,队列也属于一种规则性的数据结构,同样用接口编写队列的规则。

public interface Queue {

/**

* 入队

*

* @param e 入队的元素

*/

void enqueue(E e);

/**

* 出队

*

* @return 返回出队的元素

*/

E dequeue();

/**

* 获取队首元素

*

* @return 队首元素

*/

E getHead();

/**

* 是否是空队列

*

* @return 返回是否是空队列

*/

boolean isEmpty();

/**

* 获取队列中元素个数

*

* @return 返回队列中元素个数

*/

int getSize();

}

3、基于 Java 动态数组实现队列

import java.util.Objects;

/**

* 自定义动态数组

*/

public class Array {

/**

* 封装的原生数组

*/

private E[] data;

/**

* 数组中元素个数

*/

private int size;

/**

* 数组开辟的空间大小

*/

private int capacity;

/**

* 默认开辟空间大小

*/

private static final int DEFAULT_CAPACITY = 10;

/**

* 默认扩容倍数

*/

private static final int DEFAULT_INCREASE_CAPACITY_TIMES = 2;

/**

* 默认缩容倍数

*/

private static final int DEFAULT_DECREASE_CAPACITY_TIMES = 2;

/**

* 缩容时机

*

* 当元素个数仅仅是开辟空间大小的 1/4 时

*/

private static final int DEFAULT_DECREASE_CAPACITY_WHEN = 4;

/**

* 在数组中未发现元素时的返回值

*/

private static final int NOT_FIND_INDEX_FLAG = -1;

public Array() {

data = (E[]) new Object[DEFAULT_CAPACITY];

capacity = DEFAULT_CAPACITY;

}

public Array(int capacity) {

if (capacity > 0) {

data = (E[]) new Object[capacity];

} else {

throw new IllegalArgumentException("Capacity Error!");

}

}

/**

* 在指定索引处增加元素

*

* @param e e

* @param index index

*/

public void addElement(E e, int index) {

if (index < 0 || index > size) {

throw new IllegalArgumentException("Index Error!");

}

if (size == capacity) {

reCapacity(capacity * DEFAULT_INCREASE_CAPACITY_TIMES);

}

for (int i = size - 1; i >= index; i--) {

data[i + 1] = data[i];

}

data[index] = e;

size++;

}

/**

* 在数组首部增加元素

*

* @param e e

*/

public void addElementAtFirst(E e) {

addElement(e, 0);

}

/**

* 在数组尾部增加元素

*

* @param e e

*/

public void addElementAtLast(E e) {

addElement(e, size);

}

/**

* 删除指定索引的元素

*

* @param index index

* @return e

*/

public E removeElement(int index) {

if (index < 0 || index >= size) {

throw new IllegalArgumentException("Index Error!");

}

E oldElem = data[index];

for (int i = index; i + 1 < size; i++) {

data[i] = data[i + 1];

}

data[size - 1] = null;

size--;

if (size == capacity / DEFAULT_DECREASE_CAPACITY_WHEN) {

reCapacity(capacity / DEFAULT_DECREASE_CAPACITY_TIMES);

}

return oldElem;

}

public E removeElementAtFirst() {

return removeElement(0);

}

public E removeElementAtLast() {

return removeElement(size - 1);

}

/**

* 删除特定的元素,从数组首部开始检索

*

* @param e e

*/

public void removeSpecificElementFromFirst(E e) {

int index = findSpecificElementFromFirst(e);

if (index != NOT_FIND_INDEX_FLAG) {

removeElement(index);

}

}

/**

* 删除特定的元素,从数组尾部开始检索

*

* @param e e

*/

public void removeSpecificElementFromLast(E e) {

int index = findSpecificElementFromLast(e);

if (index != NOT_FIND_INDEX_FLAG) {

removeElement(index);

}

}

/**

* 删除特定的所有元素

*

* @param e e

*/

public void removeAllSpecificElements(E e) {

for (int i = 0; i < size; i++) {

if (Objects.equals(e, data[i])) {

removeElement(i);

}

}

}

public E get(int index) {

if (index < 0 || index >= size) {

throw new IllegalArgumentException("Index Error!");

}

return data[index];

}

public void set(E e, int index) {

if (index < 0 || index >= size) {

throw new IllegalArgumentException("Index Error!");

}

data[index] = e;

}

public int findSpecificElementFromFirst(E e) {

for (int i = 0; i < size; i++) {

if (Objects.equals(e, data[i])) {

return i;

}

}

return NOT_FIND_INDEX_FLAG;

}

public int findSpecificElementFromLast(E e) {

for (int i = size - 1; i >= 0; i--) {

if (Objects.equals(e, data[i])) {

return i;

}

}

return NOT_FIND_INDEX_FLAG;

}

public E modifyElement(E e, int index) {

if (index < 0 || index >= size) {

throw new IllegalArgumentException("Index Error!");

}

E oldElem = data[index];

data[index] = e;

return oldElem;

}

public boolean isEmpty() {

return size == 0;

}

public boolean contains(E e) {

for (int i = 0; i < size; i++) {

if (Objects.equals(e, data[i])) {

return true;

}

}

return false;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder();

sb.append("[");

for (int i = 0; i < size; i++) {

sb.append(data[i]);

if (i != size - 1) {

sb.append(", ");

}

}

sb.append("]");

return sb.toString();

}

private void reCapacity(int newCapacity) {

E[] arr = (E[]) new Object[newCapacity];

for (int i = 0; i < size; i++) {

arr[i] = data[i];

}

capacity = newCapacity;

data = arr;

}

public int getCapacity() {

return capacity;

}

public int getSize() {

return size;

}

}

/**

* 基于 动态数组 实现 队列

*

* 索引小的作为队首(左侧),索引大的作为队尾(右侧)

*

* @param

*/

public class ArrayQueue implements Queue {

private Array arr;

public ArrayQueue() {

arr = new Array<>();

}

public ArrayQueue(int capacity) {

arr = new Array<>(capacity);

}

@Override

public void enqueue(E e) {

arr.addElementAtLast(e);

}

@Override

public E dequeue() {

return arr.removeElementAtFirst();

}

@Override

public E getHead() {

return arr.get(0);

}

@Override

public boolean isEmpty() {

return arr.isEmpty();

}

@Override

public int getSize() {

return arr.getSize();

}

public int getCapacity() {

return arr.getCapacity();

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder("head [");

for (int i = 0; i < arr.getSize(); i++) {

sb.append(arr.get(i));

if (i != arr.getSize() - 1) {

sb.append(", ");

}

}

sb.append("] tail");

return sb.toString();

}

}

public class Main {

public static void main(String[] args) {

Queue queue = new ArrayQueue<>();

queue.enqueue(1);

queue.enqueue(3);

queue.enqueue(5);

queue.enqueue(7);

queue.enqueue(9);

System.out.println(queue);

queue.dequeue();

System.out.println(queue);

queue.dequeue();

System.out.println(queue);

queue.dequeue();

System.out.println(queue);

}

}

head [1, 3, 5, 7, 9] tail

head [3, 5, 7, 9] tail

head [5, 7, 9] tail

head [7, 9] tail

上述实现有一个小的问题,那就是在出队的时间复杂度上,入队的时间复杂度是

math?formula=O(1),但是出队的时间复杂度是

math?formula=O(n),原因是涉及到出队操作完成后,后续元素向前移动。所以,就有没有什么方案来将出队的时间复杂度也变为

math?formula=O(1),于是,便有了“循环队列”。

4、循环队列

循环队列的目的,就是为了优化“出队”这一操作的时间效率,“出队”操作后不需要元素移动,使之时间复杂度变为

math?formula=O(1)

循环队列的底层是数组。循环队列是对基于数组实现的队列的优化(基于链表实现的队列没有“循环队列”这一说法)。

循环队列的思想是,改变固有的思维,“数组是直的”变为“数组是一个首尾相接的圆环”。通过维护一些变量来记录“队首”、“队尾”,也就是说,“索引0一定是队首”的情况不再存在。

96271f82ed98

循环队列-模型

4.1、在不考虑扩容的情况下基于数组实现循环队列

动画演示:

96271f82ed98

循环队列-动画演示

常规循环队列中的一些约定:

维护着队首索引的变量为headIndex,维护着队尾索引的变量为tailIndex(注意:tailIndex是下一个入队元素的索引,headIndex是当前队首元素的索引)

1、为了使“队列为空”与“队列为满”这两种情况进行区分,真实数组的长度(arr.length)要比队列容量(capacity)多一个空间,否则的话,两种情况的判断标准就是一样的了。(如果使用变量对队列中元素个数进行维护的话,这个“多一个空间”不是必须的!!!)

2、队列为空:headIndex == tailIndex(两个变量不一定等于0)

3、队列为满:headIndex == (tailIndex + 1) % arr.length

4、入队操作后,tailIndex的维护:tailIndex = (tailIndex + 1) % arr.length

5、出队操作后,headIndex的维护:headIndex = (headIndex + 1) % arr.length

代码一:通过维护一个size变量来记录队列中的元素个数。

public interface Queue {

/**

* 入队

*

* @param e 入队的元素

*/

void enqueue(E e);

/**

* 出队

*

* @return 返回出队的元素

*/

E dequeue();

/**

* 获取队首元素

*

* @return 队首元素

*/

E getHead();

/**

* 是否是空队列

*

* @return 返回是否是空队列

*/

boolean isEmpty();

/**

* 获取队列中元素个数

*

* @return 返回队列中元素个数

*/

int getSize();

}

/**

* 循环队列

*/

public class LoopQueue implements Queue {

/**

* 数组

*/

private E[] arr;

/**

* 队列的最大容量

*/

private int capacity;

/**

* 队列中当前元素数目

*

* size 可以通过其他的方式计算得到

*/

private int size;

/**

* headIndex 队首索引

* tailIndex 队尾索引

*/

private int headIndex, tailIndex;

/**

* 默认容量

*/

private static final int DEFAULT_CAPACITY = 10;

public LoopQueue() {

// 根据循环队列的约定,数组的长度比队列最大容量要多1

arr = (E[]) new Object[DEFAULT_CAPACITY + 1];

capacity = DEFAULT_CAPACITY;

size = 0;

headIndex = 0;

tailIndex = 0;

}

public LoopQueue(int capacity) {

if (capacity < 0) {

throw new IllegalArgumentException("Capacity Error!");

}

arr = (E[]) new Object[capacity + 1];

this.capacity = capacity;

size = 0;

headIndex = 0;

tailIndex = 0;

}

@Override

public void enqueue(E e) {

if (size == capacity) {

throw new IllegalArgumentException("Queue is Full!");

}

arr[tailIndex] = e;

size++;

tailIndex = (tailIndex + 1) % arr.length;

}

@Override

public E dequeue() {

if (size == 0) {

throw new IllegalArgumentException("Queue is Empty!");

}

E e = arr[headIndex];

arr[headIndex] = null;

size--;

headIndex = (headIndex + 1) % arr.length;

return e;

}

@Override

public E getHead() {

return arr[headIndex];

}

@Override

public boolean isEmpty() {

return size == 0;

}

@Override

public int getSize() {

return size;

}

public int getCapacity() {

return capacity;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder("head [");

for (int i = headIndex; i != tailIndex; i = (i + 1) % arr.length) {

sb.append(arr[i]);

if ((i + 1) % arr.length != tailIndex) {

sb.append(", ");

}

}

sb.append("] tail");

return sb.toString();

}

}

代码二:不使用size变量来记录队列中的元素个数。

如果不维护size变量,那么相对来说稍微有一点复杂的是在getSize这个方法上,需要找到size与headIndex、tailIndex之间的联系。

如果

math?formula=tailIndex%20%5Cgeq%20headIndex,说明当前队列还没有开始“循环”,这样的话,

math?formula=size%3DtailIndex-headIndex

如果

math?formula=tailIndex%20%3C%20headIndex,说明当前队列已经开始“循环”,这样的话,

math?formula=size%3DtailIndex-headIndex%2Barr.length

public interface Queue {

/**

* 入队

*

* @param e 入队的元素

*/

void enqueue(E e);

/**

* 出队

*

* @return 返回出队的元素

*/

E dequeue();

/**

* 获取队首元素

*

* @return 队首元素

*/

E getHead();

/**

* 是否是空队列

*

* @return 返回是否是空队列

*/

boolean isEmpty();

/**

* 获取队列中元素个数

*

* @return 返回队列中元素个数

*/

int getSize();

}

/**

* 循环队列

*/

public class LoopQueue implements Queue {

/**

* 数组

*/

private E[] arr;

/**

* 队列的最大容量

*/

private int capacity;

/**

* headIndex 队首索引

* tailIndex 队尾索引

*/

private int headIndex, tailIndex;

/**

* 默认容量

*/

private static final int DEFAULT_CAPACITY = 10;

public LoopQueue() {

// 根据循环队列的约定,数组的长度比队列最大容量要多1

arr = (E[]) new Object[DEFAULT_CAPACITY + 1];

capacity = DEFAULT_CAPACITY;

headIndex = 0;

tailIndex = 0;

}

public LoopQueue(int capacity) {

if (capacity < 0) {

throw new IllegalArgumentException("Capacity Error!");

}

arr = (E[]) new Object[capacity + 1];

this.capacity = capacity;

headIndex = 0;

tailIndex = 0;

}

@Override

public void enqueue(E e) {

if (headIndex == (tailIndex + 1) % arr.length) {

throw new IllegalArgumentException("Queue is Full!");

}

arr[tailIndex] = e;

tailIndex = (tailIndex + 1) % arr.length;

}

@Override

public E dequeue() {

if (tailIndex == headIndex) {

throw new IllegalArgumentException("Queue is Empty!");

}

E e = arr[headIndex];

arr[headIndex] = null;

headIndex = (headIndex + 1) % arr.length;

return e;

}

@Override

public E getHead() {

return arr[headIndex];

}

@Override

public boolean isEmpty() {

return headIndex == tailIndex;

}

@Override

public int getSize() {

return tailIndex >= headIndex ? (tailIndex - headIndex) : (tailIndex - headIndex + arr.length);

}

public int getCapacity() {

return capacity;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder("head [");

for (int i = headIndex; i != tailIndex; i = (i + 1) % arr.length) {

sb.append(arr[i]);

if ((i + 1) % arr.length != tailIndex) {

sb.append(", ");

}

}

sb.append("] tail");

return sb.toString();

}

}

代码三:如果使用size变量记录队列中有多少元素,实际上完全可以不用在创建数组时比队列最大容量多出一个空间。

稍微有点复杂的是在toString方法上,也就是遍历上要注意一下:

96271f82ed98

循环队列-遍历细节

public interface Queue {

/**

* 入队

*

* @param e 入队的元素

*/

void enqueue(E e);

/**

* 出队

*

* @return 返回出队的元素

*/

E dequeue();

/**

* 获取队首元素

*

* @return 队首元素

*/

E getHead();

/**

* 是否是空队列

*

* @return 返回是否是空队列

*/

boolean isEmpty();

/**

* 获取队列中元素个数

*

* @return 返回队列中元素个数

*/

int getSize();

}

/**

* 循环队列

*/

public class LoopQueue implements Queue {

/**

* 数组

*/

private E[] arr;

/**

* 队列的最大容量

*/

private int capacity;

/**

* 队列中当前元素数目

*/

private int size;

/**

* headIndex 队首索引

* tailIndex 队尾索引

*/

private int headIndex, tailIndex;

/**

* 默认容量

*/

private static final int DEFAULT_CAPACITY = 10;

public LoopQueue() {

this(DEFAULT_CAPACITY);

}

public LoopQueue(int capacity) {

if (capacity < 0) {

throw new IllegalArgumentException("Capacity Error!");

}

arr = (E[]) new Object[capacity];

this.capacity = capacity;

size = 0;

headIndex = 0;

tailIndex = 0;

}

@Override

public void enqueue(E e) {

if (size == capacity) {

throw new IllegalArgumentException("Queue is Full!");

}

arr[tailIndex] = e;

size++;

tailIndex = (tailIndex + 1) % capacity;

}

@Override

public E dequeue() {

if (size == 0) {

throw new IllegalArgumentException("Queue is Empty!");

}

E e = arr[headIndex];

arr[headIndex] = null;

size--;

headIndex = (headIndex + 1) % capacity;

return e;

}

@Override

public E getHead() {

return arr[headIndex];

}

@Override

public boolean isEmpty() {

return size == 0;

}

@Override

public int getSize() {

return size;

}

public int getCapacity() {

return capacity;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder("head [");

// 遍历逻辑有变化

for (int i = 0; i < size; i++) {

sb.append(arr[(headIndex + i) % capacity]);

if ((i + 1) != size) {

sb.append(", ");

}

}

sb.append("] tail");

return sb.toString();

}

}

如果使用size变量,“数组的长度比队列最大容量多1个空间”不是必须的;但如果不使用size变量,“数组的长度比队列最大容量多1个空间”是必要的。对于下面将论述的含“扩容”情形,这一点同样也适用。

4.2、在考虑扩容的情况下基于数组实现循环队列

当超过队列最大容量,自动将容量进行扩充;同样当队列中元素过少时,可以进行缩容。

代码:不使用size,多出一个空间

public interface Queue {

/**

* 入队

*

* @param e 入队的元素

*/

void enqueue(E e);

/**

* 出队

*

* @return 返回出队的元素

*/

E dequeue();

/**

* 获取队首元素

*

* @return 队首元素

*/

E getHead();

/**

* 是否是空队列

*

* @return 返回是否是空队列

*/

boolean isEmpty();

/**

* 获取队列中元素个数

*

* @return 返回队列中元素个数

*/

int getSize();

}

/**

* 循环队列

*/

public class LoopQueue implements Queue {

/**

* 数组

*/

private E[] arr;

/**

* 队列的最大容量

*/

private int capacity;

/**

* headIndex 队首索引

* tailIndex 队尾索引

*/

private int headIndex, tailIndex;

/**

* 默认容量

*/

private static final int DEFAULT_CAPACITY = 10;

public LoopQueue() {

// 根据循环队列的约定,数组的长度比队列最大容量要多1

arr = (E[]) new Object[DEFAULT_CAPACITY + 1];

capacity = DEFAULT_CAPACITY;

headIndex = 0;

tailIndex = 0;

}

public LoopQueue(int capacity) {

if (capacity < 0) {

throw new IllegalArgumentException("Capacity Error!");

}

arr = (E[]) new Object[capacity + 1];

this.capacity = capacity;

headIndex = 0;

tailIndex = 0;

}

@Override

public void enqueue(E e) {

// 扩容

if (headIndex == (tailIndex + 1) % arr.length) {

reCapacity(capacity * 2);

}

arr[tailIndex] = e;

tailIndex = (tailIndex + 1) % arr.length;

}

@Override

public E dequeue() {

if (tailIndex == headIndex) {

throw new IllegalArgumentException("Queue is Empty!");

}

E e = arr[headIndex];

arr[headIndex] = null;

headIndex = (headIndex + 1) % arr.length;

// 缩容

if (getSize() <= capacity / 4 && capacity / 2 != 0) {

reCapacity(capacity / 2);

}

return e;

}

@Override

public E getHead() {

return arr[headIndex];

}

@Override

public boolean isEmpty() {

return headIndex == tailIndex;

}

@Override

public int getSize() {

return tailIndex >= headIndex ? (tailIndex - headIndex) : (tailIndex - headIndex + arr.length);

}

public int getCapacity() {

return capacity;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder("head [");

for (int i = headIndex; i != tailIndex; i = (i + 1) % arr.length) {

sb.append(arr[i]);

if ((i + 1) % arr.length != tailIndex) {

sb.append(", ");

}

}

sb.append("] tail");

return sb.toString();

}

// 这是一种写法

private void reCapacity(int newCapacity) {

E[] newArr = (E[]) new Object[newCapacity + 1];

int j = 0;

for (int i = headIndex; i != tailIndex; i = (i + 1) % arr.length) {

newArr[j] = arr[i];

j++;

}

headIndex = 0;

tailIndex = j;

capacity = newCapacity;

arr = newArr;

}

}

reCapacity方法除了上面的写法,还可以这样写:

private void reCapacity2(int newCapacity) {

E[] newArr = (E[]) new Object[newCapacity + 1];

for (int i = 0; i < getSize(); i++) {

newArr[i] = arr[(headIndex + i) % arr.length];

}

// 由于没有 size 维护队列中元素个数,所以关键变量维护的时候要注意先后顺序

// 先维护 tailIndex 再维护 headIndex

tailIndex = getSize();

headIndex = 0;

capacity = newCapacity;

arr = newArr;

}

private void reCapacity(int newCapacity) {

E[] newArr = (E[]) new Object[newCapacity + 1];

// 如果怕变量维护出现问题,也可以先保存 size

int size = getSize();

for (int i = 0; i < getSize(); i++) {

newArr[i] = arr[(headIndex + i) % arr.length];

}

headIndex = 0;

tailIndex = size;

capacity = newCapacity;

arr = newArr;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值