环形队列的介绍和使用

数据结构—循环队列(环形队列) 循环队列选择用数组实现,循环队列需要用一个指针来指向存储队列数据的空间,用两个变量来记录队列中存储数据的起始(对头)和末尾(队尾)的数组位置,最后用一个变量记录队列的存储数据的最大容量。 阅读详情

一、环形队列是什么

环形队列是一种使用固定大小的数组、但逻辑上看起来首尾相连的队列结构。

它的最大特点:

  • 不会移动元素
  • 入队和出队只移动两个指针(front、rear)
  • 操作是 O(1)

极适合作为:缓冲区(Buffer)、生产者-消费者队列、高性能网络收发、音视频流处理、线程间通信等。

二、为什么要用环形队列

普通队列基于数组,会遇到一个问题:
出队时 front++,但前面的空间不能再利用 → 必须移动元素或重新分配。

环形队列的设计目的就是:

  • 避免数组移动
  • 让释放的空间可以再次使用

做到:既高效,又节省空间。

三、核心思想:余数取模 (mod)

假设数组长度为 capacity,队列中使用两个指针:

  • front 指向队头(第一个有效元素)
  • rear 指向队尾下一个可写入的位置

移动方式:

  • rear = (rear + 1) % capacity
  • front = (front + 1) % capacity

比如容量为 5:

索引:0 1 2 3 4  (length=5)

若 rear 从 4 再加 1:

(4 + 1) % 5 = 0

存储结构示意图:
在这里插入图片描述

四、环形队列的两个重要规则

判断队列是否为空

front == rear

判断队列是否满?
当 rear 的下一个位置是 front 时,队列为满。

(rear + 1) % capacity == front

队列最多容纳 capacity - 1 个元素。

五、核心操作(以浪费一个空间的经典方案为例)

1. 入队(enqueue)

if (满) return false;
buffer[rear] = value;
rear = (rear + 1) % capacity;

2. 出队(dequeue)

if (空) return false;
value = buffer[front];
front = (front + 1) % capacity;

完整示例:

#include <iostream>

using namespace std;

class Queue {
public:
	Queue(int size = 10) 
		: cap_(size)
		, front_(0)
		, rear_(0)
		, size_(0)
	{
		pQue_ = new int[cap_];
	}

	~Queue()
	{
		delete[] pQue_;
		pQue_ = nullptr;
	}

	// 入队
	void push(int val)
	{
		if ((rear_ + 1) % cap_ == front_) {
			// 扩容
			expand(2 * cap_);
		}

		pQue_[rear_] = val;
		rear_ = (rear_ + 1) % cap_;
		size_++;
	}

	// 出队
	void pop()
	{
		if (front_ == rear_) {
			throw "queue is empty!";
		}

		front_ = (front_ + 1) % cap_;
		size_--;
	}

	// 队头元素
	int front() const 
	{
		if (front_ == rear_) {
			throw "queue is empty!";
		}

		return pQue_[front_];
	}

	// 队尾元素
	int back() const
	{
		if (front_ == rear_) {
			throw "queue is empty!";
		}

		return pQue_[(rear_ - 1 + cap_) % cap_];
	}

	// 队空
	bool empty() 
	{
		return front_ == rear_;
	}

	// 队列元素个数
	int size() const
	{
		return size_;

#if 0
		// 遍历一遍统计队列元素个数 O(n)
		int size = 0;
		for (int i = front_; i != rear_; i = (i + 1) % cap_) {
			size++;
		}

		return size;
#endif
	}

private:
	//  扩容接口
	void expand(int size)
	{
		int* p = new int[size];

		int i = 0;
		int j = front_;
		for (; j != rear_; i++, j = (j + 1) % cap_) {
			p[i] = pQue_[j];
		}

		delete pQue_;
		pQue_ = p;
		cap_ = size;
		front_ = 0;
		rear_ = i;
	}

private:
	int* pQue_;
	int cap_;		// 空间容量
	int front_;		// 队头
	int rear_;		// 队尾
	int size_;		// 队列元素个数
};

int main()
{
	int arr[] = { 12, 4, 56, 7, 89, 31, 53, 75 };
	Queue que;

	for (int v : arr) {
		que.push(v);
	}

	cout << que.front() << endl;
	cout << que.back() << endl;

	que.push(100);
	que.push(200);
	que.push(300);

	cout << que.front() << endl;
	cout << que.back() << endl;

	while (!que.empty()) {
		cout << que.front() << " " << que.back() << endl;
		
		que.pop();
	}
}

输出结果:
在这里插入图片描述

六、常见示例

使用两个栈实现一个队列

用两个栈 inStack 和 outStack:

1. 入队(push/enqueue)
所有数据 直接入 inStack:

inStack.push(x);

2. 出队(pop/dequeue)
当需要出队时:

  • 如果 outStack 不为空:直接 pop
  • 如果 outStack 为空:把 inStack 所有元素倒入 outStack
while (!inStack.empty()) {
    outStack.push(inStack.top());
    inStack.pop();
}

倒完后:

  • outStack.top() 就是队列队头
  • outStack.pop() 完成出队

为什么这样能实现队列?
栈是后进先出(LIFO);队列是先进先出(FIFO)
将栈 A 的数据全部倒到栈 B,会把顺序反转:

入队顺序:1, 2, 3
inStack:top → 3,2,1

倒入 outStack:

outStack:top → 1,2,3

此时 outStack 的 top 就是队列的队头。

完整实现:

#include <stack>
#include <stdexcept>

class QueueWithTwoStacks {
public:
    void push(int x) {
        inStack.push(x);
    }

    int pop() {
        if (empty()) {
            throw std::runtime_error("Queue is empty");
        }
        moveIfNeeded();
        int value = outStack.top();
        outStack.pop();
        return value;
    }

    int front() {  // 等同于队头 peek
        if (empty()) {
            throw std::runtime_error("Queue is empty");
        }
        moveIfNeeded();
        return outStack.top();
    }

    bool empty() const {
        return inStack.empty() && outStack.empty();
    }

private:
    std::stack<int> inStack;
    std::stack<int> outStack;

    void moveIfNeeded() {
        if (outStack.empty()) {
            while (!inStack.empty()) {
                outStack.push(inStack.top());
                inStack.pop();
            }
        }
    }
};

使用示例:

QueueWithTwoStacks q;

q.push(10);
q.push(20);
q.push(30);

std::cout << q.pop() << std::endl; // 10
std::cout << q.front() << std::endl; // 20
q.push(40);
std::cout << q.pop() << std::endl; // 20

时间复杂度分析(关键)
在这里插入图片描述
关键点:倒数据的过程只会发生在 outStack 为空时
数据被倒过去以后,不会再倒回来,所以总体均摊下来每个元素只被“倒”一次。所以队列所有操作都是均摊 O(1)。

图示(原理可视化)
队列元素顺序:[1, 2, 3]

(1)入队

inStack:  top → 3,2,1
outStack: (empty)

(2)第一次出队时,outStack 为空 → 倒数据

inStack:  (empty)
outStack: top → 1,2,3

(3)后续的出队直接从 outStack 弹出
用两个栈实现一个队列:

  1. 入队:入 inStack
  2. 出队:如果 outStack 为空,则把 inStack 全倒进去
  3. 队头在 outStack.top()
  4. 所有操作均摊 O(1)

使用两个队列实现一个栈

核心思想:

  • 栈(stack)是:后进先出(LIFO)
  • 队列(queue)是:先进先出(FIFO)

要用两个队列实现栈,就必须想办法让“最后入的元素”成为队列的“队头”。

方法一:push O(n),pop O(1)(最常见的方案)
通过让最新入栈的元素始终保持在队列队头来实现栈。只使用一个主队列 q1,另一个 q2 用于辅助倒数据。

核心思想
push(x):

  1. 先把 x 放入 q2
  2. 把 q1 中所有元素搬到 q2 中(保持顺序反转)
  3. 交换 q1 和 q2

此时:

q1 的 front 就是最新压入的元素(top)

pop:q1.front() 就是栈顶元素,直接 pop 即可。

时间复杂度
在这里插入图片描述
方法一:C++ 完整实现

#include <queue>
#include <stdexcept>

class StackWithTwoQueues {
public:
    void push(int x) {
        q2.push(x);

        // 把 q1 所有元素倒进 q2
        while (!q1.empty()) {
            q2.push(q1.front());
            q1.pop();
        }

        // 交换 q1 和 q2,使 q1 始终为主队列
        std::swap(q1, q2);
    }

    int pop() {
        if (q1.empty()) {
            throw std::runtime_error("Stack is empty");
        }
        int value = q1.front();
        q1.pop();
        return value;
    }

    int top() {
        if (q1.empty()) {
            throw std::runtime_error("Stack is empty");
        }
        return q1.front();
    }

    bool empty() const {
        return q1.empty();
    }

private:
    std::queue<int> q1, q2;
};

图示理解
假设依次 push:1、2、3
栈顶应该是 3。

push(1)

q1: [1]

push(2)

q2: [2]
q1: [1] → 倒入 q2 → q2: [2,1]

swap(q1, q2)

q1: [2,1]

push(3)

q2: [3]
q1: [2,1] → q2: [3,2,1]

swap → q1: [3,2,1]

此时:

栈顶 = q1.front() = 3

pop 时直接 q1.pop() 即可,非常快。

方法二:push O(1),pop O(n)
思路反过来:

  • push 直接入队到 q1
  • pop 时为了拿到最后入队的元素,需要将前 n-1 个元素转移到 q2

pop 步骤:

  1. 把 q1 的前 n-1 个元素全部转移到 q2
  2. 剩下的就是栈顶元素 → pop 掉
  3. swap(q1, q2)

时间复杂度
在这里插入图片描述
方法二 C++ 实现

class StackWithTwoQueues {
public:
    void push(int x) {
        q1.push(x);
    }

    int pop() {
        if (q1.empty()) {
            throw std::runtime_error("Stack is empty");
        }

        // 留下最后一个
        while (q1.size() > 1) {
            q2.push(q1.front());
            q1.pop();
        }

        int value = q1.front();
        q1.pop();
        std::swap(q1, q2);
        return value;
    }

    int top() {
        if (q1.empty()) {
            throw std::runtime_error("Stack is empty");
        }

        // 和 pop 类似,但不删除最后一个
        while (q1.size() > 1) {
            q2.push(q1.front());
            q1.pop();
        }

        int value = q1.front();
        q2.push(value);  // 回填
        q1.pop();
        std::swap(q1, q2);
        return value;
    }

    bool empty() const {
        return q1.empty();
    }

private:
    std::queue<int> q1, q2;
};

对比:两种方案差别
在这里插入图片描述

【数据结构与算法】环形队列的介绍、用数组实现环形数组 队列是一个有序列表,遵循先入先出的原则。可以用数组或链表来实现而环形队列,通过取模的方式,将数组看做是一个环形的。 阅读详情

相关推荐

golang环形队列实现

Table of Contents 什么是环形队列 实现环形队列图示过程 golang版代码实现过程 参考全部代码 什么是环形队列 在一个指定大小的数组里循环写入数据,借用二个指针分别实现入队标记与出队标记.也体现了指针的大好用处,请深入体会.大有裨益. 如图所示,一个环形队列.含有二个指针:队列头指针,队列尾指针. 实现环形队列图示过程 初始化一个数组大小为6的环形...

weixin_41745285的博客 540

环形队列详解

队列是一种线性数据结构,它遵循先进先出或后近后出的原则。队列允许在一端插入元素,另一端删除元素。队列有时也被称为待办事项列表,尤其是在计算机程序中,它被广泛用于任务排队、缓存和异步处理方案中。环形队列,它可以在固定大小的数组中循环使用。它类似于普通队列,但与普通队列不同的是,在环形队列中,队列的队尾可以接到数组的开头,使数组像一个圆环一样循环。因此,环形队列往往比普通队列的效率更高,尤其是在实现循环缓冲区等应用时。环形队列主要的操作有入队和出队。int front;int rear;

