当前位置:   article > 正文

数据结构----队列:顺序队列&顺序循环队列、链式队列、顺序优先队列

顺序队列

一、队列的概念:

  队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在其一端进行插入操作在其另一端进行删除操作

队列中允许进行插入操作的一端称为队尾,允许进行删除操作的一端称为队头。队列的插入操作通常称作入队列,队列的删除操作通常称作出队列

下图是一个依次向队列中插入数据元素a0,a1,...,an-1后的示意图:

faf7d1e6-07b1-44bb-9752-5fcc113a0648

上图中,a0是当前 队头数据元素,an-1是当前 队尾数据元素。

为了避免当只有一个元素时,对头和队尾重合使得处理变得麻烦,所以引入两个指针:front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样的话,当front指针等于rear时,此队列不是还剩一个元素,而是空队列。

二、队列的抽象数据类型:

数据集合:

  队列的数据集合可以表示为a0,a1,…,an-1,每个数据元素的数据类型可以是任意的类型。

操作集合:

(1)入队列append(obj):把数据元素obj插入队尾。

(2)出队列delete():把队头数据元素删除并由函数返回。

(3)取队头数据元素getFront():取队头数据元素并由函数返回。

(4)非空否isEmpty():非空否。若队列非空,则函数返回false,否则函数返回true。

三、循环顺序队列:

线性表有顺序存储和链式存储,队列是一种特殊的线性表,同样也存在这两种存储方式。我们先来看一下队列的顺序存储。

1、顺序队列的“假溢出”:

607ede1a-91b5-4ac9-8850-07078379fddb

上图中,front指针指向队头元素,rear指针指向队尾元素的下一个位置。图(d)中b、c、d出队后,front指针指向元素e,rear指针在数组外面。假设这个队列的总个数不超过5个,但目前如果接着入队的话,因数组末尾元素已经被占用,再向后加就会产生数组越界的错误,可实际上队列在下标为0、1、2、3、4的地方还是空闲的,我们把这种现象叫做“假溢出”。

2、循环顺序队列:

    所以解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种逻辑上首尾相连的顺序存储结构称为循环队列

如何判断循环队列究竟是空的还是满的:

  现在问题又来了,我们之前说,空队列时,front指针等于rear指针,那么现在循环队列满的时候,也是front等于rear,那么如何判断循环队列究竟是空的还是满的?有如下办法:

  • 办法1:设置一个标志位flag。初始时置flag=0;每当入队列操作成功就置flag=1;每当出队列操作成功就置flag=0。则队列空的判断条件为:rear == front && tag==0;队列满的判断条件为:rear = = front && tag= =1。
  • 办法2:保留一个元素的存储空间。此时,队列满时的判断条件为  (rear + 1) % maxSize == front;队列空的判断条件还是front == rear。
  • 办法3:设计一个计数器count,统计队列中的元素个数。此时,队列满的判断条件为:count > 0 && rear == front ;队列空的判断条件为count == 0。

我们在接下来的代码中采用方法3来实现。

3、代码实现:(循环顺序队列的创建)

(1)Queue.java:(队列接口)

  1. //队列接口
  2. public interface Queue {
  3. //入队
  4. public void append(Object obj) throws Exception;
  5. //出队
  6. public Object delete() throws Exception;
  7. //获得队头元素
  8. public Object getFront() throws Exception;
  9. //判断对列是否为空
  10. public boolean isEmpty();
  11. }

