当前位置:   article > 正文

数据结构与算法(八)——双端队列

双端队列

目录

一、前情提要

         二、双端队列介绍

(一)双端队列的顺序存储

(二)双端队列的链式存储

三、代码实现

(一)双端队列的顺序存储

(二)双端队列的链式存储


一、前情提要

         上篇文章讲了队列的两种形式,一种是队列的顺序结构,称作循环队列。另一种时队列的链式结构,称作链队列。这两种形式都遵循着一个原则,先进先出的原则,从队尾入队,队头出队。但是还存在一种数据结构“双端队列”,它包含了栈和队列的所有特性。本篇文章将会给出所有操作的代码,并标注详细注释。

二、双端队列介绍

          双端队列(Double-ended Queue,简称Deque)是一种具有队列和栈特性的数据结构,可以在队列的两端进行插入和删除操作。双端队列允许从前端和后端同时进行插入和删除操作,因此可以称为“两端都可以进出的队列”。

           双端队列有以下几个特点:

           1、可以在队列的头部和尾部进行插入和删除的操作。

           2、元素的插入和删除操作可以分别称作入队和出队操作。

           3、可以进行先进先出(FIFO)和先进后出(LIFO)两种操作方式。

           4、可以实现栈和队列以及其他需要在两端进行插入和删除操作的场景。

           双端队列如图示:

      

    对于双端队列来说,就是两端都是结尾的队列。队列的每⼀端都可以插⼊数据项和移除数据项。相对于普通队列。双端队列的⼊队和出队操作在两端都可以进进⾏。
    这是一个很大的提升,这种数据结构的特性,使得他更加的实用和方便。当你只允许一端入队和出队的时候,这就是一个栈。当你限制一端只能入队和另一端只能出队,这就是一个普通队列。
    说完了原理,再来说说存储方式:
    对于双端队列而言,也存在两种存储结构——顺序存储、链式存储。
(一)双端队列的顺序存储
         我们知道顺序存储就是利用数组进行入队和出队。但是如何操作呢?
         首先,我们会设置两个指针。
          头指针——控制从左边入队和出队的操作。
         尾指针——控制从右边入队和出队的操作。
         现在我们有了两个指针,那么问题来了,指针指向哪呢?
         如果指针指向中间
       当出现这种情况时:
      当下次还要从左边插入元素,就可能出现溢出,但是右边还有位置空缺,所以就出现了 假溢出。说起假溢出,解决问题的方法,之前就说过,那就是利用 循环数组
      
       既然是循环的,指针指向哪已经不重要了,但正常情况下 头尾指针都是指向下标为0的数组位置。我们代码也就依据这个写。
       
       还有一个问题, 先开始下标为0的数组元素存储的是从左边入队的元素,还是从右边入队的元素,还是说都可以?
       可能有人有疑问,为什么要这么分?我们仔细分析一下代码。
       如果数组下标为0处 存储的是从左边入队的元素,那么左插和右插的代码就会是:
  1. //假设数组为a[N]
  2. //头尾指针都指向下标为0处
  3. //左插入值为k1
  4. //在下标为0处
  5. a[l]=k1;
  6. l--;
  7. //当右插入值为k2
  8. r++;
  9. a[r]=k2;

        如果数组下标为0处存储的是从右边入队的元素,那么左插和右插的代码就会是:

  1. //假设数组为a[N]
  2. //先开始头尾指针指向数组下标为0处
  3. //左插入值为k1
  4. //在下标为0处
  5. l--;
  6. a[l]=k1;
  7. //右插入值为k2
  8. a[r]=k2;
  9. r++;

   你会发现两份代码不一样,我们在写代码的时候不可能一种操作有两份代码。为了代码的统一性。所以最先开始下标为0的这个点必须规定好,要么存储的是左边入队的元素,要么存储的是右边入队的元素。

    当两个指针指向相同位置时要么队列为空,要么队列已满

    具体代码实现,请看后面。

    (二)双端队列的链式存储

     双端队列的链式存储可以理解为,是基于不带头结点的双向链表。

     此时我们会设置一个mid结点,头尾指针都会指向这个结点。

     那么还会有个问题值得思考。这个mid结点到底存不存数据呢?

     可能有人说存不存无所谓。我们先画个图,模拟一个场景。

    咱假设mid结点不存数据,此时从左边入队了两个元素,右边入队了两个元素。

    重点来了!如果此时我们要从左边出队三个元素,那该咋办呢?

    很明显我们要跳过mid结点去删除数据为3的结点,这样的话我们就得写个if语句特判一下。麻不麻烦暂且不说,很容易忽略掉。

     所以我们在mid结点处,要存入数据。跟上面的顺序存储一样,可以先开始存从左边入队的元素,也可以先开始存从右边入队的元素。在这里写代码统一规定,储存右边入队的元素。、

      因为是双向链表,所以入队的时候就是个简单的尾插代码,之前也有写过。

三、代码实现

       因为很多操作之前都写过了,所以注释就不再重复了,如果看不懂的就回去看对应双向链表的知识。