qq_54938554的博客 4691

泛微ecology9二次开发-附件上传-安全实践

本文深入探讨了泛微ecology9二次开发中附件上传功能的安全实践。针对原生接口的安全机制,文章提出了同源部署与服务端代理转发两大核心策略,并提供了基于Vue3和Element Plus的完整前端实现代码,重点解决了会话验证、权限控制及文件关联等关键安全问题,确保二次开发应用能安全、稳定地与ecology9系统集成。

qsc9012345的博客 530

【数据结构】——环形队列

数据结构环形队列

如果需要沟通,可添加微信公众号找我免费答疑 6229

使用STM32F103C8T6控制舵机丝滑运转附代码

使用STM32F103C8T6控制舵机丝滑运转原理和案例,附代码。

m0_63566592的博客 4237

图形学基础|屏幕空间反射(SSR)

图形学基础|屏幕空间反射(SSR) 文章目录图形学基础|屏幕空间反射(SSR)一、前言二、反射技术概述2.1 环境贴图反射2.2 IBL反射2.3 平面反射(Planar Reflections)2.4 屏幕空间反射(Screen Space Reflection)2.4.1 SSR的基本原理2.4.2 SSR的优缺点三、SSR(Screen-Space Reflection)实现3.1 Efficient GPU Screen-Space Ray Tracing3.1.1 2D Raymarching v

【黑键】 3万+

环形队列原理,全网最通俗易懂

队列是一种很常见的数据结构,满足先进先出的方式,如果我们设定队列的最大长度,那就意味着进队列和出队列的元素的数量实则满足一种动态平衡。如果我们把首次添加入队列的元素作为一个一维坐标的原点,那么随着队列中元素的添加,坐标原点到队尾元素的长度会无穷无尽的增大,随这之前添入的元素不断出列,对头对应的下标点也在不断增大。这样,进队列和出队列的元素的数量就对应到对头和队尾下标点的移动因此我们评判一个队列长度是否溢出原先约定的最大长度,实则就是在评判队尾坐标点与队头坐标点之间的差值,无论是出队列还是入队列,队头和队尾的

qq_40979103的博客 9383

面试提问:数据开发中如何通过指标拆解来指导SQL编写?(附拆解模板)

摘要:指标拆解是一种高效的数据分析方法,通过将复杂业务指标分解为可执行的原子指标,指导SQL查询的编写。核心步骤包括:明确指标定义、拆解原子指标、定位数据源、确定分组维度、构建中间表。文章提供了指标拆解模板和实际案例(如用户留存率计算),并总结了拆解带来的5大好处:逻辑清晰、易于调试、可复用、降低错误率、便于协作。最后强调"一定义、二拆解、三定位、四分步、五合并"的口诀,适用于数仓开发、BI报表等场景。

石榴姐yyds 266

【数据结构(C语言描述)】环形队列

环形队列的数组和链表实现以及对应的接口

4617

因果强化学习:优化决策策略的因果方法

1. 背景介绍 1.1 传统强化学习的局限性 强化学习(Reinforcement Learning, RL)是一种通过与环境交互来学习最优决策策略的方法。然而,传统强化学习方法在面对复杂、动态和不确定的现实世界问题时,往往存在以下局限性: 数据效率低:传统强化学习方法

AI天才研究院 655

RAP 时代的 Service Binding:把 CDS 业务服务稳稳地“插”到 OData 里

本文深入解析SAP ABAP开发中的关键对象Service Binding,阐述了如何通过三层架构(数据模型、服务定义、服务绑定)实现业务逻辑与协议分离。文章重点分析了Service Binding的核心参数配置,包括服务命名规范、协议类型选择(OData V2/V4、InA、SQL)及服务形态(UI Service与Web API),并通过实际案例说明不同选项的应用场景。同时介绍了服务版本控制机制、发布流程及Fiori预览功能,帮助开发者理解如何通过Service Binding将业务能力高效转化为可消费

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 5710

