1.队列的定义。
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队头:Rear 队尾:Front
入队操作 O(1) 出队操作O(n)
2.队列接口Queue的定义。
代码如下:
package com.oupeng.p3队列;
public interface Queue<E> {
/**
* 获取元素的总数
* @return 返回size
* */
public int getSize();
/**
* 判断队列是否为空
* @return 返回false 或者true
* */
public boolean isEmpty();
/**
* 进入队列一个元素
* */
public void enqueue();
/**
* 出队列一个元素
* */
public E dequeue();
/**
* 获取队头的元素
* @return 将该元素返回
* */
public E getFront();
/**
* 获取队尾的元素
* @return 将该元素返回
* */
public E getRear();
/**
* 清空队列
* */
public E clear();
}
Queue分析:
int getSize() 获取队列元素的总和
boolean isEmpey() 判断队列元素是否为空
void enqueue(E e) 进入队列一个元素
E dequeue() 出队列一个元素
E getFront() 获取队列头指针的一个元素
E getRear() 获取队列尾指针的一个元素
E clear() 清空队列的所有元素
3.队列的顺序存储结构ArrayQueue的定义。
代码如下:
package com.oupeng.p3队列;
import com.oupeng.p1线性表.ArrayList;
public class ArrayQueue<E> implements Queue<E> {
private ArrayList<E> list;
public ArrayQueue(){
list=new ArrayList<E>();
}
public ArrayQueue(int capacity){
list=new ArrayList<E>(capacity);
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void enqueue(E e) {
list.addLast(e);;
}
@Override
public E dequeue() {
return list.removeFirst();
}
@Override
public E getFront() {
return list.getFirst();
}
@Override
public E getRear() {
return list.getLast();
}
@Override
public void clear() {
list.clear();
}
public String toString(){
StringBuilder sb=new StringBuilder();
sb.append("ArrayQu: size="+getSize()+",capacity="+list.getCapacity()+"\n");
if(isEmpty()){
sb.append("[]");
}else{
sb.append('[');
for(int i=0;i<getSize();i++){
sb.append(list.get(i));
if(i==getSize()-1){
sb.append(']');
}else{
sb.append(',');
}
}
}
return sb.toString();
}
public boolean equals(Object obj){
if(obj==null){
return false;
}
if(obj==this.list){
return true;
}
if(obj instanceof ArrayQueue){
ArrayList<E> l=(ArrayList<E>) obj;
if(l.getSize()==getSize()){
for(int i=0;i<getSize();i++){
if(l.get(i)!=list.get(i)){
return false;
}
}
return true;
}
}
return false;
}
}
- ArrayQueue分析
int getSize() 获取队列元素的总和, 相当于返回线性表的list.getSize();
boolean isEmpey() 判断队列元素是否为空,相当于返回线性表的list.isEmpty();
void enqueue(E e) 进入队列一个元素,相当于在线性表的addLast()加入一个数
E dequeue() 出队列一个元素,相当于在线性表removeFirst()删除一个元素
E getFront() 获取队列头指针的一个元素,相当于返回线性表的list.getFirst();
E getRear() 获取队列尾指针的一个元素,相当于返回线性表的list.getLast();
E clear() 清空队列的所有元素,相当于清空线性表的list.clear();
String toString()
1.这里使用StringBuileder :方便线程不安全
不使用StringBuffer :不方便线程安全
2.先判断线性表是否为空
空打印 []
非空 sb.append('['); 起步
开始遍历线性表 i为最后一个元素时 sb.append(']');
i非最后一个元素时 sb.append(',');
最后返回sb.toString();
boolean equals() 先对传入的元素进行判断,看是否为空,空则返回false
再对该元素是否直接等于this,是返回true
最后再比较里边内容,当出现内容元素不等时返回一个false,比完相等则返回true
存在弊端:
1、 队列的顺序存储结构本身是由ArrayList实现的。
2、在数据元素入队的时候,相当于在ArrayList表尾添加元素,addLast();
3、在数据元素出队的时候,相当于在ArrayList表头 删除元素,removeFirst();
4、入队时间复杂度O(1),从下标最后一个入队。出队的时间复杂度O(n),因为下标为0进行出列,后边所有的元素往前移动。
5、线性表增删数据元素时间复杂符都是O(n)(平均计算的)。
6、队列的出队时间复杂度O(n),可不是按平均算的,因为每次出队都是O(n)。
4.对ArrayQqueue进行优化。
优化1、想方法减少出队的时间复杂度,可以这么思考。
元素出队后,不将队头Front下标后边的元素遍历往前移动,而是将队头Front的位置往后移动一位。
这样便解决了时间复杂度的问题,出队和入队都是O(1)。但是却产生了空间的浪费,当队尾Rear指针到达末点时无法在进行移 动了。
优化2、所以在这里便出现第二种思路优化。
当队尾Rear指针到达末端时,当再次进行元素入队操作,可以将队尾Rear指针位置移动到表头(也就是数组下标0处)。
所以在这里可以想到,优化1是一条线,优化2便成了一个圆环。
现在就需要判断什么时候队列已满?什么时候队列为空?
当队头Front 队尾Rear按正常逻辑对应位置存在元素时。队列满:(Rear+1)%n==Front 队列空: (Rear+1)%n==Front
这时候出现队满和队空的判断条件一样,这时候也就无法进行判断了。
优化3、对队满队空判断条件的优化。
队头Front指针,队尾Rear指针。让队尾Rear指针指向一个空元素位置,队头Front指针指向存元素位置。
这时候便出现,队满时:(Rear+1)%n==Front 队空时:Rear==Front
5.优化后的ArrayQueue的实现。
代码如下:
package com.oupeng.p3队列;
public class ArrayQueueLoop<E> implements Queue<E>{
private E[] data;
private int front;
private int rear;
private int size;
private static int DEFAULT_SIZE=10;
public ArrayQueueLoop() {
this(DEFAULT_SIZE);
}
public ArrayQueueLoop(int capacity){
data=(E[]) new Object[capacity+1];
front=0;
rear=0;
size=0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front==rear&&size==0;
}
@Override
public void clear() {
size=0;
front=0;
rear=0;
//与其缩容清空 不如重新创建数组
}
@Override
public void enqueue(E e) {
if((rear+1)%data.length==front){
//【扩容】
resize(data.length*2-1);
}
data[rear]=e;
rear=(rear+1)%data.length;
size++;
}
private void resize(int newLen) {
E[] newData=(E[]) new Object[newLen];
int index=0;//表示新数组角标
for(int i=front;i!=rear;i=(i+1)%data.length){
newData[index++]=data[i];
}
front=0;
rear=index;
data=newData;
}
@Override
public E dequeue() {
if(isEmpty()){
throw new NullPointerException("队列为空!");
}
E e=data[front];
front=(front+1)%data.length;
size--;
if(size<=data.length/4&&data.length>DEFAULT_SIZE){
resize(data.length/2+1);
}
return e;
}
@Override
public E getFront() {
return data[front];
}
@Override
public E getRear() {
return data[(data.length+rear-1)%data.length];
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("ArrayQueueLoop: size="+getSize()+",capacity="+(data.length-1)+"\n");
if(isEmpty()){
sb.append("[]");
}else{
sb.append('[');
for(int i=front;i!=rear;i=(i+1)%data.length){
sb.append(data[i]);
if((i+1)%data.length==rear){
sb.append(']');
}else{
sb.append(',');
}
}
}
return sb.toString();
}
}
这段优化主要需要注意队满和队空的判断,便容易理解。
ArrayLoop分析:
无参无返回值构造函数: 默认队列容量初始值
有参无返回值构造函数: 队列容量初始值为传入的值
定义头指针Front初始值为0,尾指针初始值为0,有效元素size初始值为0
int getSize() 获取队列元素的总和, 相当于返回size
boolean isEmpey() 判断队列元素是否为空,相当于判断front==rear&&size==0
void enqueue(E e) 进入队列一个元素,尾指针的圆环移动,如果元素满则进行判断扩容
E dequeue() 出队列一个元素,相当于在头指针的圆环移动,如果元素大量减少,进行判断缩容
E getFront() 获取队列头指针的一个元素,相当于返回队列头指针Front对应的元素;
E getRear() 获取队列尾指针的一个元素,相当于返回队列(尾指针Rear+队列长度-1)对队列长度取余;
E clear() 清空队列的所有元素,相当于相当于初始化size=0;
front=0;
rear=0;
String toString()
1.这里使用StringBuileder :方便线程不安全
不使用StringBuffer :不方便线程安全
2.先判断线性表是否为空
空打印 []
非空 sb.append('['); 起步
开始遍历线性表 i为最后一个元素时 sb.append(']');
i非最后一个元素时 sb.append(',');
最后返回sb.toString();
这里的toString,初始值未优化0对应优化front
未优化data.length对应rear