C# 实现泛型动态循环数组队列的方法

唐可
唐可
发布时间:2026-10-16 | 6人浏览过
原创

C# 中的标准 Queue<T> 内部同样基于动态数组实现。本文将演示如何从头构建一个支持动态扩容的泛型循环数组队列,重点讲解如何通过取模运算处理索引循环,以及在队列为空或过于稀疏时进行空间释放的策略。

核心设计思路

队列(Queue)是一种先进先出(FIFO)的数据结构。若使用普通数组实现,每次出队后整体移动元素会导致性能下降。采用循环数组可以复用空闲空间,但需解决以下两个问题:

  • 索引循环计算:当索引到达数组末尾时,需回到开头继续存储或读取。
  • 动态扩容与缩容:为防止内存溢出或浪费,需在容量不足时倍增扩容,在剩余元素较少时减半缩容。

基础版本:固定容量循环队列

首先实现一个基础框架,仅包含入队、出队、判空和计数功能,暂不包含自动扩容逻辑。

using System;

namespace DataStructure
{
    /// <summary>
    /// 固定容量的泛型循环数组队列
    /// </summary>
    public class ArrayQueue<T>
    {
        private int capacity;
        private int startIndex;
        private int endIndex;
        private int count;
        private T[] array;

        public ArrayQueue(int capacity)
        {
            this.capacity = capacity;
            this.array = new T[capacity];
        }

        public int Count => count;
        public bool IsFull => count == capacity;
        public bool IsEmpty => count == 0;

        /// <summary>
        /// 入队操作
        /// </summary>
        public void Enqueue(T element)
        {
            if (IsFull)
                throw new InvalidOperationException("The queue is full.");

            array[endIndex] = element;
            // 使用取模运算实现索引循环
            endIndex = (endIndex + 1) % capacity;
            count++;
        }

        /// <summary>
        /// 出队操作
        /// </summary>
        public T Dequeue()
        {
            if (IsEmpty)
                throw new InvalidOperationException("The queue is empty.");

            T result = array[startIndex];
            // 重置旧位置引用,辅助 GC(可选,视数据大小而定)
            array[startIndex] = default(T);
            
            startIndex = (startIndex + 1) % capacity;
            count--;
            return result;
        }
    }
}

第1步:添加元素

将元素写入当前 endIndex 指向的位置,随后更新 endIndex 以指向下一个可用空位。公式 (index + 1) % length 确保索引在边界处正确回绕。

第2步:移除元素

记录并返回 startIndex 处的值,随后更新 startIndex 向后移动一位。此时逻辑上的队列长度 count 减一,实际物理数组并未清空,等待后续覆盖或触发缩容。

进阶版本:支持动态扩容与缩容

在生产环境中,通常期望队列具备动态调整大小的能力。以下为完整的可复用实现,包含扩容与缩容逻辑。

using System;
using System.Text;

namespace DataStructrue
{
    /// <summary>
    /// 支持动态扩容/缩容的循环数组队列
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class DynamicArrayQueue<E>
    {
        private E[] data;
        private int first;
        private int last;
        private int N; // 当前元素数量

        // 默认构造函数,初始容量为10
        public DynamicArrayQueue() : this(10) { }

        public DynamicArrayQueue(int capacity)
        {
            data = new E[capacity];
            first = 0;
            last = 0;
            N = 0;
        }

        public int Count { get { return N; } }
        public bool IsEmpty { get { return N == 0; } }
        public int Capacity { get { return data.Length; } }

        /// <summary>
        /// 向尾部添加元素
        /// </summary>
        public void Add(E e)
        {
            // 如果空间不足,扩容至两倍
            if (N == data.Length)
                Resize(data.Length * 2);

            data[last] = e;
            last = (last + 1) % data.Length;
            N++;
        }

        /// <summary>
        /// 从头部移除元素
        /// </summary>
        public E RemoveFirst()
        {
            if (N == 0)
                throw new ArgumentException("Queue is empty");

            // 当元素少于总容量的四分之一时,缩容至一半
            // 防止频繁交替插入删除导致反复扩容
            if (N <= data.Length / 4 && data.Length / 2 > 0)
                Resize(data.Length / 2);

            E ret = data[first];
            data[first] = default(E); // 辅助垃圾回收
            first = (first + 1) % data.Length;
            N--;
            return ret;
        }

        /// <summary>
        /// 查看队头元素(不移除)
        /// </summary>
        public E Peek()
        {
            if (N == 0)
                throw new ArgumentException("Queue is empty");
            return data[first];
        }

        /// <summary>
        /// 私有方法:调整数组容量
        /// 注意:必须保持元素的相对顺序
        /// </summary>
        private void Resize(int newSize)
        {
            E[] newData = new E[newSize];
            for (int i = 0; i < N; i++)
            {
                newData[i] = data[(first + i) % data.Length];
            }
            data = newData;
            first = 0;
            last = N;
        }

        public override string ToString()
        {
            StringBuilder res = new StringBuilder();
            res.AppendFormat("Queue: Count = {0}, Capacity = {1}\n[", N, data.Length);
            for (int i = 0; i < N; i++)
            {
                res.Append(data[(first + i) % data.Length]);
                if (i != N - 1)
                    res.Append(", ");
            }
            res.Append(']');
            return res.ToString();
        }
    }
}

第3步:处理扩容逻辑

当 Count == Capacity 时,说明物理空间已耗尽。此时创建一个新数组,长度为原长度的 2倍。由于原数组可能已经“首尾相接”,不能直接拷贝,而应通过 (first + i) % length 的顺序遍历原数组,并将元素连续存入新数组中。最后替换引用,并将 first 重置为 0。

第4步:处理缩容逻辑

为了防止用户在频繁入队和出队的场景下触发多次无意义的扩容(震荡),引入阈值判断。只有当元素数量小于容量的 1/4 且当前容量大于特定下限(如 2)时才执行缩容。缩容至原容量的 1/2。缩容同样需要重新排列元素,使其在新数组中连续存放。

性能对比与总结

  1. 时间复杂度:Add 和 RemoveFirst 的平均时间复杂度均为 O(1)。仅在触发扩容或缩容时会产生 O(N) 的开销,但由于分摊到每次操作上,均摊复杂度仍为 O(1)。
  2. 内存优势:相比普通线性数组队列(每次出队后移动元素),本实现无需移动数据,节省了内存带宽和 CPU 周期。
  3. 局限性:该结构主要用于标准的队列场景。如果需要支持随机访问(通过索引获取任意位置元素),则需要额外的逻辑转换或改用 List 结构。
以上就是 C# 实现泛型动态循环数组队列的详细内容,更多关于 C# 泛型队列性能优化的资料请关注本站其它相关文章!
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表