单片机实现环形队列_队列的介绍和利用环形队列实现STM32进阶之串口环形缓冲区的概述...

队列的概念在此之前,我们来回顾一下队列的基本概念:队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入(入队),在另一端进行删除(出队)。队列的特点类似售票排队窗口,先到的人看到能先买到票,然后先走,后来的人只能后买到票队列的常见两种形式普通队列在计算机中,每个信息都是存储在存储单元中的,比喻一下吧,上图的一些小正方形格子就是一个...

weixin_39627390的博客 484

基于YOLOv11的无人机目标检测系统:实现drone、bird、aircraft多类别识别

采用改进的YOLOv11网络结构,平衡检测精度和速度融合VisDrone和UAVDT数据集,增强模型泛化能力设计多尺度特征融合模块,提升小目标检测性能开发直观的UI界面,便于实际部署和应用。

YOLO 322

串口环形队列的应用

串口环形队列的应用 --Written by SimonLien 2020.05.25(转载请注明出处) 在项目的开发过程中,由于有些串口不具备FIFO或者FIFO的buffer很小,我们经常会自己设计一个队列(Queue)来避免串口...

u010516347的专栏 3328

104、母线电容设计与选型

母线电容选型,容值不是最关键的,纹波电流和温度才是。很多炸机事故,不是容值不够,而是纹波电流超标导致过热。选电容时,多花点时间算纹波电流,比盲目加大容值有效得多。另外,别迷信进口品牌。国产电容(如江海、艾华)在常规应用里完全够用,价格只有进口的1/3。但高频特性确实有差距,所以高频去耦一定要用薄膜电容。最后,留个调试技巧:用示波器测母线电容两端的电压纹波,同时用电流探头测电容的纹波电流。如果纹波电流波形有尖峰,说明ESL太大,需要并联高频电容。如果纹波电压是低频波动,说明容值不够。

2401_88858637的博客 46

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

数据结构--环形队列的介绍与实现

数据结构--环形队列实现一、环形队列实现原理环形队列的几个判断条件二、代码实现1.环形队列类(CircleQueue)2.环形队列类测试类3.程序运行结果4.完整代码 环形队列可以用数组实现,也可以使用循环链表实现.在使用数组实现循环队列的时候,需要理解清楚队列头和队列尾的判断空还是满的处理方法; 环形队列特点: 避免假溢出现象(由于入队和出队的操作,头尾指针只增加不减少,致使被删元素的空间永远无法重新利用,当队列继续存储元素时,出现尾指针已经到达了队列尾,而实际头指针前面并未满的情况),可以将队列空间充

凌云 1万+

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

环形队列介绍及用java中的数组实现环形队列

一、环形队列的介绍 可以将队列空间想象成一个 环形空间 ,这能使队列空间重复使用:无论插入还是删除,front(队头) 增1或 rear(队尾) 加1时超出所分配的空间,就让它指向这片连续空间的起始地址。取元素时先从队列头开始,取完后,下一个元素就成了队列头,依次循环。只有一个元素时,既是队列头,也是队列尾。 二、环形队列图示 三、用数组模拟环形队列思路 四、编写java代码实现环形队列 4...

a1786742005的博客 1182

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

环形队列实现原理

环形队列是在实际编程极为有用的数据结构,它有如下特点。   它是一个首尾相连的FIFO的数据结构,采用数组的线性空间,数据组织简单。能很快知道队列是否满为空。能以很快速度的来存取数据。   因为有简单高效的原因,甚至在硬件都实现了环形队列.    环形队列广泛

Kfree博客园 4万+

数据结构 - 队列(非环形队列,以及优化成环形队列)

