探索数据结构,链式队与循环队列的模拟、实现与应用,数据结构深度探索,链式队列与循环队列的模拟、实现及应用

马肤

温馨提示:这篇文章已超过466天没有更新,请注意相关的内容是否还可用!

摘要:本文探讨了数据结构中链式队列和循环队列的模拟、实现与应用。文章介绍了这两种数据结构的基本概念,详细阐述了它们的模拟方法和实现过程,并探讨了它们在各种场景中的应用。通过模拟和实现链式队列和循环队列,可以更好地理解队列操作的基本原理和特点,为实际开发中高效利用队列数据结构提供了重要参考。

1. 队列的定义

队列(queue)是一种只允许在一端进行插入操作,而在另一端进行删除操作的线性表。其严格遵循先进先出(First In First Out)的规则,简称FIFO。

探索数据结构,链式队与循环队列的模拟、实现与应用,数据结构深度探索,链式队列与循环队列的模拟、实现及应用 第1张

探索数据结构,链式队与循环队列的模拟、实现与应用,数据结构深度探索,链式队列与循环队列的模拟、实现及应用 第2张

  • 队头(Front):允许删除的一端,又称队首。
  • 队尾(Rear):允许插入的一端。

    2. 队列的分类

    队列与栈类似,实现方式有两种。一种是以数组的方式实现,另一种以单链表来实现。这两种实现方式各有优劣,并且都有细节需要处理。

    1. 基于单链表实现:我们可以将链表的头节点与尾节点分别作为队列的队首与队尾,这样我们就能用两个指针来对其进行操作。如下图:

    探索数据结构,链式队与循环队列的模拟、实现与应用,数据结构深度探索,链式队列与循环队列的模拟、实现及应用 第3张

    1. 基于数组实现:我们同样可以通过两个下标分别指向数组的起始与结束,但这时我们就可能发现两个问题:
    • 问题一:在不断出队与进队得到过程中,起始下标与末尾下标都在向后移动,当两个下标同时指向数组末尾时就无法再移动了,并且**浪费前面大量空间,**如图1

    • 问题二:为了解决上述问题,我们将数组首尾相接变为循环数组。但这时又会出现一个问题,那便是当队首与队尾下标指向同一个节点时,这个队列到底是空还是满呢?这时我们有三个解决方法

    • 第一种:牺牲一个单元来区分队空和队满,这时若队列不为空,让队尾下标指向队尾的下一个位置。约定以队头指针在队尾指针的下一位置作为队满的标志,即Q->rear+1==Q->front。如图二。

    • 第二种:增设表示元素个数的数据成员 size 。这样,队空的条件为 Q->size==0;队满的条件为 Q->size==MaxSize。

    • 第三种:增加表示队满的数据成员flag。将flag初始化为0,当队满时将其置为1。

      探索数据结构,链式队与循环队列的模拟、实现与应用,数据结构深度探索,链式队列与循环队列的模拟、实现及应用 第4张

      3. 队列的功能

      1. 队列的初始化。
      2. 判断队列是否为空。
      3. 判断队列是否满了。
      4. 返回队头与队尾的元素。
      5. 返回队列的大小。
      6. 入队与出队。
      7. 打印队列的元素。
      8. 销毁队列。

      4. 队列的声明

      4.1. 链式队

      链式队的声明十分简单,参照上面图我们就可以直接实现了。

      typedef int QDataType;
      typedef struct QueueNode 
      {
      	QDataType data;
      	struct QueueNode* next;
      }QNode;
      typedef struct Queue 
      {
      	QNode* front;
      	QNode* rear;
      	size_t size;
      }Queue;
      

      4.2. 循环队

      根据上述分析,我们采用一个数组来实现队列,其声明如下

      typedef int QDataType;
      #define MAXSIZE 50  //定义元素的最大个数
      /*循环队列的顺序存储结构*/
      typedef struct {
          QDataType *data;
          int front;  //头指针
          int rear;   //尾指针
      }Queue;
      

      5. 队列的初始化

      对队列声明的数据进行初始化,防止随机值。

      5.1. 链式队

      void QueueInit(Queue* q)
      {
      	q->front = NULL;
      	q->rear = NULL;
      	q->size = 0;
      }
      

      5.2. 循环队

      void QueueInit(Queue* q) 
      {
          q->data = (QDataType*)malloc(sizeof(QDataType )* MAXSIZE);
      	if (q->data == NULL)
      	{
      		perror("malloc fail:");
      		return;
      	}
          q->front = 0;
          q->rear = 0;
      }
      

      5.3. 复杂度分析

      • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
      • 空间复杂度:链式队空间是一个固定大小,空间复杂度为O(1)。而需要开辟整个队列的大小,空间复杂度为O(N)。

        6. 判断队列是否为空

        判断队列是否为空十分简单,这里就不在赘述。

        6.1. 链式队

        bool QueueEmpty(Queue* q)
        {
        	assert(q);
        	return (q->front == NULL) && (q->rear == NULL);
        }
        

        6.2. 循环队

        bool QueueEmpty(Queue*q)
        {
            assert(q);
            return q->front == q->rear;
        }
        

        6.3. 复杂度分析

        • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
        • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

          7. 判断队列是否为满

          7.1. 链式队

          链式队不需要判断是否为满的情况。

          7.2. 循环队

          为了完成循环操作,需要对其进行取模操作,防止越界。

          bool QueueFull(Queue*q)
          {
              assert(q);
              return q->front == (q->rear+1)%MAXSIZE;
          }
          

          7.3. 复杂度分析

          • 时间复杂度:循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
          • 空间复杂度:循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

            8. 返回队头与队尾元素

            因为定义了头指针与尾指针,所以访问数据也十分方便。

            8.1. 链式队

            QDataType QueueFront(Queue* q)
            {
            	assert(q);
            	assert(!QueueEmpty(q));
            	return q->front->data;
            }
            QDataType QueueBack(Queue* q)
            {
            	assert(q);
            	assert(!QueueEmpty(q));
            	return q->rear->data;
            }
            

            8.2. 循环队

            rear下标可能指向下标0,这时贸然减一就可能出现越界的情况。为了解决这个问题我们可以加上MAXSIZE再对其取模。

            QDataType QueueFront(Queue* q)
            {
                assert(q);
                assert(!QueueEmpty(q));
                return q->data[q->front];
            }
            QDataType QueueBack(Queue* q)
            {
                assert(q);
                assert(!QueueEmpty(q));
                return q->data[(q->rear-1+MAXSIZE)%MAXSIZE];
            }
            

            8.3. 复杂度分析

            • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
            • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

              9. 队列的大小

              9.1. 链式队

              size_t QueueSize(Queue* q)
              {
              	return q->size;
              }
              

              9.2. 循环队

              求循环队列的大小,我们很容易想到用Q->rear-Q->front得出队列元素个数。但是我们要考虑到一种特殊情况:当队列先删除元素再添加元素时,末尾下标**rear**可能循环重置,如下图。

              探索数据结构,链式队与循环队列的模拟、实现与应用,数据结构深度探索,链式队列与循环队列的模拟、实现及应用 第5张

              那到底该如何解决这个问题呢?其实我们只需要在原来基础上加上一个MAXSIZE就行了,为了使图一情况也适用我们仍需模上一个MAXSIZE。

              size_t QueueSize(Queue*q) 
              {
                  assert(q);
                  return (q->rear - q->front + MAXSIZE) % MAXSIZE;
              }
              

              9.3. 复杂度分析

              • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
              • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

                10. 入队

                10.1. 链式队

                链式队列入队时需要判断队列是否为空的特殊情况,如果是则还需要将尾指针也指向这个节点。

                void QueuePush(Queue* q, QDataType x)
                {
                	assert(q);
                	QNode* newnode = (QNode*)malloc(sizeof(QNode));
                	newnode->data = x;
                	newnode->next = NULL;
                	if (newnode == NULL)
                	{
                		perror("malloc fail");
                		return;
                	}
                	if (q->front == NULL)
                	{
                		q->front = q->rear = newnode;
                	}
                	else
                	{
                		q->rear->next = newnode;
                		q->rear = newnode;
                	}
                	q->size++;
                }
                

                10.2. 循环队

                为了使循环队列在插入数据时实现循环操作,我们可以每次进行取模操作。并且每次入队之前要检查循环队是否已满。

                void QueuePush(Queue* q, QDataType x) 
                {
                    assert(q);
                    if(QueueFull)
                    {
                        printf("队列已满\n");
                        return ;
                    }
                    q->data[q->rear] = x;   
                    q->rear = (q->rear + 1) % MAXSIZE;  //rear指针向后移一位置,若到最后则转到数组头部
                }
                

                10.3. 复杂度分析

                • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
                • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

                  11. 出队

                  11.1. 链式队

                  同样考虑特殊情况,防止队列为空。并且当队列只有一个节点时需要将头指针与尾指针都置为空。

                  void QueuePop(Queue* q)
                  {
                  	assert(q);
                  	assert(!QueueEmpty(q));
                  	//1.只有一个结点
                  	if (q->front == q->rear)
                  	{
                  		free(q->front);
                  		q->front = q->rear = NULL;
                  	}
                  	//2.有多个结点
                  	else
                  	{
                  		QNode* del = q->front;
                  		q->front = q->front->next;
                  		free(del);
                  		del = NULL;
                  	}
                  	q->size--;
                  }
                  

                  11.2. 循环队

                  同样为了实现循环,我们可以进行取模操作。

                   void QueuePop(Queue* q)
                   {
                       assert(q);
                       assert(!QueueEmpty(q));
                      q->front = (q->front + 1) % MAXSIZE;    //front指针向后移一位置,若到最后则转到数组头部
                   }
                  

                  11.3. 复杂度分析

                  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
                  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

                    12. 打印队列

                    12.1. 链式队

                    void QueuePrint(Queue* q)
                    {
                    	assert(q);
                    	QNode* cur = q->front;
                    	QNode* tail = q->rear;
                    	printf("队头->");
                    	while (cur != tail->next)
                    	{
                    		printf("%d->",cur->data);
                    		cur = cur->next;
                    	}
                    	printf("队尾\n");
                    }
                    

                    12.2. 循环队

                     void QueuePrint(Queue* q)
                     {
                         assert(q);
                         int cur = q->front;
                         printf("队头->");
                         while (cur != q->rear)
                         {
                             printf("%d->", q->data[cur]);
                             cur = (cur + 1) % MAXSIZE;
                         }
                         printf("队尾\n");
                     }
                    

                    12.3. 复杂度分析

                    • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
                    • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

                      13. 销毁队列

                      13.1. 链式队

                      void QueueDestroy(Queue* q)
                      {
                      	assert(q);
                      	QNode* cur = q->front;
                      	while (cur)
                      	{
                      		QNode* del = cur;
                      		cur = cur->next;
                      		free(del);
                      		del = NULL;
                      	}
                      	q->front = q->rear = NULL;
                      }
                      

                      13.2. 循环队

                      void QueueDestroy(Deque* q)//销毁队列
                      {
                      	assert(q);
                      	free(q->data);
                      	q->data = NULL;
                      	q->front = q->rear = 0;
                      }
                      

                      13.3. 复杂度分析

                      • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
                      • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

                        14. 链式队与循环队列的对比与应用

                        14.1. 对比

                        对比项链式队循环队列
                        时间效率因为存在头指针与尾指针,所以链式队的出队与入队的时间都相对较小。循环队列是基于数组实现的,支持下标的随机访问,所以时间消耗也并不大
                        空间效率链式队每次入队都需固定创造一个新的节点,空间利用率较高,较稳定。循环队列的空间是固定的,可能会造成空间的浪费。

                        14.2. 应用

                        队列的应用与栈一样,十分广泛

                        1. 当我们去食堂扫码订餐时,你的订单就会加入一个队列中。
                        2. 在操作系统中,队列可以用来管理任务进度与进程切换。

                        15. 完整代码

                        15.1. 链式队

                        15.1.1. Queue.h
                        #include
                        #include
                        #include
                        #include
                        typedef int QDataType;
                        typedef struct QueueNode 
                        {
                        	QDataType data;
                        	struct QueueNode* next;
                        }QNode;
                        typedef struct Queue 
                        {
                        	QNode* front;
                        	QNode* rear;
                        	size_t size;
                        }Queue;
                        void QueueInit(Queue* q);//初始化队列
                        bool QueueEmpty(Queue* q);//判断是否为空
                        QDataType QueueFront(Queue* q);//获取队头元素
                        QDataType QueueBack(Queue* q);//或许队尾元素
                        size_t QueueSize(Queue* q);//或许队列长度
                        void QueuePush(Queue* q, QDataType x);//入队
                        void QueuePop(Queue* q);//出队
                        void QueuePrint(Queue* q);//打印队列元素
                        void QueueDestroy(Queue* q);//销毁队列
                        
                        15.1.2. Queue.c
                        #include"Queue.h"
                        void QueueInit(Queue* q)
                        {
                        	q->front = NULL;
                        	q->rear = NULL;
                        	q->size = 0;
                        }
                        bool QueueEmpty(Queue* q)
                        {
                        	assert(q);
                        	return (q->front == NULL) && (q->rear == NULL);
                        }
                        QDataType QueueFront(Queue* q)
                        {
                        	assert(q);
                        	assert(!QueueEmpty(q));
                        	return q->front->data;
                        }
                        QDataType QueueBack(Queue* q)
                        {
                        	assert(q);
                        	assert(!QueueEmpty(q));
                        	return q->rear->data;
                        }
                        size_t QueueSize(Queue* q)
                        {
                        	return q->size;
                        }
                        void QueuePush(Queue* q, QDataType x)
                        {
                        	assert(q);
                        	QNode* newnode = (QNode*)malloc(sizeof(QNode));
                        	newnode->data = x;
                        	newnode->next = NULL;
                        	if (newnode == NULL)
                        	{
                        		perror("malloc fail");
                        		return;
                        	}
                        	if (q->front == NULL)
                        	{
                        		q->front = q->rear = newnode;
                        	}
                        	else
                        	{
                        		q->rear->next = newnode;
                        		q->rear = newnode;
                        	}
                        	q->size++;
                        }
                        void QueuePop(Queue* q)
                        {
                        	assert(q);
                        	assert(!QueueEmpty(q));
                        	//1.只有一个结点
                        	if (q->front == q->rear)
                        	{
                        		free(q->front);
                        		q->front = q->rear = NULL;
                        	}
                        	//2.有多个结点
                        	else
                        	{
                        		QNode* del = q->front;
                        		q->front = q->front->next;
                        		free(del);
                        		del = NULL;
                        	}
                        	q->size--;
                        }
                        void QueuePrint(Queue* q)
                        {
                        	assert(q);
                        	QNode* cur = q->front;
                        	QNode* tail = q->rear;
                        	printf("队头->");
                        	while (cur != tail->next)
                        	{
                        		printf("%d->",cur->data);
                        		cur = cur->next;
                        	}
                        	printf("队尾\n");
                        }
                        void QueueDestroy(Queue* q)
                        {
                        	assert(q);
                        	QNode* cur = q->front;
                        	while (cur)
                        	{
                        		QNode* del = cur;
                        		cur = cur->next;
                        		free(del);
                        		del = NULL;
                        	}
                        	q->front = q->rear = NULL;
                        }
                        

                        15.2. 循环队列

                        15.2.1. Queue.h
                        #include
                        #include
                        #include
                        #include
                        typedef int QDataType;
                        #define MAXSIZE 50  //定义元素的最大个数
                        /*循环队列的顺序存储结构*/
                        typedef struct {
                            QDataType *data;
                            int front;  //头指针
                            int rear;   //尾指针
                        }Queue;
                        void QueueInit(Queue* q);//初始化队列
                        bool QueueEmpty(Queue* q);//判断是否为空
                        bool QueueFull(Queue*q);//判断队列是否满
                        QDataType QueueFront(Queue* q);//获取队头元素
                        QDataType QueueBack(Queue* q);//或许队尾元素
                        size_t QueueSize(Queue* q);//或许队列长度
                        void QueuePush(Queue* q, QDataType x);//入队
                        void QueuePop(Queue* q);//出队
                        void QueuePrint(Queue* q);//打印队列元素
                        
                        15.2.2. Queue.c
                        void QueueInit(Queue* q) 
                        {
                            q->data = (QDataType*)malloc(sizeof(QDataType )* MAXSIZE);
                        	if (q->data == NULL)
                        	{
                        		perror("malloc fail:");
                        		return;
                        	}
                            q->front = 0;
                            q->rear = 0;
                        }
                        bool QueueEmpty(Queue*q)
                        {
                            assert(q);
                            return q->front == q->rear;
                        }
                        bool QueueFull(Queue*q)
                        {
                            assert(q);
                            return q->front == (q->rear+1)%MAXSIZE;
                        }
                        QDataType QueueFront(Queue* q)
                        {
                            assert(q);
                            assert(!QueueEmpty(q));
                            return q->data[q->front];
                        }
                        QDataType QueueBack(Queue* q)
                        {
                            assert(q);
                            assert(!QueueEmpty(q));
                            return q->data[(q->rear-1+MAXSIZE)%MAXSIZE];
                        }
                        size_t QueueSize(Queue*q) 
                        {
                            assert(q);
                            return (q->rear - q->front + MAXSIZE) % MAXSIZE;
                        }
                        void QueuePush(Queue* q, QDataType x) 
                        {
                            assert(q);
                            if(QueueFull)//判断队列是否满了
                            {
                                printf("队列已满\n");
                                return ;
                            }
                            q->data[q->rear] = x;   
                            q->rear = (q->rear + 1) % MAXSIZE;  //rear指针向后移一位置,若到最后则转到数组头部
                        }
                         void QueuePop(Queue* q)
                         {
                             assert(q);
                             assert(!QueueEmpty(q));
                            q->front = (q->front + 1) % MAXSIZE;    //front指针向后移一位置,若到最后则转到数组头部
                         }
                         void QueuePrint(Queue* q)
                         {
                             assert(q);
                             int cur = q->front;
                             printf("队头->");
                             while (cur != q->rear)
                             {
                                 printf("%d->", q->data[cur]);
                                 cur = (cur + 1) % MAXSIZE;
                             }
                             printf("队尾\n");
                         }
                        void QueueDestroy(Deque* q)//销毁队列
                        {
                        	assert(q);
                        	free(q->data);
                        	q->data = NULL;
                        	q->front = q->rear = 0;
                        }
                        

0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码