C#数据结构-队列
创始人
2025-06-01 21:18:50

C#数据结构-队列

简述

区别于堆栈的LIFO,队列具有LIFO(先进先出,First In First Out)的特性。
同样的,.NET提供了Queue的泛型实现,它是一个环形队列,其内部也是通过数组实现的。

实现方案

同样的,也可以用数组或者链表来实现队列。

数组实现队列

.NET的Queue就是数组实现的,这里做简要的分析。

// 构造函数
public Queue(int capacity) {if (capacity < 0)ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNumRequired);_array = new T[capacity];_head = 0;_tail = 0;_size = 0;
}
// 清理
public void Clear() {if (_head < _tail)Array.Clear(_array, _head, _size);else {Array.Clear(_array, _head, _array.Length - _head);Array.Clear(_array, 0, _tail);}_head = 0;_tail = 0;_size = 0;_version++;
}
// 入队列
public void Enqueue(T item) {if (_size == _array.Length) {//_GrowFactor为2,每次翻倍int newcapacity = (int)((long)_array.Length * (long)_GrowFactor / 100);//_MinimumGrow为4,即最少为4if (newcapacity < _array.Length + _MinimumGrow) {newcapacity = _array.Length + _MinimumGrow;}SetCapacity(newcapacity);}_array[_tail] = item;_tail = (_tail + 1) % _array.Length;_size++;_version++;
}
// 出队列
public T Dequeue() {if (_size == 0)ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EmptyQueue);T removed = _array[_head];_array[_head] = default(T);_head = (_head + 1) % _array.Length;_size--;_version++;return removed;
}
// 取首
public T Peek() {if (_size == 0)ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EmptyQueue);return _array[_head];
}
// 包含
public bool Contains(T item) {int index = _head;int count = _size;EqualityComparer c = EqualityComparer.Default;while (count-- > 0) {if (((Object) item) == null) {if (((Object) _array[index]) == null)return true;} else if (_array[index] != null && c.Equals(_array[index], item)) {return true;}index = (index + 1) % _array.Length;}return false;
} 

综上,Enqueue的时间复杂度可以是O(n),Dequeue的时间复杂度是O(1),Contains的时间复杂度是O(n)

其他API

  • TrimExcess()

​ 如果元素数小于当前容量的 90%,将容量设置为 Queue 中的实际元素数。

固定容量环形队列

一些使用场景下我们并不需要一个可以扩容的队列。

取而代之的,我们想要的是一个固定容量,当容量超出后,会覆盖头部的环形队列。

这在处理历史数据,网络通信,实时数据处理的时候会很有用。

using System;
using System.Text;
public class BoundedCircQueue
{private T[] _data;private int _head;private int _tail;private int _capacity;private bool _isOverwrite;public int Count{get{if (_tail >= _head){return _tail - _head;}else{return _tail + _capacity - _head;}}}public bool IsEmpty() => _head == _tail;/// /// 构造函数/// /// 容量/// 是否可重写/// /// 实际占用容量会比capacity+1/// 原因是为了区分队列为空和队列已满的情况public BoundedCircQueue(int capacity, bool isOverwrite = true){if (capacity <= 0){throw new Exception("Capacity must be greater than 0");}capacity++;_data = new T[capacity];_head = 0;_tail = 0;_capacity = capacity;_isOverwrite = isOverwrite;}public void Enqueue(T data){if (!_isOverwrite && Count >= _capacity - 1){throw new Exception("Queue is full");}_data[_tail] = data;_tail = (_tail + 1) % _capacity;if (_head == _tail){_head = (_head + 1) % _capacity;}}public T Dequeue(){if (IsEmpty()){throw new Exception("Queue is empty");}T data = _data[_head];_data[_head] = default(T);_head = (_head + 1) % _capacity;return data;}public T Peek(){if (IsEmpty()){throw new Exception("Queue is empty");}return _data[_head];}public override string ToString(){if (IsEmpty()){return "[]";}var stringBuilder = new StringBuilder();stringBuilder.Append("[");int index = _head;while (true){stringBuilder.Append(_data[index].ToString());index = (index + 1) % _capacity;if (index == _tail){break;}stringBuilder.Append(", ");}stringBuilder.Append("]");return stringBuilder.ToString();}
}

使用演示:

// 测试可重写环形队列
BoundedCircQueue queue = new BoundedCircQueue(3, false);
queue.Enqueue(1);
Debug.Log($"插入1后:{queue.ToString()}, 队列长度:{queue.Count()}");
queue.Enqueue(2);
Debug.Log($"插入2后:{queue.ToString()}, 队列长度:{queue.Count()}");
queue.Enqueue(3);
Debug.Log($"插入3后:{queue.ToString()}, 队列长度:{queue.Count()}");
queue.Enqueue(4);
Debug.Log($"插入4后:{queue.ToString()}, 队列长度:{queue.Count()}");
var data = queue.Dequeue();
Debug.Log($"移除{data}后:{queue.ToString()}, 队列长度:{queue.Count()}");
queue.Enqueue(5);
Debug.Log($"插入5后:{queue.ToString()}, 队列长度:{queue.Count()}");

输出结果

插入1后:[1], 队列长度:1	
插入2后:[1, 2], 队列长度:2
插入3后:[1, 2, 3], 队列长度:3
插入4后:[2, 3, 4], 队列长度:3
移除2后:[3, 4], 队列长度:2
插入5后:[3, 4, 5], 队列长度:3

使用演示:

// 测试不可重写环形队列
BoundedCircQueue queue = new BoundedCircQueue(3, false);
queue.Enqueue(1);
Debug.Log($"插入1后:{queue.ToString()}, 队列长度:{queue.Count}");
queue.Enqueue(2);
Debug.Log($"插入2后:{queue.ToString()}, 队列长度:{queue.Count}");
queue.Enqueue(3);
Debug.Log($"插入3后:{queue.ToString()}, 队列长度:{queue.Count}");
queue.Enqueue(4);
Debug.Log($"插入4后:{queue.ToString()}, 队列长度:{queue.Count}");
var data = queue.Dequeue();
Debug.Log($"移除{data}后:{queue.ToString()}, 队列长度:{queue.Count}");
queue.Enqueue(5);
Debug.Log($"插入5后:{queue.ToString()}, 队列长度:{queue.Count}");

输出结果

插入1后:[1], 队列长度:1
插入2后:[1, 2], 队列长度:2
插入3后:[1, 2, 3], 队列长度:3
Exception: Queue is full

双向队列

双向队列(Double Ended Queues,DEQue)为一个有序线性表,加入与删除可在队列任意一端进行。

.NET提供的LinkedList双向链表就可以作为双向队列使用,双向队列还可以用数组实现。

优先队列

优先队列(Priority Queue)是一种遵循指定优先级的顺序表。

它非常有用,例如以下场景:

  • 游戏中的路径搜索算法
  • 游戏中的AI行为决策

.NET6(2021年11月9日发布)开始提供了名为PriorityQueue的优先队列实现。

但是Unity是基于Mono .NET的,它的版本比较老旧。

在Unity升级到CoreCLR之前,无法使用这个API(参考:.NET和Unity的未来 | Unity Blog)

可以使用这个版本:PriorityQueue/PriorityQueue.cs at main · FyiurAmron/PriorityQueue (github.com),是由.NET提供的PriorityQueue拿来兼容的。

底层实现依然是数组(元表数组),Enqueue算法是堆排序。

特别的,当以输入先后为优先级时就是一般的队列;当以输入先后的倒序为优先级时就变成堆栈。

相关内容

热门资讯

投资者提问:请问董秘,当前中日... 投资者提问:请问董秘,当前中日关系陷入僵局乃至谷底之际,公司有规划更多使用国产光刻胶等材料来替代日系...
富德生命人寿咸阳中心支公司被罚... 12月16日金融一线消息,据咸阳金融监管分局行政处罚信息公开表显示,富德生命人寿保险股份有限公司咸阳...
市场监管总局:抓紧推进出台《互... 转自:北京日报客户端记者16日从市场监管总局获悉,市场监管总局正抓紧推进《互联网平台反垄断合规指引》...
天奇股份:锂电循环业务夯实产业... 12月16日,江西天奇金泰阁集团(简称“天奇金泰阁”)资本赋能发展大会在江西龙南举行。会上,天奇股份...
「侠客岛」他们用上了“AI分身...   炒股就看金麒麟分析师研报,权威,专业,及时,全面,助您挖掘潜力主题机会! (来源:海外网)来源...