小言_互联网的博客

【数据结构】队列(Queue)

363人阅读  评论(0)

目录

一.队列(Queue)

二.队列的使用

三.队列的模拟实现(单链表实现)

四.循环队列

五.双端队列(了解)


 

一.队列(Queue)

队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 后进后出的特点

进行插入操作的一端称为队尾

出队列,进行删除操作的一端称为队头
 

二.队列的使用

在Java中,Queue是个接口,底层是通过链表实现的。

如图,队列中主要有这些方法

其实,1和2中方法的效果几乎差不多,但是我们经常使用的是方框2中的方法,某种程度上,2中的几个方法更好

方法 功能
boolean offer(E e) 入队列
E poll() 出队列
peek() 获取队头元素,但不删除元素
int size() 获取队列中有效元素个数
boolean isEmpty() 检测队列是否为空

  
  1. public static void main (String[] args) {
  2. Queue<Integer> queue = new LinkedList<>();
  3. queue.offer( 1);
  4. queue.offer( 2);
  5. queue.offer( 3);
  6. queue.offer( 4);
  7. System.out.println(queue.poll()); //从队头出队列,并将删除的元素返回
  8. Integer val = queue.peek(); //获取队头元素,但不删除
  9. System.out.println(val);
  10. if (queue.isEmpty()) {
  11. System.out.println( "队列空");
  12. } else {
  13. System.out.println(queue.size());
  14. }
  15. }

 

三.队列的模拟实现(单链表实现)

首先,我们提出一个疑问:入队采用头插法还是尾插法呢?

如果入队采用的是头插法:入队的时候就是从头入:头插法【O(1)】

                                            出队的时候:删除尾节点【O(n)】

如果入队采用的是尾插法:入队的时候就是从尾入:尾插法【O(n)】 

                                           出队的时候:删除头节点【O(1)】

可当我们给单向链表加一个last属性以标记尾节点时,采用尾插法入队的入队和出队的时间复杂度都是O(1),而采用头插法入队的入队是O(1),出队仍是O(n) 【这是因为要删除尾节点,就得找到尾节点的前一个】 

所以,我们如果要用单链表,采用尾插法入队更好,即尾巴入队,从头出队的方法

 

但如果是双向链表,不管从哪边入队,时间复杂度都是O(1)

下面我们来看一下用单链表模拟实现队列的代码吧


  
  1. public class MyLinkedList {
  2. class Node{
  3. public int val;
  4. public Node next;
  5. public Node (int val){
  6. this.val = val;
  7. }
  8. public Node head;
  9. public Node last;
  10. public int UsedSize;
  11. /**
  12. * 入队
  13. * @param val
  14. */
  15. public void offer (int val){
  16. Node node = new Node(val);
  17. if( this.head == null){
  18. head = node;
  19. last = node;
  20. } else{
  21. last.next = node;
  22. last = node;
  23. }
  24. this.UsedSize++;
  25. }
  26. /**
  27. * 出队(从头出)
  28. * @return
  29. */
  30. public int poll (){
  31. if(isEmpty()){
  32. throw new RuntimeException( "队列为空!");
  33. } else{
  34. int val = this.head.val;
  35. head = head.next;
  36. //处理只有一个节点的情况
  37. if( this.head == null){
  38. last = null;
  39. }
  40. this.UsedSize--;
  41. return val;
  42. }
  43. }
  44. /**
  45. * 出队但是不删除
  46. * @return
  47. */
  48. public int peek (){
  49. if(isEmpty()){
  50. throw new RuntimeException( "队列为空!");
  51. }
  52. return this.head.val;
  53. }
  54. public int size (){
  55. return this.UsedSize;
  56. }
  57. public boolean isEmpty (){
  58. return UsedSize== 0 ;
  59. }
  60. }
  61. }

 

四.循环队列

实际使用中,我们有时还会使用一种特殊的队列——循环队列

循环队列通常用数组实现

 

我们分析一下如何实现

数组下标循环的小技巧

1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length
 

 2. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length

 

 如何区分空与满

  1.  通过添加 size 属性记录(UsedSize)
  2.  使用标记
  3.  保留一个位置(空一个位置)

 

例题 

力扣 

 设计循环队列:具体的解析都写在注释里啦~


  
  1. class MyCircularQueue {
  2. private int[] elem;
  3. private int front; //表示队头下标
  4. private int rear; //表示队尾下标
  5. /**
  6. * 构造方法
  7. * @param k k个大小
  8. */
  9. public MyCircularQueue (int k) {
  10. elem = new int[k+ 1]; //由于我们需要浪费一个空间来确定是否为满,所以k要加 1
  11. //如果不加 1 ,就用UsedSize==length来判断满
  12. }
  13. /**
  14. * 入队
  15. * 1.判断队列是否是满的
  16. * 2.不满,则把当前需要的元素放到 rear下标的地方
  17. * @param value
  18. * @return
  19. */
  20. public boolean enQueue (int value) {
  21. if(isFull()) {
  22. return false;
  23. }
  24. this.elem[rear] = value;
  25. //this.rear++; 这是错误的,因为是一个循环队列,要考虑大小,直接加可能会导致数组越界
  26. this.rear = (rear+ 1) % elem.length;
  27. return true;
  28. }
  29. /**
  30. * 出队
  31. * 1.判断队列是否为空的
  32. * @return
  33. */
  34. public boolean deQueue () {
  35. if(isEmpty()) {
  36. return false;
  37. }
  38. this.front = ( this.front+ 1) % this.elem.length; //不能直接加 1
  39. return true;
  40. }
  41. /**
  42. * 得到队头元素
  43. * @return
  44. */
  45. public int Front () {
  46. if(isEmpty()) {
  47. return - 1;
  48. }
  49. return elem[front];
  50. }
  51. /**
  52. * 得到队尾元素
  53. * @return
  54. */
  55. public int Rear () {
  56. if(isEmpty()) {
  57. return - 1;
  58. }
  59. /*
  60. return elem[rear];
  61. 这样写是错误的,由于(0-1)=-1,会越界,我们应该考虑 rear=0的情况
  62. */
  63. int index = ( this.rear== 0) ? ( this.elem.length- 1) : ( this.rear- 1);
  64. return elem[index];
  65. }
  66. /**
  67. * 判断当前队列是否为空
  68. * rear和front相遇就是空的
  69. * @return
  70. */
  71. public boolean isEmpty () {
  72. return rear == front; //rear和front相遇就是空的
  73. }
  74. /**
  75. * 判断当前队列是否为满
  76. * 浪费一个空间来表示满
  77. * @return
  78. */
  79. public boolean isFull () {
  80. if( (rear+ 1)%elem.length == front ){
  81. return true;
  82. }
  83. return false;
  84. }
  85. }

五.双端队列(了解)

下面我们再来简单的介绍一下双端队列(Deque)
 双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
 

Deque是一个接口,使用时必须创建LinkedList的对象。

 

 

 以上是一些队列的有关知识

 

 

 


转载:https://blog.csdn.net/Living_Amethyst/article/details/125386843
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场