引言
本文中对队列进行了基本介绍,通过对队列的基本原理进行一些说明,加之代码实现,以此记录。
队列的基本介绍
队列在存储结构上是一种线性结构,具有以下两个特点:
1,具备先进先出(FIFO,first int first out)原则
2,操作队列中的元素,只能在队首和队尾两端操作,入队:队尾添加,出队:队首删除
进队操作演示:
图1-1
入队之前队列中的元素:5 3 8
将7 4 6依次从队尾入队之后
队列中元素:5 3 8 7 4 6
出队操作演示:
图1-2
我们在前面将7 4 6入队之后,队列现在的情况为: 5 3 8 7 4 6
出队时,操作队首元素,将队首元素”删除"
出队后,队列情况为: 3 8 7 4 6
通过以上可以发现队列的特性,即先进先出原则,入队操作是将元素添加至队尾,进行”排队”,出队时,是将队首元素”删除”,由于队列的插入和删除操作分别在队尾和队首进行,每个元素必然按照进入的次序离队,也就是说先进队的元素必定先离队,所以队列也成为先进先出表。队列结构与日常生活中排队等候服务的模型是一致的,最早进入队列的人,最早得到服务并从队首离开,最后到来的人只能排在队列的最后,最后得到服务并最后离开。本文中采用数组实现顺序队列和循环队列(逻辑上的循环,原理后文会给出),因为顺序队列比较简单,下面直接给出代码实现:
1 import java.lang.reflect.Array; 2 3 /** 4 * @author x1c 5 * 6 * @param <T> 7 */ 8 public class ArrayQueue<T> { 9 10 private T[] arr; //模拟队列 11 private int count; //队列计数器 12 13 14 //构造初始化队列 15 @SuppressWarnings("unchecked") 16 public ArrayQueue(Class<T> type,int count){ 17 arr = (T[]) Array.newInstance(type, count); 18 count = 0; 19 } 20 21 //进队列 22 public void enQueue(T val){ 23 arr[count++] = val; 24 } 25 26 //返回队列开头元素 不删除元素 27 public T front(){ 28 return arr[0]; 29 } 30 31 //获取队首元素,将队首元素逻辑删除,将剩余元素向前移动,返回队首元素 32 public T deQueue(){ 33 T ret = arr[0]; 34 count--; 35 for(int i = 1;i <= count;i++){ 36 arr[i-1] = arr[i]; 37 } 38 return ret; 39 } 40 41 //返回当前队列大小 42 public int size(){ 43 return count; 44 } 45 //队列判空 46 public boolean isEmpty(){ 47 return size() == 0; 48 } 49 50 //打印队列 51 public void showQueue(){ 52 for(int i = 0;i < count;i++){ 53 System.out.print("arr["+i+"]="+arr[i]+","); 54 } 55 } 56 }
通过以上对队列的简单分析,对队列有一个基本的认识。在队列的顺序存储实现中,我们可以将队列当做一般的表用数组加以实现,但这样做的效果并不好,虽然通过队尾插入的方式入队,使得入队的运算可以在O(1)时间内完成,但是在执行出队操作时,为了删除队首元素,必须将数组中其他所有元素都向前移动一个位置,这样,当队列中有n个元素时,执行出队操作就需要O(n)时间。
为了提高运算效率,我们用另外一种方法来表达数组中个单元的位置关系,可以想象将数组A[0…maxSize-1]中的单元并不是排成一行,而是围城一个”圆环”,即A[0]接在A[maxSize-1]的后边,这种意义下的数组我们成为循环数组,如下图所示:
图2-1
用循环数组实现的队列成为循环队列,我们将循环队列中从队首到队尾的元素按逆时针方向存放在循环数组中一段连续的单元中,并且直接用队首指针front指向队首元素所在的单元,用队尾指针rear指向队尾元素所在单元的”后一个单元”,如下图所示,队首元素存储在数组下标为0的位置,front=0;队尾元素存储在数组下标为2的位置,rear=3.
当需要将新元素入队时,可在队尾指针指示的单元中存入新元素,并将队尾指针rear按逆时针方向移动一位。出队的操作也很简单,只要将队首指针front按逆时针方向移动一位即可。容易看出,用循环数组实现的队列可以在O(1)时间内完成入队和出队操作,执行一系列入队和出队操作,将是整个队列在循环数组中按逆时针方向移动。
当然队首和队尾指针也可以有不同的指向,例如也可以用队首指针front指向队首元素所在单元的”前一个单元”,用队尾指针rear指向队尾元素所在单元的方法来表示队列在循环数组中的位置。不论使用哪一种方法指示队首与队尾元素,我们都要解决一个细节问题,即如何表示满队列和空队列。
下面以图2-1所示的表示方法来说明问题,在图2-1中用队首指针front指向队首元素所在的单元,用队尾指针rear指向队尾元素所在单元的后一个单元。如此在图2-2(b)中,队首元素为e0,队尾元素为e3。当e4、e5、e6、e7相继进入队列后,如图2-2(c)所示,队列空间被占满,此时队尾指针追上队首指针,有rear=front。反之,如果从图2-2(b)所示的状态开始,e0、e1、e2、e3相继出队,则得到空队列,如图2-2(a)所示,此时队首指针追上队尾指针,所以也有front=rear。可见仅凭front与rear是否相等无法判断队列的状态是”空”还是”满”,解决这个问题可以少使用一个存储空间,当队尾指针的下一个单元就是队首指针所指单元时,则停止入队。这样队尾指针就不会追上队首指针,所以在队列满时就不会有front=rear。 这样一来,队满的条件就变为(rear+1)%maxSize=front,而队列判空的条件不变,仍然为front=rear。
图2-2
循环队列就是通过模运算,实现数组索引的一个”逻辑”循环功能,下表给出了计算公式。
队首元素 | arr[front] |
队尾元素 | arr[(rear-1)%maxSize] |
队空 | rear==front |
队满 | (rear+1)%maxSize==front |
队列中有效元素个数 | (rear+maxSize-front)%maxSize |
队列中元素位置 | i%maxSize |
根据以上的计算公式,程序就很清晰了,如下:
1 class CircleArray<T>{ 2 private int maxSize; //队列数组最大空间,真正可用空间应为maxSize-1 3 private int front; //前指针 4 private int rear; //后指针 5 private T[] arr; //模拟数组 6 7 @SuppressWarnings("unchecked") 8 public CircleArray(Class<T> type,int arrMaxSize){ 9 maxSize = arrMaxSize; 10 arr = (T[]) Array.newInstance(type, arrMaxSize); //实例化数组 11 front = rear = 0; 12 } 13 14 public boolean isFull(){ 15 return (rear+1) % maxSize == front; 16 //return (((rear = rear+1) & (maxSize-1))==front); jdk方式 17 } 18 19 //判空 20 public boolean isEmpty(){ 21 return rear == front; 22 } 23 24 //入队 25 public void enQueue(T n){ 26 if(isFull()){ 27 System.out.println("Empty is full"); 28 return; 29 } 30 arr[rear] = n; 31 rear = (rear+1) % maxSize; 32 } 33 34 //出队 35 public T deQueue(){ 36 if(isEmpty()){ 37 throw new RuntimeException("Exception:Empty Queue!!!"); 38 } 39 T ret = arr[front]; //从队首弹出 40 front = (front+1) % maxSize; //调整指针到下一个位置,在数组内循环的方式调整 41 return ret; 42 } 43 44 //打印队列 45 public void showQueue(){ 46 if(isEmpty()){ 47 System.out.println("Empty Queue"); 48 return; 49 } 50 for(int i = front;i < front+size();i++){ 51 System.out.print("arr["+i%maxSize+"]="+arr[i%maxSize]+","); 52 } 53 } 54 55 //获取当前队列有效元素 56 private int size(){ 57 return (rear+maxSize-front) % maxSize; 58 } 59 60 //获取队首元素,不删除 61 public T peak(){ 62 if(isEmpty()){ 63 throw new RuntimeException("Empty Queue"); 64 } 65 return arr[front]; 66 } 67 }
闲余时间复习数据结构,本人尚还才疏学浅,如果以上哪里有问题,请不吝指出,会及时作出修正,非常感谢。