一、 队列的定义
- 1. 为什么要学习队列?
你们在用电脑时有没有经历,机器有时会处于疑似死机的状态,鼠标点什么似乎都没用,双击任何快捷方式都不动弹。就当你失去耐心,打算rest时。突然他像酒醒了一样,把你刚才点击的所有操作全部按顺序执行一遍。这其实是因为操作系统中的多个程序因需要通过一个通道输出,而按先后次序排队等待造成的。
再比如向移动、联通、电信等客服电话,客服人员与客户相比总是少数,在所有的客服人员都占线的情况下,客户会被要求等待,直到有某个客户人员空下来,才能让最先等待的客户接通电话。这里也是将所有当前打客服电话的客户进行排队处理。
操作系统和客服系统中,都是应用了一种数据结构来实现刚才提到的先进先出的排队功能,这就是队列。
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First in First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。假设队列是q=(a1,a2,…,an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,总是从a1开始,而插入时,列在最后。这也比较符合我们通常生活中的习惯,排在第一个的优先出列,最后来的当然在队伍的最后。(先入先出原则)
二.基于数组实现链表
思路:
1.数组有最大容量,因此定义maxSize来指定队列的最大容量。
2.队列存取遵循先入先出的原则,我们定义两个指针,头部指针front,尾部指针rear。
3.队列添加数据时,尾部指针rear依次加1,从队列取数据时,头部指针front依次加1。
4.当front == rear时,代表队列为空。
5.当rear = maxSize - 1时,代表队列已满。
1.定义队列 ArrayQueue
public class ArrayQueue { private int maxSize;//数组最大容量 private int front;//队列头 private int rear;//队列尾 private int[] arr; /*创建队列的构造器*/ public ArrayQueue (int arrMaxSize){ maxSize = arrMaxSize; arr = new int[arrMaxSize]; front = -1; //指向队列头部前一个位置 rear = -1; //指向队列尾部的数据 } /*判断队列是否满*/ public boolean isFull(){ return rear == maxSize-1; } /*判断队列是否为空*/ public boolean isnull(){ return front==rear; } /*向队列添加数据*/ public void addQueue(int n){ //判断队列是否已经满 if (isFull()){ throw new RuntimeException("队列已经满,不能添加数据"); } rear++;//头部后移 arr[rear] = n; } /*获取数据*/ public int getQueue(){ //判断队列是否为空 if (isnull()){ throw new RuntimeException("队列为空,不能取数据"); } front++;//后移 return arr[front]; } /*显示队列数据*/ public void showQueue(){ if (isnull()){ throw new RuntimeException("队列为空!!不能遍历"); } for (int i = 0; i <arr.length ; i++) { System.out.printf("arr[%d]=%d ",i,arr[i]); } } /*显示队列头部数据不是取数据*/ public int headQueue(){ if (isnull()){ throw new RuntimeException("队列为空,不能看数据"); } return arr[front + 1]; } }
2.进行测试 :ArrayQueueDemo
public class ArrayQueueDemo { public static void main(String[] args) { ArrayQueue arrayQueue = new ArrayQueue(3); char key = ' '; Scanner scanner = new Scanner(System.in); boolean loop = true; while (loop){ System.out.println("s(show):显示队列"); System.out.println("e(exit):退出程序"); System.out.println("a(add):添加数据"); System.out.println("g(get):取数据"); System.out.println("h(head):查看头部数据"); key = scanner.next().charAt(0);//接收数据 switch (key){ case 's': try { arrayQueue.showQueue(); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'a': System.out.println("请输入数据"); int value = scanner.nextInt(); try { arrayQueue.addQueue(value); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'g': try { int res = arrayQueue.getQueue(); System.out.printf("取出的数据:%d ",res); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'h': try { int res = arrayQueue.headQueue(); System.out.printf("队列头部数据:%d ",res); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'e': scanner.close(); loop = false; break; default: break; } } System.out.println("程序退出"); } }
三.环形队列
1.区别:
内存中不存在环形数组或者环形队列,所谓环形,是我们通过取模运算在逻辑上实现闭环,使得队列头尾部指针在这个闭环内能够周而复始重复使用的一个概念,正如上图所示。
那么,环形队列和一般队列的区别在哪,我们从指针和队列空、队列满的判断逻辑上看下他们的区别:
1.front头部指针 一般队列:front头部指针初始值为-1,从队列取数据时,该值依次递增,指向的元素即待取出的数据,而队列的头部数据所在的指针位置为front+1。当front=maxSize-1时,队列最后一个数据取出,此时队列为空。 环形队列:front头部指针初始值为0,指向的元素既是队列的头部数据也是待取出的数据。从队列取数据时,因逻辑上的闭环,指针可能再次回到前面的位置,不能单一递增处理,需通过取模来重新计算指针的值。 2.rear尾部指针 一般队列:rear尾部指针初始值为-1,队列添加数据时,该值依次递增,当rear=maxSize-1时,队列满,无法再添加数据。 环形队列:rear尾部指针初始值为0,指向待添加数据的位置,队列添加数据时,因逻辑上的闭环,指针可能再次回到前面的位置,不能单一递增处理,会出现角标越界异常,需通过取模来重新计算指针的值。 3.队列空的判断逻辑 一般队列:rear == front时,队列空。 环形队列:rear == front时,队列空。 4.队列满的判断逻辑 一般队列:rear = maxSize - 1时,队列满。 环形队列:(rear + 1) % maxSize == front时,队列满。
2.基于数组实现环形队列
1.定义环形队列CircleArrayQueue
public class CircleArrayQueue { private int maxSize;//数组最大容量 private int front;//队列头 初始值为0 private int rear;//队列尾 初始值为0 private int[] arr; /*创建队列的构造器*/ public CircleArrayQueue(int arrMaxSize){ maxSize = arrMaxSize; arr = new int[arrMaxSize]; } /*判断队列是否满*/ public boolean isFull(){ //尾索引的西藏一个为头索引时表示队列满,即将队列容量空出一个作为约定 return (rear + 1) % maxSize == front ; } /*判断队列是否为空*/ public boolean isEmpty(){ return front==rear; } /*向队列添加数据*/ public void addQueue(int n){ //判断队列是否已经满 if (isFull()){ throw new RuntimeException("队列已经满,不能添加数据"); } arr[rear] = n; rear = (rear + 1) % maxSize;//头部后移 } /*获取数据*/ public int getQueue(){ //判断队列是否为空 if (isEmpty()){ throw new RuntimeException("队列为空,不能取数据"); } int value = arr[front]; front = (front + 1) % maxSize;//后移 return value; } /*显示队列数据*/ public void showQueue(){ if (isEmpty()){ throw new RuntimeException("队列为空!!不能遍历"); } for (int i = front; i <front + size() ; i++) { System.out.printf("arr[%d]=%d ",i % maxSize,arr[i]); } } /*显示队列头部数据不是取数据*/ public int headQueue(){ if (isEmpty()){ throw new RuntimeException("队列为空,不能看数据"); } return arr[front]; } /*求出当前队列有效数据的个数*/ public int size(){ return (rear + maxSize - front) % maxSize; } }
2.进行测试
public class CircleArrayQueueDemo { public static void main(String[] args) { CircleArrayQueue circleQueue = new CircleArrayQueue(4); char key = ' '; Scanner scanner = new Scanner(System.in); boolean loop = true; while (loop){ System.out.println("s(show):显示队列"); System.out.println("e(exit):退出程序"); System.out.println("a(add):添加数据"); System.out.println("g(get):取数据"); System.out.println("h(head):查看头部数据"); key = scanner.next().charAt(0);//接收数据 switch (key){ case 's': try { circleQueue.showQueue(); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'a': System.out.println("请输入数据"); int value = scanner.nextInt(); try { circleQueue.addQueue(value); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'g': try { int res = circleQueue.getQueue(); System.out.printf("取出的数据:%d ",res); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'h': try { int res = circleQueue.headQueue(); System.out.printf("队列头部数据:%d ",res); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'e': scanner.close(); loop = false; break; default: break; } } System.out.println("程序退出"); } }