• 数据结构与算法【队列】的Java实现


    队列:以顺序的方式维护的一组数据集合,在一端添加数据,从另一端移除数据。习惯来说,添加的一端称为尾,移除的一端称为头。

    通用接口

    1. public interface Queue {
    2. /**
    3. * 插入队列
    4. */
    5. boolean offer(E value);
    6. /**
    7. * 从队列中获取值并移除
    8. */
    9. E poll();
    10. /**
    11. * 从队列中获取值但不移除
    12. */
    13. E peek();
    14. /**
    15. * 检查队列是否已满
    16. */
    17. boolean isFull();
    18. /**
    19. * 检查队列是否不为空
    20. */
    21. boolean isEmpty();
    22. }

    基于单向循环链表的简单实现

    1. public class LinkedQueue implements Queue, Iterable {
    2. //提供哨兵节点
    3. private Node sentinel = new Node(null, null);
    4. //提供尾节点
    5. private Node tail = sentinel;
    6. //队列大小
    7. private int size = 0;
    8. //队列容量
    9. private int capacity = Integer.MAX_VALUE;
    10. public LinkedQueue(int capacity) {
    11. this.capacity = capacity;
    12. tail.next = sentinel;
    13. }
    14. private static class Node {
    15. Node next;
    16. E value;
    17. public Node(Node next, E value) {
    18. this.next = next;
    19. this.value = value;
    20. }
    21. }
    22. @Override
    23. public boolean offer(E value) {
    24. //在队尾插入元素,选择尾插法
    25. if (isFull()) {
    26. return false;
    27. }
    28. Node node = new Node<>(sentinel, value);
    29. tail.next = node;
    30. tail = node;
    31. size++;
    32. return true;
    33. }
    34. @Override
    35. public E poll() {
    36. if (isEmpty()) {
    37. return null;
    38. }
    39. Node first = sentinel.next;
    40. if (first==tail){
    41. //如果是最后一个节点,那么将tail指向sentinel
    42. tail =sentinel;
    43. }
    44. sentinel.next = first.next;
    45. E value = first.value;
    46. size--;
    47. return value;
    48. }
    49. @Override
    50. public E peek() {
    51. return sentinel.next.value;
    52. }
    53. @Override
    54. public boolean isFull() {
    55. return size == capacity;
    56. }
    57. @Override
    58. public boolean isEmpty() {
    59. return size == 0;
    60. }
    61. @Override
    62. public Iterator iterator() {
    63. return new Iterator() {
    64. Node p = sentinel.next;
    65. @Override
    66. public boolean hasNext() {
    67. return p != sentinel;
    68. }
    69. @Override
    70. public E next() {
    71. E value = p.value;
    72. p = p.next;
    73. return value;
    74. }
    75. };
    76. }
    77. }

    基于循环数组的简单实现

    实现前,介绍一下环形数组与数组的区别

    • 对比普通数组,起点和终点更为自由,不用考虑数据移动(普通数组移除元素时需要移动其他元素)
    • “环”意味着不会存在【越界】问题
    • 数组性能更佳
    • 环形数组比较适合实现有界队列、RingBuffer 等
    1. public class ArraysQueue implements Queue, Iterable {
    2. private int head = 0;
    3. private int tail = 0;
    4. //用来记录循环数组大小
    5. private final int length;
    6. private E[] array;
    7. @SuppressWarnings("all")
    8. public ArraysQueue(int capacity) {
    9. this.length = capacity + 1;
    10. //加一是为尾指针留一个空间去判断是否队列已满
    11. this.array = (E[]) new Object[length];
    12. }
    13. @Override
    14. public boolean offer(E value) {
    15. if (isFull()) {
    16. return false;
    17. }
    18. array[tail++] = value;
    19. tail = tail % length;
    20. return true;
    21. }
    22. @Override
    23. public E poll() {
    24. if (isEmpty()) {
    25. return null;
    26. }
    27. E value = array[head];
    28. head = (head + 1) % length;
    29. return value;
    30. }
    31. @Override
    32. public E peek() {
    33. if (isEmpty()) {
    34. return null;
    35. }
    36. return array[head];
    37. }
    38. @Override
    39. public boolean isFull() {
    40. if ((tail + 1) % length == head) {
    41. return true;
    42. }
    43. return false;
    44. }
    45. @Override
    46. public boolean isEmpty() {
    47. return head == tail;
    48. }
    49. @Override
    50. public Iterator iterator() {
    51. return new Iterator() {
    52. int p = head;
    53. @Override
    54. public boolean hasNext() {
    55. return p != tail;
    56. }
    57. @Override
    58. public E next() {
    59. E value = array[p];
    60. p = (p + 1) % length;
    61. return value;
    62. }
    63. };
    64. }
    65. }

    在Java源码中的基于数组实现的队列对容量有一个要求,即一定是2的n次方。之所以这么要求,是因为方便头指针和尾指针的边界确认。

    我们的实现方式中指针的值是通过+1并取余来确定指针的下一个位置,也就是说,head和tail的值始终是在数组长度中。而在Java源码中,并没有规定head与tail的取值一定是数组长度内,而是不停的+1然后通过对数组长度的取余,来确定head与tail的下标位置。

    但是这样又存在一个问题。那就是head或是tail超过了int类型所能表达的最大值后,再去取余会得到负数,使用负数去数组中拿元素会报错。为了解决这个问题,Java针对二进制特点采用了更高效率的实现方案。

    首先就是规定数组长度一定是2的n次方。

    看下面例子

    因此我们不需要在意符号位是否为负数,只需要关心余数的二进制即可。

    对于如何通过二进制的方式获取到余数。是二进制的另一个特性

    我们查看ArrayDeque源码中的添加元素方法

    正是采用了二进制的位运算特性来控制head与tail在数组中的下标位置。如果用户指定数组队列不是一个2的n次方时,他会强制扩容到最近的2的n次方大小。具体实现方式如下

  • 相关阅读:
    界面中局部(部分)区域嵌套滑动
    【C++语法难点】 深拷贝和浅拷贝是什么,拷贝构造,拷贝赋值
    【前端】JavaScript
    基于R语言lavaan结构方程模型(SEM)技术应用
    Python自动化测试中实现远程服务器管理
    计算机组成原理 | 数据的表示、运算和校验(3)数据处理与存储
    深化服务成工业品电商角逐新焦点
    性能测试-Jmeter的三个重要组件(重点)
    Hadoop学习记录1
    达梦数据库将DMHR模式下的表(迁移)导出为EXCEL文件
  • 原文地址:https://blog.csdn.net/zmbwcx/article/details/134448169