(2)CircleSequenceQueue.java:(循环顺序队列

  1. //循环顺序队列
  2. public class CircleSequenceQueue implements Queue {
  3. static final int defaultSize = 10; //默认队列的长度
  4. int front; //队头
  5. int rear; //队尾
  6. int count; //统计元素个数的计数器
  7. int maxSize; //队的最大长度
  8. Object[] queue; //队列
  9. public CircleSequenceQueue() {
  10. init(defaultSize);
  11. }
  12. public CircleSequenceQueue(int size) {
  13. init(size);
  14. }
  15. public void init(int size) {
  16. maxSize = size;
  17. front = rear = 0;
  18. count = 0;
  19. queue = new Object[size];
  20. }
  21. @Override
  22. public void append(Object obj) throws Exception {
  23. // TODO Auto-generated method stub
  24. if (count > 0 && front == rear) {
  25. throw new Exception("队列已满!");
  26. }
  27. queue[rear] = obj;
  28. rear = (rear + 1) % maxSize;
  29. count++;
  30. }
  31. @Override
  32. public Object delete() throws Exception {
  33. // TODO Auto-generated method stub
  34. if (isEmpty()) {
  35. throw new Exception("队列为空!");
  36. }
  37. Object obj = queue[front];
  38. front = (front + 1) % maxSize;
  39. count--;
  40. return obj;
  41. }
  42. @Override
  43. public Object getFront() throws Exception {
  44. // TODO Auto-generated method stub
  45. if (!isEmpty()) {
  46. return queue[front];
  47. } else {
  48. return null;
  49. }
  50. }
  51. @Override
  52. public boolean isEmpty() {
  53. // TODO Auto-generated method stub
  54. return count == 0;
  55. }
  56. }

(3)Test.java:

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. CircleSequenceQueue queue = new CircleSequenceQueue();
  4. queue.append("a");
  5. queue.append("b");
  6. queue.append("c");
  7. queue.append("d");
  8. queue.append("e");
  9. queue.append("f");
  10. queue.delete();
  11. queue.delete();
  12. queue.append("g");
  13. while (!queue.isEmpty()) {
  14. System.out.println(queue.delete());
  15. }
  16. }
  17. }

运行效果:

251fa2d6-120f-4b20-8754-f7a795b20cf1

4、循环队列应用:

  使用顺序循环队列和多线程实现一个排队买票的例子。而且,我们只允许这个队伍中同时排队的只有10个人,那就需要用到队列了。

    生产者(等候买票)

    消费者 (买票离开)

这里面我们需要用到上面的Queue.java类和CircleSequenceQueue.java类。

代码结构:

62c99e55-fe55-41d0-8794-7a74a37ca826

(3)WindowQueue.java:

  1. //卖票窗口
  2. public class WindowQueue {
  3. //卖票的队列
  4. int maxSize = 10;
  5. CircleSequenceQueue queue = new CircleSequenceQueue(maxSize);
  6. int num = 0; //统计卖票的数量,一天最多卖100张票。
  7. boolean isAlive = true; //判断是否继续卖票。
  8. //排队买票
  9. public synchronized void producer() throws Exception {
  10. if (queue.count < maxSize) {
  11. queue.append(num++); //等待买票的数量加1
  12. System.out.println("第" + num + "个客户排队等待买票!");
  13. this.notifyAll();//唤醒卖票的线程
  14. } else {
  15. try {
  16. System.out.println("队列已满...请等待!");
  17. this.wait();//队列满时,排队买票线程等待。
  18. } catch (Exception ex) {
  19. ex.printStackTrace();
  20. }
  21. }
  22. }
  23. //卖票
  24. public synchronized void consumer() throws Exception {
  25. if (queue.count > 0) {
  26. Object obj = queue.delete();
  27. int temp = Integer.parseInt(obj.toString());
  28. System.out.println("第" + (temp + 1) + "个客户买到票离开队列!");
  29. //如果当前队列为空,并且卖出票的数量大于等于100,说明卖票结束
  30. if (queue.isEmpty() && this.num >= 100) {
  31. this.isAlive = false;
  32. }
  33. this.notifyAll(); //唤醒排队买票的线程。
  34. } else {
  35. try {
  36. System.out.println("队列已空...请等待!");
  37. this.wait();//队列空时,卖票线程等待。
  38. } catch (Exception ex) {
  39. ex.printStackTrace();
  40. }
  41. }
  42. }
  43. }

(4)Producer.java:

  1. //买票者
  2. public class Producer implements Runnable {
  3. WindowQueue queue;
  4. public Producer(WindowQueue queue) {
  5. this.queue = queue;
  6. }
  7. @Override
  8. public void run() {
  9. // TODO Auto-generated method stub
  10. while (queue.num < 100) {
  11. try {
  12. queue.producer();
  13. } catch (Exception ex) {
  14. ex.printStackTrace();
  15. }
  16. }
  17. }
  18. }

(5)Consumer.java:

  1. //卖票者
  2. public class Consumer implements Runnable {
  3. WindowQueue queue;
  4. public Consumer(WindowQueue queue) {
  5. this.queue = queue;
  6. }
  7. @Override
  8. public void run() {
  9. // TODO Auto-generated method stub
  10. while (queue.isAlive) {
  11. try {
  12. queue.consumer();
  13. } catch (Exception ex) {
  14. ex.printStackTrace();
  15. }
  16. }
  17. }
  18. }

