数据结构学习之——队列与循环队列java
队列(Queue)同栈(stack)同样也是一种运算收限的线性数据结构,参考:数据结构之——栈。栈即:LIFO(Last In First Out),队列则是FIFO(First In First Out),也就是说队列这种数据结构仅容许在一端(队尾)添加元素,在另外一端(队首)删除元素,因此说队列是一种先进先出的数据结构。能够将队列想象成银行柜台的排队机制同样,在前面排队的人能够先办理业务,在最后排队的人等到前面全部的人办理完毕后,才能够进行业务的处理,如图:
git
队列同ArrayStack的实现同样,都须要基于动态数组做为底层实现。
动态数组实现代码,动态数组实现原理
在设计模式上,同ArrayStack同样,设计的是Queue这样一个接口,并建立ArrayQueue这样一个类implements这个接口,Queue接口的方法与Stack栈的方法大致相同,只不过咱们将入栈push设计成enqueue(入队),出栈pop设计为dequeue(出队)。接口代码以下:github
public interface Queue<E>{
void enqueue(E e);
E dequeue();
E getFront();
int getSize();
boolean is Empty();
}
复制代码
ArrayQueue代码以下:算法
public class ArrayQueue<E> implements Queue<E>{
private Array<E> array;
public ArrayQueue(int capacity){
array = new Array<>(capacity);
}
public ArrayQueue(){
array = new Array<>();
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void enqueue(E e){
array.addLast(e);
}
@Override
public E dequeue(){
return array.removeFirst();
}
@Override
public E getFront(){
if(array.isEmpty)
throw new IllegalArgumentException("ArrayQueue is Empty");
return array.get(0)
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("Queue:\n");
sb.append("front:[");
for(int i=0;i<getSize();i++){
sb.append(array.get(i));
if(i!=getSize()-1){
sb.append(",");
}else{
sb.append("]tail");
}
}
return sb.toString();
}
}
复制代码
时间复杂度分析以下:设计模式
E getFront();
int getSize();
boolean is Empty();
复制代码
以上方法,时间复杂度为:O(1)。数组
void enqueue(E e);
复制代码
由于入队操做至关于在动态数组的尾部添加元素,虽然有resize()这样一个O(n)级别的算法,可是以均摊时间复杂度分析,enqueue操做仍然是一个O(1)级别的算法。bash
E dequeue();
复制代码
dequeue()操做至关于动态数组的removeFirst()操做,在数组的头部删除一个元素,array[0] 后面的全部元素都须要向前挪动一个位置,因此dequeue出队是一个O(n)级别的算法。
综上分析,ArrayQueue仍是有些不完美的地方,ArrayStack全部的操做均为O(1)级别的算法,可是基于动态数组实现的队列ArrayQueue 在出队操做dequeue上性能仍是略显不足,LoopQueue(循环队列)就优化了ArrayQueue出队这样一个问题。数据结构
咱们已经知道了,ArrayQueue美中不足的地方就在于dequeue这样一个操做是O(n)级别的算法。出现这个问题的缘由其实是由于每次进行出队操做时,动态数组都须要将array[0]后面全部的元素向前挪动一个单位,但实际上想想这个过程并不“划算”,由于,队列元素的数量达到万以上的级别时,仅仅删除一个元素,咱们就须要将全部的元素进行一次大换血。和银行柜台业务办理的排队不一样,银行柜台的一号办理人办理完毕,后面全部的人只须要上前一小步就能够了,可是对于计算机来说,每个数据的调整都须要计算机亲历躬行。有什么办法能够避免这种大规模的动辄呢?咱们可使用两个变量去维护队列的队首和队尾。
app
public class MyQueue<E> implements Queue<E> {
private int front;
private int tail;
private E[]data;
public MyQueue(int capacity){
data = (E[])new Object[capacity+1];
}
public MyQueue(){
this(10);
}
@Override
public int getSize(){
return tail-front;
}
@Override
public boolean isEmpty(){
return front==tail;
}
@Override
public E getFront(){
return data[front];
}
private void resize(int newCapacity){
E[]newData = (E[])new Object[newCapacity+1];
for(int i=0;i<(tail-front);i++){
newData[i] = data[front+i];
}
data = newData;
tail = tail - front;
front = 0;
}
@Override
public void enqueue(E e){
if(tail == data.length-1)
resize((tail-front)*2);
data[tail] = e;
tail++;
}
@Override
public E dequeue(){
E ret = data[front];
// Loitering Object
data[front] = null;
front++;
if(((tail-front)==(data.length-1)/4) && (data.length-1)/2!=0)
resize((data.length-1)/2);
return ret;
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("MyQueue size=%d,capacity=%d\n",tail-front,data.length-1));
stringBuilder.append("front:[");
for(int i=front;i<tail;i++){
stringBuilder.append(data[i]);
if((i+1)!=tail){
stringBuilder.append(",");
}
}
stringBuilder.append("]tail");
return stringBuilder.toString();
}
}
复制代码
MyQueue对ArrayQueue进行了优化操做,本来dequeue须要O(n)的时间复杂度,进行优化后,dequeue由O(n)的时间复杂度变为了均摊O(1)的时间复杂度。这里面须要注意的是:MyQueue的enqueue入队操做规定了tail == data.length-1时进行“扩容”操做,这里面扩容二字我使用了双引号,由于有可能这个“扩容”其实是缩容。
dom
if(tail == data.length-1)
resize((tail-front)*2);
复制代码
在上图的示例中,若是reisze,咱们实际上就至关于进行了缩容的操做。咱们这样的设计看起来解决了问题,但仍然不灵活,咱们但愿在入队时的resize只涉及到扩容,出队时的resize只涉及缩容,咱们是否能对这样的需求进行优化呢?
循环队列的思想和ArrayQueue优化后的MyQueue大致相同,只不过循环队列里面加入了更加巧妙的循环机制。
public class LoopQueue<E> implements Queue<E> {
private E[]data;
private int front;
private int tail;
public LoopQueue(int capacity){
data = (E[])new Object[capacity+1];
}
public LoopQueue(){
this(10);
}
@Override
public int getSize(){
if(tail<front)
return data.length-(front-tail);
else{
return tail-front;
}
}
public int getCapacity(){
return data.length-1;
}
@Override
public boolean isEmpty(){
return getSize()==0;
}
private void resize(int newCapacity){
E[]newData = (E[])new Object[newCapacity+1];
for(int i=0;i<getSize();i++){
newData[i] = data[(i+front)%data.length];
}
data = newData;
tail = getSize();
front = 0;
}
@Override
public void enqueue(E e){
if(front==(tail+1)%data.length)
resize(2*getSize());
data[tail] = e;
tail = (tail+1)%data.length;
}
@Override
public E dequeue(){
E ret = data[front];
// Loitering Object
data[front] = null;
front = (front+1)%data.length;
if(getSize() == getCapacity()/4 && getCapacity()/2!=0){
resize(getCapacity()/2);
}
return ret;
}
@Override
public E getFront(){
if(getSize()==0)
throw new IllegalArgumentException("LoopQueue is empty");
return data[front];
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("LoopQueue size:%d,capacity:%d\n",getSize(),getCapacity()));
stringBuilder.append("front:[");
for(int i=front;i!=tail;i=(i+1)%data.length){
stringBuilder.append(data[i]);
if((i+1)%data.length!=tail)
stringBuilder.append(",");
}
stringBuilder.append("]tail");
return stringBuilder.toString();
}
}
复制代码
如今咱们对ArrayQueue,LoopQueue进行性能上的测试,
import java.util.Random;
public class Main {
private static double testQueue(Queue<Integer>q,int testCount){
long startTime = System.nanoTime();
Random random = new Random();
for(int i=0;i<testCount;i++)
q.enqueue(random.nextInt(Integer.MAX_VALUE));
for(int i=0;i<testCount;i++)
q.dequeue();
long endTime = System.nanoTime();
return (endTime-startTime)/1000000000.0;
}
public static void main(String[]args){
int testCount = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double loopQueueTime = testQueue(loopQueue,testCount);
double arrayQueueTime = testQueue(arrayQueue,testCount);
System.out.println("LoopQueue:"+loopQueueTime);
System.out.println("ArrayQueue"+arrayQueueTime);
}
}
复制代码
在jdk1.8的环境下测试结果为: