区别于堆栈的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
如果元素数小于当前容量的 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)是一种遵循指定优先级的顺序表。
它非常有用,例如以下场景:
.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算法是堆排序。
特别的,当以输入先后为优先级时就是一般的队列;当以输入先后的倒序为优先级时就变成堆栈。