(6)test.java:

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. WindowQueue queue = new WindowQueue();
  4. Producer p = new Producer(queue);//注意一定要传同一个窗口对象
  5. Consumer c = new Consumer(queue);
  6. //排队买票线程
  7. Thread pThread = new Thread(p);
  8. //卖票线程
  9. Thread cThread = new Thread(c);
  10. pThread.start(); //开始排队买票
  11. cThread.start(); //开始卖票
  12. }
  13. }

注意第07行的注释。

运行效果:

bb9b709e-bc10-48ab-89e6-260ca38ae5d2

  

四、链式队列:

    链式队列其实就是特殊的单链表,只不过它只能尾进头出而已。链式队列的存储结构如下图所示:

6c1486eb-a189-413f-bce1-a8f516aede26

1、链式队列的实现:

(1)Node.java:结点类

  1. //结点类
  2. public class Node {
  3. Object element; //数据域
  4. Node next; //指针域
  5. //头结点的构造方法
  6. public Node(Node nextval) {
  7. this.next = nextval;
  8. }
  9. //非头结点的构造方法
  10. public Node(Object obj, Node nextval) {
  11. this.element = obj;
  12. this.next = nextval;
  13. }
  14. //获得当前结点的后继结点
  15. public Node getNext() {
  16. return this.next;
  17. }
  18. //获得当前的数据域的值
  19. public Object getElement() {
  20. return this.element;
  21. }
  22. //设置当前结点的指针域
  23. public void setNext(Node nextval) {
  24. this.next = nextval;
  25. }
  26. //设置当前结点的数据域
  27. public void setElement(Object obj) {
  28. this.element = obj;
  29. }
  30. public String toString() {
  31. return this.element.toString();
  32. }
  33. }

(2)Queue.java:

  1. //队列接口
  2. public interface Queue {
  3. //入队
  4. public void append(Object obj) throws Exception;
  5. //出队
  6. public Object delete() throws Exception;
  7. //获得队头元素
  8. public Object getFront() throws Exception;
  9. //判断对列是否为空
  10. public boolean isEmpty();
  11. }

(3)LinkQueue.java:

  1. public class LinkQueue implements Queue {
  2. Node front; //队头
  3. Node rear; //队尾
  4. int count; //计数器
  5. public LinkQueue() {
  6. init();
  7. }
  8. public void init() {
  9. front = rear = null;
  10. count = 0;
  11. }
  12. @Override
  13. public void append(Object obj) throws Exception {
  14. // TODO Auto-generated method stub
  15. Node node = new Node(obj, null);
  16. //如果当前队列不为空。
  17. if (rear != null) {
  18. rear.next = node; //队尾结点指向新结点
  19. }
  20. rear = node; //设置队尾结点为新结点
  21. //说明要插入的结点是队列的第一个结点
  22. if (front == null) {
  23. front = node;
  24. }
  25. count++;
  26. }
  27. @Override
  28. public Object delete() throws Exception {
  29. // TODO Auto-generated method stub
  30. if (isEmpty()) {
  31. new Exception("队列已空!");
  32. }
  33. Node node = front;
  34. front = front.next;
  35. count--;
  36. return node.getElement();
  37. }
  38. @Override
  39. public Object getFront() throws Exception {
  40. // TODO Auto-generated method stub
  41. if (!isEmpty()) {
  42. return front.getElement();
  43. } else {
  44. return null;
  45. }
  46. }
  47. @Override
  48. public boolean isEmpty() {
  49. // TODO Auto-generated method stub
  50. return count == 0;
  51. }
  52. }

(4)Test.java:

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. LinkQueue queue = new LinkQueue();
  4. queue.append("a");
  5. queue.append("b");
  6. queue.append("c");
  7. queue.append("d");
  8. queue.append("e");
  9. queue.append("f");
  10. queue.delete();
  11. queue.delete();
  12. queue.append("g");
  13. while (!queue.isEmpty()) {
  14. System.out.println(queue.delete());
  15. }
  16. }
  17. }

运行效果:

7ee743ea-3e2a-4795-b7b3-10dfef4f3e9b

2、链式队列的应用:

题目:

  编写一个判断一个字符串是否是回文的算法。

思路:

  设字符数组str中存放了要判断的字符串。把字符数组中的字符逐个分别存入一个队列和栈中,然后逐个出队和出栈比较出队的字符与出栈的字符是否相同,若全部相等则该字符串为回文。

