• 数据结构与算法(四)——队列


    二、队列

    1、介绍

      队列是一种先进先出的线性表,它只允许在一端(队尾)进行插入操作,在另一端(队头)进行删除操作。可以用数组或链表来实现,一般用链表来实现,简称为链队列,建立在内存的动态区。

    2、队列的顺序存储实现

      顾名思义,用顺序表的方法实现,通常用数组。队列的顺序储存结构:

      下面用数组实现队列:
      思想:队列出队入队动态图:

      队列的特性:队尾插入,队头删除。那么,一个元素入队列,rear++;一个元素出队列,front++。
      结论:看图可知
      判空:起始状态,front == rear == -1,则空队列判定,front == rear。
      判满:一个元素入队列,rear++,则满队列判定,rear == length() - 1。
      代码示例:数组实现队列

     1 // 用数组模拟队列
     2 public class MyQueue<T> {
     3     private T[] elementData;
     4     private int front;
     5     private int rear;
     6 
     7     public MyQueue(int initialCapacity) {
     8         if (initialCapacity > 0) {
     9             this.elementData = (T[]) new Object[initialCapacity];
    10         } else if (initialCapacity == 0) {
    11             this.elementData = (T[]) new Object[]{};
    12         } else {
    13             throw new IllegalArgumentException("Illegal Capacity: " +
    14                     initialCapacity);
    15         }
    16 
    17         front = -1;
    18         rear = -1;
    19     }
    20 
    21     // 入队
    22     public boolean offer(T e) {
    23         if (isFull()) {
    24             System.out.println("队列满,不能添加~");
    25             return false;
    26         }
    27         rear++;
    28         elementData[rear] = e;
    29         return true;
    30     }
    31 
    32     // 出队
    33     public T poll() {
    34         if (isEmpty()) {
    35             throw new RuntimeException("队空,没有任何数据~");
    36         }
    37         front++;
    38         return elementData[front];
    39     }
    40 
    41     // 读取队头
    42     public T peek() {
    43         if (isEmpty()) {
    44             throw new RuntimeException("队空,没有任何数据~");
    45         }
    46         return elementData[front + 1];
    47     }
    48 
    49     // 判空
    50     public boolean isEmpty() {
    51         return front == rear;
    52     }
    53 
    54     // 判满
    55     private boolean isFull() {
    56         return rear == elementData.length - 1;
    57     }
    58 
    59     // 获取队列元素的个数
    60     public int size() {
    61         return rear - front;
    62     }
    63 
    64     @Override
    65     public String toString() {
    66         if (isEmpty()) {
    67             return "[]";
    68         }
    69 
    70         StringBuilder builder = new StringBuilder();
    71         builder.append("[");
    72         for (int i = front + 1; i < rear + 1; i++) {
    73             builder.append(elementData[i])
    74                     .append(",");
    75         }
    76         // 去掉最后一个,
    77         builder.deleteCharAt(builder.length() - 1);
    78         builder.append("]");
    79 
    80         return builder.toString();
    81     }
    82 }
    数组实现队列

      代码示例:测试类

     1 // 测试类
     2 public static void main(String[] args) {
     3     MyQueue<Integer> myQueue = new MyQueue<>(5);
     4     myQueue.offer(1);
     5     myQueue.offer(10);
     6     myQueue.offer(5);
     7     myQueue.offer(9);
     8     myQueue.offer(90);
     9     myQueue.offer(900);
    10 
    11     // 1 10 5 9 90
    12     System.out.println(myQueue);
    13     System.out.println("size:" + myQueue.size());
    14 
    15     System.out.println(myQueue.poll());
    16     System.out.println(myQueue.poll());
    17     System.out.println(myQueue.poll());
    18     System.out.println(myQueue.poll());
    19     System.out.println(myQueue);
    20     System.out.println("size:" + myQueue.size());
    21 
    22     myQueue.offer(900);
    23 }
    24 
    25 // 结果
    26 堆满,不能添加~
    27 [1,10,5,9,90]
    28 size:5
    29 1
    30 10
    31 5
    32 9
    33 [90]
    34 size:1
    35 堆满,不能添加~
    测试类

      问题:看图可知
      假溢出:这样实现的队列,在H、e出队后,继续入队列,会出现"堆满,不能添加元素"或"数组越界"的情况。但事实上,数组索引 0 和 1 的位置已经空了,这叫假溢出。
      那么,如何解决这个问题呢?
      很自然的想到,如果此时继续入队列,就将元素放在索引 0 的位置。这就是循环队列。

      循环队列:

      为解决假溢出,将数组的首尾相接,模拟出来的逻辑上的循环,容量是固定的。循环队列出队入队动态图:

      特别注意:①这里修改了front、rear的含义,起始状态,front == rear == 0。②上图是一个循环队列,请脑补一下,将他们首尾相接。所以上图,后两图rear == 0。
      为了让小伙伴方便看的清楚,下面给出一个上图首尾相接的图。
      思想:

      由队列的特性:队尾插入,队头删除。那么,一个元素入队列,rear++;一个元素出队列,front++。
      问题:队空(图a):front == rear == 0。队满(图d1):front == rear。那么,队空和队满就无法判断。
      解决方案:牺牲一个存储单元(图d2),即队列最后一个存储单元不用。

      结论:看图可知

      判空:起始状态,front == rear == 0,则空队列判定,front == rear。
      判满:一个元素入队列,rear++,则满队列判定,(rear + 1) % length() == front。
      队列长度:队列元素的个数,(rear - front + length()) % length()。
      代码示例:数组实现循环队列

     1 // 用数组模拟循环队列
     2 public class MyCircleQueue<T> {
     3     private T[] elementData;
     4     private int front;
     5     private int rear;
     6 
     7     public MyCircleQueue(int initialCapacity) {
     8         if (initialCapacity > 0) {
     9             this.elementData = (T[]) new Object[initialCapacity];
    10         } else if (initialCapacity == 0) {
    11             this.elementData = (T[]) new Object[]{};
    12         } else {
    13             throw new IllegalArgumentException("Illegal Capacity: " +
    14                     initialCapacity);
    15         }
    16 
    17         front = 0;
    18         rear = 0;
    19     }
    20 
    21     // 入队
    22     public boolean offer(T e) {
    23         if (isFull()) {
    24             System.out.println("队列满,不能添加~");
    25             return false;
    26         }
    27         elementData[rear] = e;
    28         rear = (rear + 1) % elementData.length;
    29         return true;
    30     }
    31 
    32     // 出队
    33     public T poll() {
    34         if (isEmpty()) {
    35             throw new RuntimeException("队空,没有任何数据~");
    36         }
    37         final T t = elementData[front];
    38         front = (front + 1) % elementData.length;
    39 
    40         return t;
    41     }
    42 
    43     // 读取队头
    44     public T peek() {
    45         if (isEmpty()) {
    46             throw new RuntimeException("队空,没有任何数据~");
    47         }
    48         return elementData[front];
    49     }
    50 
    51     // 判空
    52     public boolean isEmpty() {
    53         return front == rear;
    54     }
    55 
    56     // 判满
    57     private boolean isFull() {
    58         return (rear + 1) % elementData.length == front;
    59     }
    60 
    61     // 获取队列元素的个数
    62     public int size() {
    63         return (rear - front + elementData.length) % elementData.length;
    64     }
    65 
    66     @Override
    67     public String toString() {
    68         if (isEmpty()) {
    69             return "[]";
    70         }
    71 
    72         StringBuilder builder = new StringBuilder();
    73         builder.append("[");
    74         // 从front开始遍历,遍历 size() 个元素
    75         for (int i = front; i < front + size(); i++) {
    76             builder.append(elementData[i % elementData.length])
    77                     .append(",");
    78         }
    79         // 去掉最后一个,
    80         builder.deleteCharAt(builder.length() - 1);
    81         builder.append("]");
    82 
    83         return builder.toString();
    84     }
    85 }
    数组实现循环队列

      代码示例:测试类

     1 // 测试类
     2 public static void main(String[] args) {
     3     MyCircleQueue<Integer> myQueue = new MyCircleQueue<>(6);
     4     myQueue.offer(1);
     5     myQueue.offer(10);
     6     myQueue.offer(5);
     7     myQueue.offer(9);
     8     myQueue.offer(90);
     9 
    10     // 1 10 5 9 90
    11     System.out.println(myQueue);
    12     System.out.println("size:" + myQueue.size());
    13     myQueue.offer(900);
    14 
    15     System.out.println(myQueue.poll());
    16     System.out.println(myQueue.poll());
    17     System.out.println(myQueue.poll());
    18     System.out.println(myQueue.poll());
    19     System.out.println(myQueue);
    20     System.out.println("size:" + myQueue.size());
    21 
    22     // 可以继续入队数据
    23     myQueue.offer(900);
    24     System.out.println(myQueue);
    25 }
    26 
    27 // 结果
    28 [1,10,5,9,90]
    29 size:5
    30 队列满,不能添加~
    31 1
    32 10
    33 5
    34 9
    35 [90]
    36 size:1
    37 [90,900]
    测试类

    3、队列的链式存储实现

      顾名思义,用链表的方法实现,通常用单链表。队列的链式储存结构:

      下面用链表实现队列:
      代码示例:带头结点的链表实现队列

     1 public class MyLinkedQueue<E> {
     2     // 头指针.不变
     3     private final Node<E> front;
     4 
     5     // 尾指针
     6     private Node<E> rear;
     7 
     8     public MyLinkedQueue() {
     9         // 初始化头结点
    10         front = new Node<>(null);
    11         rear = front;
    12     }
    13 
    14     // 入队
    15     public boolean offer(E e) {
    16         Node<E> node = new Node<>(e);
    17 
    18         rear.next = node;
    19         rear = node;
    20 
    21         return true;
    22     }
    23 
    24     // 出队
    25     public E poll() {
    26         Node<E> temp = front.next;
    27 
    28         if (temp == null) {
    29             return null;
    30         }
    31 
    32         // 出队.删除队头元素
    33         front.next = front.next.next;
    34         if (isEmpty()) {
    35             rear = front;
    36         }
    37 
    38         return temp.item;
    39     }
    40 
    41     // 获取.队头
    42     public E peek() {
    43         Node<E> temp = front.next;
    44 
    45         if (temp != null) {
    46             return temp.item;
    47         }
    48         return null;
    49     }
    50 
    51     // 空队列时,front和rear都指向头结点.
    52     public boolean isEmpty() {
    53         return front.next == null;
    54     }
    55 
    56     /**
    57      * 打印队列
    58      */
    59     @Override
    60     public String toString() {
    61         StringBuilder builder = new StringBuilder();
    62 
    63         Node<E> temp = front.next;
    64 
    65         while (temp != null) {
    66             builder.append(temp.item)
    67                     .append(",");
    68             temp = temp.next;
    69         }
    70 
    71         if (builder.length() == 0) {
    72             return "[]";
    73         }
    74 
    75         builder.insert(0, "[");
    76         // 去掉最后一个,
    77         builder.deleteCharAt(builder.length() - 1);
    78         builder.append("]");
    79 
    80         return builder.toString();
    81     }
    82 
    83     /**
    84      * 队列的结点
    85      */
    86     private static class Node<E> {
    87         public E item;
    88         public Node<E> next;
    89 
    90         public Node(E item) {
    91             this.item = item;
    92         }
    93     }
    94 
    95 }
    带头结点的链表实现队列

      代码示例:测试类

     1 // 测试类
     2 public static void main(String[] args) {
     3     MyLinkedQueue<Integer> queue = new MyLinkedQueue<>();
     4 
     5     // 添加.入队
     6     queue.offer(9);
     7     queue.offer(5);
     8     queue.offer(2);
     9     queue.offer(7);
    10     System.out.println(queue);
    11 
    12     // 出队
    13     final Integer poll = queue.poll();
    14     System.out.println(poll);
    15     System.out.println(queue);
    16 
    17     // 获取.队头
    18     final Integer peek = queue.peek();
    19     System.out.println(peek);
    20     System.out.println(queue);
    21 }
    22 
    23 // 结果
    24 [9,5,2,7]
    25 9
    26 [5,2,7]
    27 5
    28 [5,2,7]
    测试类

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    最小费用最大流问题
    成大事必备9种能力、9种手段、9种心态
    转 fpga学习经验2
    算法 FFT理论1
    FPGA进阶之路1
    FPGA:亲和力激活竞争力
    1030 又回来了
    转 fpga学习经验1
    调查:近半大学生愿接受15002000元月薪
    转 观点:哪些人适合做FPGA开发(精华)
  • 原文地址:https://www.cnblogs.com/originator/p/14069790.html
Copyright © 2020-2023  润新知