队列(Queue)代表了一个先进先出的对象集合。当您需要对各项进行先进先出的访问时,则使用队列。当您在列表中添加一项,称为入队,当您从列表中移除一项时,称为出队。
队列是一种特殊的线性表,特殊之处在于它只允许在表头(head)进行删除操作,而在表尾(tail)进行插入操作。
队列可以分为顺序队列和循环队列,.NET中为了提高空间的利用率,采用的是循环队列
Queue<int> qu = new Queue<int>();
循环数组
循环数组不同于顺序数组,循环数组会建立两个指针,分别指向数组头部和数组尾部。
初始化时,head和last都指向数组下标空间0处,当添加元素时,为head指向的下标赋值。head后移。当再次添加时,同时head指向的下标赋值,head继续后移。
当头部元素被移除时,head指向的下标元素移除,head后移此时head指向的元素当作队首。
当last指向开辟空间的队尾时,假如队列的元素小于队列容量,则说明还有空余空间。此时last指向first开辟空间之前的空间。具体操作如下:
当队列内元素值=队列容量值,时对其进行扩充。创建一个新的数组data,容量为原先的两倍(通常情况)将data数组中的值赋予newData。
此时还需要设置建立两个指针,分别指向数组头部和数组尾部。first指向下标为0,last指向元素个数N的下标。
属性 | 描述 |
---|---|
Count | 获取 Queue 中包含的元素个数。 |
1 | public virtual void Clear(); 从 Queue 中移除所有的元素。 |
2 | public virtual bool Contains( object obj ); 判断某个元素是否在 Queue 中。 |
3 | public virtual object Dequeue(); 移除并返回在 Queue 的开头的对象。 |
4 | public virtual void Enqueue( object obj ); 向 Queue 的末尾添加一个对象。 |
5 | public virtual object[] ToArray(); 复制 Queue 到一个新的数组中。 |
6 | public virtual void TrimToSize(); 设置容量为 Queue 中元素的实际个数。 |
C#Queue底层实现
public class Queue : ICollection, ICloneable { private Object[] _array; private int _head; // First valid element in the queue private int _tail; // Last valid element in the queue private int _size; // Number of elements. private int _growFactor; // 100 == 1.0, 130 == 1.3与旧容量相乘得到新容量 private int _version; [NonSerialized] private Object _syncRoot; private const int _MinimumGrow = 4;// 最小增长值 private const int _ShrinkThreshold = 32;// 收缩阈值
构造函数
public Queue() : this(32, (float)2.0) { } // Creates a queue with room for capacity objects. The default grow factor // is used. // public Queue(int capacity) : this(capacity, (float)2.0) { } // Creates a queue with room for capacity objects. When full, the new // capacity is set to the old capacity * growFactor. // public Queue(int capacity, float growFactor) { if (capacity < 0) throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); if (!(growFactor >= 1.0 && growFactor <= 10.0)) throw new ArgumentOutOfRangeException("growFactor", Environment.GetResourceString("ArgumentOutOfRange_QueueGrowFactor", 1, 10)); Contract.EndContractBlock(); _array = new Object[capacity]; _head = 0; _tail = 0; _size = 0; _growFactor = (int)(growFactor * 100); }
当为默认构造函数时,Queue会自己生成一个容器大小为32,扩容因子为2.0的对象。我们可以自己设置构造函数的容量以及扩容因子growFactor(为浮点型变量)。
1、Clear()函数
// Removes all Objects from the queue. public virtual 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++; }
Clear有两种情况:
(1)当首指针指向的下标小于尾指针指向的下标:直接从下标_head处开始移除,移除_size个元素。例子:
(2)当首指针指向的下标大于尾指针指向的下标:先清除下标_head到数组尾部的元素,再清除下标0到下标_tail的值。例子:
2、bool Contains( object obj );
public virtual bool Contains(Object obj) { int index = _head; int count = _size; while (count-- > 0) { if (obj == null) {//当查询元素为空且队列头部为空时成立 if (_array[index] == null) return true; } else if (_array[index] != null && _array[index].Equals(obj)) { return true; } index = (index + 1) % _array.Length; } return false; }
第一个if变相的说明了队列之中能入队null。
需要注意的是,当创建的队列的类型不为object类型时,我们无法去用Contains(null)去确定。
null为object类型。Contains循环遍历时间复杂度O(n)。
3、object Dequeue();出队操作,移除并返回在 Queue 的开头的对象。
public virtual Object Dequeue() { if (Count == 0) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EmptyQueue")); Contract.EndContractBlock(); Object removed = _array[_head]; _array[_head] = null; _head = (_head + 1) % _array.Length; _size--; _version++; return removed; }
创建一个队列元素类型的变量,并将队首的元素赋予它。清空队首元素,_head后移(或前移)。
时间复杂度O(1).
4、void Enqueue( object obj );向 Queue 的末尾添加一个对象。
public virtual void Enqueue(Object obj) { if (_size == _array.Length) { int newcapacity = (int)((long)_array.Length * (long)_growFactor / 100); if (newcapacity < _array.Length + _MinimumGrow) { newcapacity = _array.Length + _MinimumGrow; } SetCapacity(newcapacity); } _array[_tail] = obj; _tail = (_tail + 1) % _array.Length; _size++; _version++; }
(1)当队列内元素个数=容量时,创建一个新的容量大小newcapacity。此大小=队列容量*增长因子。当增长因子太小时,newcapacity<原队列容量+最小增长量时。使 newcapacity = _array.Length + _MinimumGrow;引用 SetCapacity(newcapacity)函数。
private void SetCapacity(int capacity) { Object[] newarray = new Object[capacity]; if (_size > 0) { if (_head < _tail) { Array.Copy(_array, _head, newarray, 0, _size); } else { Array.Copy(_array, _head, newarray, 0, _array.Length - _head); Array.Copy(_array, 0, newarray, _array.Length - _head, _tail); } } _array = newarray; _head = 0; _tail = (_size == capacity) ? 0 : _size; _version++; }
同循环数组两种情况。时间复杂度O(1)。
(2)直接再_tail下标处添加元素,_tail向后(或向前)移动。
5、object[] ToArray();复制 Queue 到一个新的数组中
public virtual Object[] ToArray() { Object[] arr = new Object[_size]; if (_size==0) return arr; if (_head < _tail) { Array.Copy(_array, _head, arr, 0, _size); } else { Array.Copy(_array, _head, arr, 0, _array.Length - _head); Array.Copy(_array, 0, arr, _array.Length - _head, _tail); } return arr; }
同理分两种情况。
6、void TrimToSize();设置实际元素个数
public virtual void TrimToSize() { SetCapacity(_size); }