代码实现:

  这里面需要用到上面一段中的LinkQueue类。代码结构如下:

3a04b30d-6ed1-4556-a408-afc8b4c6068a

(4)Stack.java:栈接口

  1. //栈接口
  2. public interface Stack {
  3. //入栈
  4. public void push(Object obj) throws Exception;
  5. //出栈
  6. public Object pop() throws Exception;
  7. //获得栈顶元素
  8. public Object getTop() throws Exception;
  9. //判断栈是否为空
  10. public boolean isEmpty();
  11. }

(5)LinkStack.java:

  1. public class LinkStack implements Stack {
  2. Node head; //栈顶指针
  3. int size; //结点的个数
  4. public LinkStack() {
  5. head = null;
  6. size = 0;
  7. }
  8. @Override
  9. public Object getTop() throws Exception {
  10. // TODO Auto-generated method stub
  11. return head.getElement();
  12. }
  13. @Override
  14. public boolean isEmpty() {
  15. // TODO Auto-generated method stub
  16. return head == null;
  17. }
  18. @Override
  19. public Object pop() throws Exception {
  20. // TODO Auto-generated method stub
  21. if (isEmpty()) {
  22. throw new Exception("栈为空!");
  23. }
  24. Object obj = head.getElement();
  25. head = head.getNext();
  26. size--;
  27. return obj;
  28. }
  29. @Override
  30. public void push(Object obj) throws Exception {
  31. // TODO Auto-generated method stub
  32. head = new Node(obj, head);
  33. size++;
  34. }
  35. }

(6)Test.java:测试类

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. String str1 = "ABCDCBA"; //是回文
  4. String str2 = "ABCDECAB"; //不是回文
  5. try {
  6. if (Test.isHuiWen(str1)) {
  7. System.out.println(str2 + ":是回文!");
  8. } else {
  9. System.out.println(str2 + ":不是回文!");
  10. }
  11. } catch (Exception ex) {
  12. ex.printStackTrace();
  13. }
  14. }
  15. //方法:判断字符串是否回文
  16. public static boolean isHuiWen(String str) throws Exception {
  17. int n = str.length();
  18. LinkStack stack = new LinkStack();//创建堆栈
  19. LinkQueue queue = new LinkQueue();//创建队列
  20. for (int i = 0; i < n; i++) {
  21. stack.push(str.subSequence(i, i + 1)); //把字符串每个字符压进堆栈
  22. queue.append(str.subSequence(i, i + 1));//把字符串每个字符压入队列
  23. }
  24. while (!queue.isEmpty() && !stack.isEmpty()) {
  25. if (!queue.delete().equals(stack.pop())) { //出队列,出栈,同时判断是否相同
  26. return false;
  27. }
  28. }
  29. return true;
  30. }
  31. }

3、循环队列和链式队列的比较:

(1)从时间上看,它们的基本操作都是常数时间,即O(1)的。不过循环队列是事先申请好空间,使用期间不释放;而链式队列,每次申请和释放结点也会存在一定的时间开销,如果入栈和出栈比较频繁,则两者还是有细微的差别。

(2)从空间上看,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链式队列不存在这个问题,尽管它需要一个指针域,会产生一些空间上的开销,但也可以接受。所以在空间上,链式队列更加灵活。

总结:总的来说,在可以确定队列长度的最大值的情况下,建议用循环队列,如果你无法估计队列的长度,那就用链式队列。

五、优先级队列:

  优先级队列是带有优先级的队列。

    用顺序存储结构实现的优先级队列称作顺序优先级队列

    用链式存储结构存储的优先级队列称作链式优先级队列

顺序优先级队列顺序循环队列相比主要有两点不同

(1)对于顺序优先级队列来说,出队列操作不是把队头数据元素出队列,而是把队列中优先级最高的数据元素出队列。(入队操作没区别)

(2)对于顺序优先级队列来说,数据元素由两部分组成,一部分是原先意义上的数据元素,另一部分是优先级。通常设计优先级为int类型的数值,并规定数值越小优先级越高

1、顺序优先队列的实现:

设计顺序优先级队列分为两个类:

  数据元素类

  优先级队列类

代码实现:

(1)Element.java:

  1. //优先级队列元素类
  2. public class Element {
  3. private Object element; // 数据
  4. private int priority; // 优先级
  5. public Element(Object obj, int priority) {
  6. this.element = obj;
  7. this.priority = priority;
  8. }
  9. public Object getElement() {
  10. return element;
  11. }
  12. public void setElement(Object element) {
  13. this.element = element;
  14. }
  15. public int getPriority() {
  16. return priority;
  17. }
  18. public void setPriority(int priority) {
  19. this.priority = priority;
  20. }
  21. }

(2)Queue.java:

  1. //队列接口
  2. public interface Queue {
  3. //入队
  4. public void append(Object obj) throws Exception;
  5. //出队
  6. public Object delete() throws Exception;
  7. //获得队头元素
  8. public Object getFront() throws Exception;
  9. //判断对列是否为空
  10. public boolean isEmpty();
  11. }

(3)PrioritySequenceQueue.java:

  1. //优先级队列
  2. public class PrioritySequenceQueue implements Queue {
  3. static final int defaultSize = 10; //默认队列长度
  4. int front; //队头
  5. int rear; //队尾
  6. int count; //计数器
  7. int maxSize; //队列最大长度
  8. Element[] queue; //队列
  9. public PrioritySequenceQueue() {
  10. init(defaultSize);
  11. }
  12. public PrioritySequenceQueue(int size) {
  13. init(size);
  14. }
  15. public void init(int size) {
  16. maxSize = size;
  17. front = rear = 0;
  18. count = 0;
  19. queue = new Element[size];
  20. }
  21. @Override
  22. public void append(Object obj) throws Exception {
  23. // TODO Auto-generated method stub
  24. //如果队列已满
  25. if (count >= maxSize) {
  26. throw new Exception("队列已满!");
  27. }
  28. queue[rear] = (Element) obj;
  29. rear++;
  30. count++;
  31. }
  32. @Override
  33. public Object delete() throws Exception {
  34. // TODO Auto-generated method stub
  35. if (isEmpty()) {
  36. throw new Exception("队列为空!");
  37. }
  38. //默认第一个元素为优先级最高的。
  39. Element min = queue[0];
  40. int minIndex = 0;
  41. for (int i = 0; i < count; i++) {
  42. if (queue[i].getPriority() < min.getPriority()) {
  43. min = queue[i];
  44. minIndex = i;
  45. }
  46. }
  47. //找的优先级别最高的元素后,把该元素后面的元素向前移动。
  48. for (int i = minIndex + 1; i < count; i++) {
  49. queue[i - 1] = queue[i]; //移动元素
  50. }
  51. rear--;
  52. count--;
  53. return min;
  54. }
  55. @Override
  56. public Object getFront() throws Exception {
  57. // TODO Auto-generated method stub
  58. if (isEmpty()) {
  59. throw new Exception("队列为空!");
  60. }
  61. //默认第一个元素为优先级最高的。
  62. Element min = queue[0];
  63. int minIndex = 0;
  64. for (int i = 0; i < count; i++) {
  65. if (queue[i].getPriority() < min.getPriority()) {
  66. min = queue[i];
  67. minIndex = i;
  68. }
  69. }
  70. return min;
  71. }
  72. @Override
  73. public boolean isEmpty() {
  74. // TODO Auto-generated method stub
  75. return count == 0;
  76. }
  77. }

2、代码测试:

  设计一个程序模仿操作系统的进程管理问题。进程服务按优先级高的先服务,优先级相同的先到先服务的原则管理。

  模仿数据包含两个部分:进程编号和优先级。如下有五个进程:

    1      30

    2      20

    3      40

    4      20

    5      0       ----------优先级最高,先服务

(4)Test.java:

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. PrioritySequenceQueue queue = new PrioritySequenceQueue();
  4. Element temp;
  5. //五个进程入队
  6. queue.append(new Element(1, 30));
  7. queue.append(new Element(2, 20));
  8. queue.append(new Element(3, 40));
  9. queue.append(new Element(4, 20));
  10. queue.append(new Element(5, 0));
  11. //按照优先级出队。
  12. System.out.println("编号 优先级");
  13. while (!queue.isEmpty()) {
  14. temp = (Element) queue.delete();
  15. System.out.println(temp.getElement() + " " + temp.getPriority());
  16. }
  17. }
  18. }

运行效果:

f22bc718-69dd-488e-a893-dd93109da4cf

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/551855
推荐阅读
相关标签
  

闽ICP备14008679号