(一)双端队列的顺序存储
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define QSize 10
  4. int* Queue;
  5. int Left,Right;
  6. int MaxSize;
  7. int Size;
  8. void InitQueue()
  9. {
  10. Queue=(int*)malloc(QSize*sizeof(int));
  11. MaxSize= QSize;
  12. Size=0;
  13. Left=Right=0;
  14. }
  15. void Left_Insert(int Date)
  16. {
  17. if(Size==MaxSize)
  18. {
  19. printf("空间已满");
  20. return;
  21. }
  22. else
  23. {
  24. Left=(Left-1+QSize)%QSize;//如果看不懂,去看循环队列那篇
  25. Queue[Left]=Date;
  26. Size++;
  27. }
  28. }
  29. void Right_Insert(int Date)
  30. {
  31. if(Size==MaxSize)
  32. {
  33. printf("空间已满");
  34. return;
  35. }
  36. else
  37. {
  38. Queue[Right]=Date;
  39. Right=(Right+1)%QSize;
  40. Size++;
  41. }
  42. }
  43. void Left_Remove()
  44. {
  45. if(Size==0)
  46. {
  47. printf("队列为空");
  48. return;
  49. }
  50. else
  51. {
  52. Left=(Left+1)%QSize;
  53. Size--;
  54. }
  55. }
  56. void Right_Remove()
  57. {
  58. if(Size==0)
  59. {
  60. printf("队列为空");
  61. return;
  62. }
  63. else
  64. {
  65. Right=(Right-1+QSize)%QSize;
  66. Size--;
  67. }
  68. }
  69. void Show()
  70. {
  71. if(Size==0) printf("队列为空");
  72. else
  73. {
  74. int flag=Left;
  75. while(flag!=Right)
  76. {
  77. printf("%d ",Queue[flag]);
  78. flag=(flag+1)%QSize;
  79. }
  80. printf("\n");
  81. }
  82. }
  83. int main()
  84. {
  85. InitQueue();
  86. Left_Insert(1);
  87. Left_Insert(2);
  88. Left_Insert(3);
  89. Left_Insert(4);
  90. Right_Insert(5);
  91. Right_Insert(6);
  92. Show();
  93. Left_Remove();
  94. Right_Remove();
  95. Show();
  96. return 0;
  97. }
(二)双端队列的链式存储
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define MaxSize 10
  4. typedef struct LinkQueue
  5. {
  6. int Data;
  7. struct LinkQueue* Pre;
  8. struct LinkQueue* Next;
  9. }Node;
  10. Node* Right;//创建左右指针
  11. Node* Left;
  12. void InitQueue()
  13. {
  14. Right=Left=(Node*)malloc(sizeof(Node));
  15. Left->Next=Left->Pre=NULL;
  16. }
  17. void Left_Insert(int Data)
  18. {
  19. Node* P=(Node*)malloc(sizeof(Node));
  20. if(P==NULL)
  21. {
  22. printf("申请内存失败");
  23. return;
  24. }
  25. P->Data=Data;
  26. P->Pre=NULL; //尾插
  27. P->Next=Left;
  28. Left->Pre=P;
  29. Left=P;
  30. }
  31. void Right_Insert(int Data)
  32. {
  33. Node* P=(Node*)malloc(sizeof(Node));
  34. if(P==NULL)
  35. {
  36. printf("申请内存失败");
  37. return;
  38. }
  39. Right->Data=Data;
  40. Right->Next=P; //尾插
  41. P->Pre=Right;
  42. P->Next=NULL;
  43. Right=P;
  44. }
  45. void Left_Remove()
  46. {
  47. if(Left==NULL||Left==Right)
  48. {
  49. printf("队列为空");
  50. return;
  51. }
  52. Node* P=Left;
  53. Left=Left->Next;
  54. Left->Pre=NULL;
  55. free(P);
  56. }
  57. void Right_Remove()
  58. {
  59. //注意删的是right->pre ,right->pre才是真正的最后一个
  60. //实际操作时只需要把right删掉,即可
  61. if(Left==NULL||Left==Right)
  62. {
  63. printf("队列为空");
  64. return;
  65. }
  66. Node* P=Right;
  67. Right=Right->Pre;
  68. Right->Next=NULL;
  69. free(P);
  70. }
  71. void Show()
  72. {
  73. if(Left==NULL||Left==Right)
  74. {
  75. printf("此表为空");
  76. return;
  77. }
  78. for(Node* i=Left;i!=Right;i=i->Next)
  79. {
  80. printf("%d ",i->Data);
  81. }
  82. printf("\n");
  83. }
  84. int main()
  85. {
  86. InitQueue();
  87. Left_Insert(1);
  88. Left_Insert(2);
  89. Left_Insert(3);
  90. Left_Insert(4);
  91. Left_Insert(5);
  92. Right_Insert(6);
  93. Right_Insert(7);
  94. Show();
  95. Left_Remove();
  96. Right_Remove();
  97. Show();
  98. return 0;
  99. }

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

闽ICP备14008679号