1)队列的定义与实现形式-方式 2)队列实现思路(非环形,下面进行优化) 3)代码实现(注意并不是环形) package DataStructures.queue; import java.util.Scanner; /** * 使用数组模拟队列 */ public class ArrayQueueDemo { public static void main(String []...

chun的博客 2155

freertos中,队列和环形缓冲区有什么关系,该如何使用它们,使用队列必须使用环形缓冲区吗

**队列基于环形缓冲区实现**:FreeRTOS的队列底层使用了环形缓冲区的思想,但队列是一个更高层次的抽象,提供了线程安全和阻塞操作。// 缓冲区满,覆盖旧数据。- **队列已经封装了环形缓冲区的功能**:FreeRTOS队列内部使用了类似环形缓冲区的机制来存储数据,但用户无需手动实现。在FreeRTOS中,**队列**和**环形缓冲区**是两种不同的数据存储和传递机制,它们有各自的用途和特点。- **队列是线程安全的**:FreeRTOS队列内置了同步机制,适合任务之间的数据传递。

xiaojin511025的博客 948

项目框架介绍,环形队列设计

如果是发布, 则需要根据主题在 hashmap 中查询到订阅进程的 id,从发布消息进程获。消息发布者 (商家) 在公众号上发布相应的内容,并不关注具体的消息接收者 (消费者)消息发布者负责发布消息,是基于消息主题 (类别) 将发布的内容传输到消息服务器。消息的发布者与消息订阅者解耦,即消息发布者不⽤关⼼订阅者,订阅者也不⽤关⼼。消息订阅者负责接收消息,是基于消息主题 (类别)从消息服务器接收消息。是所有的关注公众号的消息接受者 (消费者) 都可以在公众号中看到消息。

interese的博客 798

数组模拟环形队列 |Java实现

文章目录队列的应用场景与介绍数组模拟队列 队列的应用场景与介绍 队列是一个有序列表,可以用数组或者链表来实现。 遵循先入先出的原则。即先存入队列的数据要先取出,后存入队列的数据要后取出。 队列示意图(使用数组模拟) 应用场景:银行挂号系统。 当有一个服务台的业务员为一个客户服务完毕后,就会依次叫号。 数组模拟队列 数组模拟队列思路分析 设计队列时,maxSize为队列最大容量;由...

匿名攻城狮 952

环形队列的使用

前面讲到了的使用,我们能会发先当队尾指针加到和队列的大小相同时,就无法再继续存放数据,而此时队头指针持续取出直到队头指针和队尾指针相同时,就判断队满了。但此时下面队列下面还是有没使用的部分,出现了的情况(如上一篇文章中插图的图d)。为了避免假溢出这一情况,引入了环形队列。环形队列的初始化和销毁等和普通队列大致相同(初始化队列时无需指向-1),不同的是对队头指针和队尾指针的操作。

2201_75493615的博客 464

数组模拟队列-环形队列

餐厅排队,去银行排队。先进先出,即先存入的数据先取出。

m0_64487275的博客 1716

Java数据结构:使用数组模拟队列(队列与环形队列)

本文介绍了队列这一数据结构,并通过数组的方式分别实现了队列与环形队列。环形队列解决了队列无法复用的问题。文中附有实现的完整代码,供读者学习使用。

黄小黄的博客 1054

如何用数组实现环形队列

上次实现了一个普通队列,但是普通队列存在的问题就是队列是一次性的,并不能够重复利用,所以这次来写环形队列,实现队列的复用。 环形队列 环形队列要比普通的队列复杂一些,要对原来的普通队列做一些调整。 首先头指针front要做一些变化,原本的front = -1,此时要让front指向队列的第一个元素也就是arr[front]就是队列的第一个元素,front的初始值变为了0。 尾指针rear也要进行一些改变,要让rear指向最后一个元素的后一个位置,因为希望空出一个空间作为约定,rear的初始值也为0。

欢迎留言技术探讨 381
上一篇: MVP架构的介绍和使用
下一篇: 排序算法:冒泡排序算法
TechNomad
博客等级 码龄2年 787粉丝 183原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值