基于数组实现定长队列

本文介绍了如何使用数组实现定长队列,并探讨了高级实现方式,通过缓存行填充避免伪共享,提高效率。详细分析了对象内存布局,并提出进一步优化建议,如填充对象并利用其属性,以及添加日志和事件处理来同步数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,一般实现

有时候我们需要一个定长的队列,超过的数据会挤掉最先添加的数据

这里提供一个技术数组实现

/***
 * data format of offset+separator+operation+separator+identify+separator+key+separator+dataSerial+endMark
 *
 */
public class RaftLeaderAppendData implements FixedLengthStoreQueue{

    private Integer appendArraySize = 1024 ;

    private RaftData[] appendData ;

    private int headIndex = -1 ;
    private long headOffset ;

    public RaftLeaderAppendData(int size){
        this.appendArraySize = size;
        appendData = new RaftData[appendArraySize];
    }

    public RaftLeaderAppendData(){
        appendData = new RaftData[appendArraySize];
    }

    @Override
    public synchronized void append(RaftData raftData){
        Assert.notNull(raftData,"raft data cannot be
<think>我们正在讨论循环队列实现。用户想知道循环队列是否必须基于定长空间实现,以及如何实现动态扩容。根据引用[2]中的信息,循环队列有两种处理队满的方式:1.固定长度,队满则入队失败(处理简单)。2.长度不固定,队满则自动扩容(实现稍微复杂)。因此,循环队列并不必须基于定长空间实现,我们可以实现动态扩容的循环队列。下面我将详细解释循环队列的基本原理以及动态扩容的实现方式。###循环队列的基本概念循环队列是一种顺序存储结构的队列,它使用一个数组和两个指针(队头指针front和队尾指针rear)来实现。为了区分队空和队满,通常会浪费一个存储空间(即当队列中元素个数为数组长度减一时,认为队满)。-队空条件:`front==rear`-队满条件(浪费一个空间时):`(rear+1)%capacity==front`###动态扩容的循环队列队列满时,我们可以进行扩容操作。扩容的基本步骤:1.申请一个更大的数组(通常是原数组的2倍)。2.将原队列中的所有元素复制到新数组中。这里需要注意的是,由于循环队列数组中可能是“环绕”的(即rear可能在front的前面),我们需要按顺序复制:从队头元素开始,直到队尾元素,按队列顺序复制到新数组的0位置开始。3.调整front和rear指针:复制后,front指向新数组的0位置,rear指向最后一个元素的下一个位置(即原队列中元素个数,因为新数组的起始位置是连续的)。4.释放原数组,将队列数组指针指向新数组,并更新队列容量。###动态扩容循环队列实现(C语言)下面我们使用C语言实现一个支持动态扩容的循环队列。首先,定义队列结构体(参考引用[1]):```ctypedefstructSqQueue{int*base;//指向动态分配的数组intfront;//队头指针,指向队头元素intrear;//队尾指针,指向队尾元素的下一个位置intcapacity;//数组的总容量(注意:实际可用容量为capacity-1,因为要浪费一个空间区分队空队满。但动态扩容后,我们也可以改变策略,使用一个额外的标志或者计数来避免浪费空间?不过这里我们仍然使用浪费一个空间的方式,但注意扩容后容量变为原来的2倍,实际可用空间为2*capacity-1?实际上,我们扩容后可用空间是原来的2倍(因为新数组容量是原来的2倍,我们仍然浪费一个空间,所以实际可用空间为2*capacity-1,但这样理解不对,因为扩容后容量是新的,我们重新调整了数组,所以实际可用空间为new_capacity-1)。为了简单,我们仍然采用浪费一个空间的方式。}SqQueue,*PSqQueue;```注意:在动态扩容的实现中,我们增加了`capacity`成员来记录数组的总容量(包括浪费的那个空间)。####初始化队列```cvoidInitQueue(SqQueue*Q,intinit_capacity){Q->base=(int*)malloc(init_capacity*sizeof(int));if(!Q->base)exit(0);//分配失败Q->front=Q->rear=0;Q->capacity=init_capacity;}```####入队操作(带扩容)```c//检查队列是否已满intisFull(SqQueue*Q){return(Q->rear+1)%Q->capacity==Q->front;}//扩容操作voidexpandQueue(SqQueue*Q){intnew_capacity=Q->capacity*2;//扩容为原来的两倍int*new_base=(int*)malloc(new_capacity*sizeof(int));if(!new_base)exit(0);//将原队列中的元素复制到新数组(按顺序,从front开始到rear-1)inti=0;intj=Q->front;while(j!=Q->rear){new_base[i]=Q->base[j];j=(j+1)%Q->capacity;i++;}//复制完成后,新队列的front指向0,rear指向i(即元素个数)free(Q->base);Q->base=new_base;Q->front=0;Q->rear=i;//注意:此时队列中有i个元素,rear指向下一个插入位置(即下标i)Q->capacity=new_capacity;}//入队voidEnQueue(SqQueue*Q,intvalue){if(isFull(Q)){expandQueue(Q);}Q->base[Q->rear]=value;Q->rear=(Q->rear+1)%Q->capacity;}```####出队操作```cintDeQueue(SqQueue*Q,int*value){if(Q->front==Q->rear){return0;//队空}*value=Q->base[Q->front];Q->front=(Q->front+1)%Q->capacity;return1;}```####注意点1.在扩容时,我们按顺序将原队列中的元素复制到新数组的起始位置,这样新队列就不再是循环的(在新数组中是连续存储的),然后我们调整front为0,rear为元素个数(即最后一个元素的下一个位置)。这样,新数组的0到rear-1就是队列的所有元素,然后继续使用循环队列的规则(利用取模运算)进行入队和出队操作。2.由于扩容后数组大小改变,所以取模运算会使用新的capacity。###总结循环队列并不必须基于定长空间实现,我们可以通过动态扩容的方式来实现一个空间可扩展的循环队列。在队满时,我们进行扩容操作(通常扩容为原来的2倍),并将原队列中的元素按顺序复制到新数组中,然后调整队头和队尾指针。这种方法虽然实现稍微复杂,但提高了队列的灵活性,避免了因固定长度导致的入队失败问题。§§相关问题§§1.循环队列的动态扩容会影响时间复杂度吗?如何分析?2.除了动态扩容,还有哪些方法可以避免循环队列的空间浪费?3.循环队列与链式队列在性能和应用场景上有何不同?</think>### 循环队列定长与动态扩容实现 循环队列**不一定必须基于定长空间**实现。根据引用[2]和引用[3],循环队列有两种实现策略: 1. **定长空间实现**:队列容量固定,队满时入队失败(实现简单) 2. **动态扩容实现**:队满时自动扩容(实现更复杂但更实用) --- ### 一、定长空间实现(基础版) **核心特性**: - 预分配固定大小的数组 - 通过`front`和`rear`指针循环移动 - 浪费一个空间区分队空/队满(引用[2]): - 队空条件:`front == rear` - 队满条件:`(rear + 1) % capacity == front` **结构体定义**(引用[1]): ```c typedef struct SqQueue { int *base; // 存储空间基地址 int front; // 队头元素下标 int rear; // 队尾后一位置下标 int capacity; // 队列容量 } SqQueue; ``` **入队操作伪代码**: ```c bool EnQueue(SqQueue *Q, int value) { if ((Q->rear + 1) % Q->capacity == Q->front) return false; // 队满失败 Q->base[Q->rear] = value; Q->rear = (Q->rear + 1) % Q->capacity; return true; } ``` --- ### 二、动态扩容实现(进阶版) **核心思想**(引用[2]): - 队满时自动扩容(通常2倍扩容) - 需处理数据迁移:将循环队列的"环绕数据"拉直存储到新数组 **扩容步骤**: 1. 申请2倍大小的新数组 2. 数据迁移(分两段复制): - 从`front`复制到原数组末尾 - 从数组头部复制到`rear-1` 3. 重置指针:`front=0`, `rear=原元素个数` **动态入队实现**: ```c void EnQueue_Dynamic(SqQueue *Q, int value) { // 队满时扩容 if ((Q->rear + 1) % Q->capacity == Q->front) { int new_cap = Q->capacity * 2; int *new_base = (int*)malloc(new_cap * sizeof(int)); // 数据迁移 int i = 0; int j = Q->front; while (j != Q->rear) { new_base[i++] = Q->base[j]; j = (j + 1) % Q->capacity; // 循环移动 } free(Q->base); Q->base = new_base; Q->front = 0; Q->rear = i; // 指向最后一个元素的下一个位置 Q->capacity = new_cap; } // 正常入队 Q->base[Q->rear] = value; Q->rear = (Q->rear + 1) % Q->capacity; } ``` --- ### 三、两种实现对比 | **特性** | 定长实现 | 动态扩容实现 | |------------------|----------------------------|--------------------------| | 空间管理 | 固定大小 | 按需扩容(通常2倍) | | 队满处理 | 入队失败 | 自动扩容后继续入队 | | 时间复杂度 | $O(1)$ 入队/出队 | 均摊 $O(1)$ 入队 | | 空间利用率 | 浪费一个空间 | 扩容时暂有额外空间 | | 适用场景 | 内存受限环境 | 通用场景 | > **关键结论**:动态扩容通过均摊时间复杂度保证高效性。虽然单次扩容需 $O(n)$ 时间,但n次入队操作的总时间复杂度仍是 $O(n)$,均摊后每次 $O(1)$(引用[2]扩容策略)。 --- ### 四、其他避免空间浪费的方法 1. **增加计数标志**: - 添加`count`成员记录元素数量 - 队空:`count == 0` - 队满:`count == capacity` - 无需浪费空间,但增加维护成本 2. **链式循环队列**: - 每个节点动态分配内存 - 无固定容量限制,但指针占用额外空间 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值