队列可以通过数组和链表来实现, 就看内置的栈和队列类是用哪种方式实现了。
队列:是一种先进先出的线性表,在队头删除元素(出队),在队尾添加元素(入队)
线性表: 是n个数据元素的有限序列,是连续的。 这也是一个例子
下面是数组队列实现原理:
#region 数组队列 /// <summary> /// 数组队列 /// </summary> /// <typeparam name="E"></typeparam> class MyArrayQueue<E> : IQueue<E> { //当前数组 private MyArray<E> arr; /// <summary> /// 创建指定长度的数组 /// </summary> /// <param name="capacity"></param> public MyArrayQueue(int capacity) { arr = new MyArray<E>(capacity); } public MyArrayQueue() { arr = new MyArray<E>(); } public int Count { get { return arr.Count; } } public bool IsEmpty { get { return arr.IsNull; } } /// <summary> /// 移除队头的元素 /// </summary> /// <returns></returns> public E Dequeue() { return arr.RemoveFirst(); } /// <summary> /// 添加元素到队尾 /// </summary> /// <param name="e"></param> public void Enqueue(E e) { arr.AddLast(e); } /// <summary> /// 获取对头元素 /// </summary> /// <returns></returns> public E Peek() { return arr.GetFirst(); } } #endregion
复杂度:从图中我们可以看到Dequeue移除队头元素的方法是最复杂的,因为数组在移除头元素之后,后面的所有元素都要进行重新移位。
循环队列
在上面代码得知Dequeue方法复杂度太高,所以可以通过循环数组解决。
比如下图:用2个字段记录头部和尾部的位置信息,当元素2后面添加新元素3的时候,last指向的位置添加新元素,last后移移位,这样下次再次添加新元素的时候可以直接根据last的位置进行插入。
当我们移除元素0的时候,first++,后移一位。
此时,我们在尾部添加元素4,按照上面逻辑来说last还要后移一位,但是此时已经到了最大长度了,所以可以将last按照图中的算式指向对应的位置。
代码实现:
class Array2<E> { private E[] data; private int first; private int last; //元素数量 private int N; public Array2(int capacity) { data = new E[capacity]; N = 0; first = 0; last = 0; } public Array2() : this(10) { } /// <summary> /// 数组实际存储数量 /// </summary> public int Count { get { return N; } } /// <summary> /// 可以看数组元素是否为空 /// </summary> public bool IsNull { get { return N == 0; } } /// <summary> /// 添加元素 队列只能在队尾添加,队头出 /// </summary> /// <param name="e"></param> public void AddLast(E e) { data[last] = e; //移动last last = (last + 1) % data.Length; N++; } public E RemoveFirst() { if (IsNull) { throw new Exception("数组为空"); } E del = data[first]; //设置为默认值,垃圾回收机制这样就可以自动回收值。 data[first] = default(E); //移动first first = (first + 1) % data.Length; N--; return del; } public E GetFirst() { if (IsNull) { throw new Exception("数组为空"); } return data[first]; } }
但是循环数组同样需要扩容操作,比如下图,此时已经没有空间了,
通过下面2图中的形式来扩容
:
下面代码实现了循环数组的扩容和缩容,分别在添加和删除的时候进行的:
class Array2<E> { private E[] data; private int first; private int last; //元素数量 private int N; public Array2(int capacity) { data = new E[capacity]; N = 0; first = 0; last = 0; } public Array2() : this(10) { } /// <summary> /// 数组实际存储数量 /// </summary> public int Count { get { return N; } } /// <summary> /// 可以看数组元素是否为空 /// </summary> public bool IsNull { get { return N == 0; } } /// <summary> /// 添加元素 队列只能在队尾添加,队头出 /// </summary> /// <param name="e"></param> public void AddLast(E e) { if (N == data.Length) { //扩容 ResetCapacity(2 * data.Length); } data[last] = e; //移动last last = (last + 1) % data.Length; N++; } public E RemoveFirst() { if (IsNull) { throw new Exception("数组为空"); } E del = data[first]; //设置为默认值,垃圾回收机制这样就可以自动回收值。 data[first] = default(E); //移动first first = (first + 1) % data.Length; N--; if (N == data.Length / 4) { //扩容 ResetCapacity(data.Length / 2); } return del; } public E GetFirst() { if (IsNull) { throw new Exception("数组为空"); } return data[first]; } private void ResetCapacity(int newCapacity) { E[] newData = new E[newCapacity]; for (int i = 0; i < N; i++) { newData[i] = data[(first + i) % data.Length]; } data = newData; first = 0; last = N; } }
在上面已经实现了循环数组,所以根据循环数组实现的循环队列代码如下:
#region 数组队列 /// <summary> /// 循环数组队列 /// </summary> /// <typeparam name="E"></typeparam> class Array2Queue<E> : IQueue<E> { //当前数组 private Array2<E> arr; /// <summary> /// 创建指定长度的数组 /// </summary> /// <param name="capacity"></param> public Array2Queue(int capacity) { arr = new Array2<E>(capacity); } public Array2Queue() { arr = new Array2<E>(); } public int Count { get { return arr.Count; } } public bool IsEmpty { get { return arr.IsNull; } } /// <summary> /// 移除队头的元素 /// </summary> /// <returns></returns> public E Dequeue() { return arr.RemoveFirst(); } /// <summary> /// 添加元素到队尾 /// </summary> /// <param name="e"></param> public void Enqueue(E e) { arr.AddLast(e); } /// <summary> /// 获取对头元素 /// </summary> /// <returns></returns> public E Peek() { return arr.GetFirst(); } } #endregion
此时Dequeue移除数组队列中的头元素复杂度有O(n)变为O(1),直接根据first便可以找到头元素,移除之后first后移一位,不会像普通数组还要重新排位。
链表队列
一个普通的链表实现的队列:
#region 链表队列 /// <summary> /// 链表队列 /// </summary> /// <typeparam name="E"></typeparam> class MyLinkedListQueue<E> : IQueue<E> { //当前链表 private MyLinkedList<E> arr; public MyLinkedListQueue() { arr = new MyLinkedList<E>(); } public int Count { get { return arr.Count; } } public bool IsEmpty { get { return arr.IsEmpty; } } /// <summary> /// 移除队头的元素 /// </summary> /// <returns></returns> public E Dequeue() { return arr.RemoveFirst(); } /// <summary> /// 添加元素到队尾 /// </summary> /// <param name="e"></param> public void Enqueue(E e) { arr.AddLast(e); } /// <summary> /// 获取队头元素 /// </summary> /// <returns></returns> public E Peek() { return arr.GetFirst(); } } #endregion
时间复杂度:
通过上面得知Enqueue添加元素的时间复杂度最高,正常的链表:头->....->尾 ,从左往右2端分别是单链表的头与尾,而队列的规定的原理是要遵循先(尾进)进先出(头出),所以添加的时候是往尾部添加,单链表中从尾部添加元素就要从头往后一步一步的找到尾部节点,所以时间复杂度最高。
要解决上面的的问题,可以通过实现一个具有尾指针的链表来解决,
#region 具有尾指针的单链表 public class MyLinkedList2<T> { //当前链表类记录这个链表的头部类 private Node head; //链表的节点数量 private int N; //尾指针 private Node tail; public MyLinkedList2() { N = 0; //因为一开始链表没有元素,所以头部节点指向空。 head = null; tail = null; } /// <summary> /// 链表节点数量 /// </summary> public int Count { get { return N; } } /// <summary> /// 链表是否为空 /// </summary> public bool IsEmpty { get { return N == 0; } } /// <summary> /// 队列是先进先出队尾进入队头出,所以 /// 当前链表只保留队尾元素增加方法即可 /// </summary> /// <param name="e"></param> public void AddLast(T e) { Node node = new Node(e); if (IsEmpty) { //添加前链表为空 head = node; tail = node; } else { tail.next = node; tail = node; } N++; } /// <summary> /// 删除 /// </summary> /// <returns></returns> public T RemoveFirst() { if (IsEmpty) { throw new Exception("链表为空"); } T e = head.e; head = head.next; N--; if (head == null) { tail = null; } return e; } public T GetFirst() { return head.e; } /// <summary> /// 节点类 不让外部知道此类,设为私有 /// </summary> private class Node { //当前节点 public T e; //当前节点的下一节点 public Node next; public Node(T e, Node next) { this.e = e; this.next = next; } public Node(T e) { this.e = e; } } /// <summary> /// 修改 /// </summary> /// <param name="index"></param> /// <param name="e"></param> /// <returns></returns> public void Set(int index, T e) { if (index < 0 || index >= N) { throw new Exception("非法索引"); } Node currentNode = head;//从头结点开始查找 for (int i = 0; i < index; i++) { currentNode = currentNode.next; } currentNode.e = e; ; } /// <summary> /// 查找链表中是否存在此元素 /// </summary> /// <param name="e"></param> /// <returns></returns> public bool Contains(T e) { Node current = head; while (current != null) { if (current.e.Equals(e)) { return true; } current = current.next; } return false; } /// <summary> /// 可以重写tostring打印方法 /// 打印出链表信息 /// </summary> /// <returns></returns> public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); Node cur = head; while (cur != null) { stringBuilder.Append(cur.e + "->"); cur = cur.next; } stringBuilder.Append("null"); return stringBuilder.ToString(); } } #endregion
代码如上,在队列中使用带有尾指针的类就可以解决上面问题,具体引用实现如下:
#region 带有尾指针链表队列 /// <summary> /// 链表队列 /// </summary> /// <typeparam name="E"></typeparam> class MyLinkedList2Queue<E> : IQueue<E> { //当前链表 private MyLinkedList2<E> arr; public MyLinkedList2Queue() { arr = new MyLinkedList2<E>(); } public int Count { get { return arr.Count; } } public bool IsEmpty { get { return arr.IsEmpty; } } /// <summary> /// 移除队头的元素 /// </summary> /// <returns></returns> public E Dequeue() { return arr.RemoveFirst(); } /// <summary> /// 添加元素到队尾 /// </summary> /// <param name="e"></param> public void Enqueue(E e) { arr.AddLast(e); } /// <summary> /// 获取队头元素 /// </summary> /// <returns></returns> public E Peek() { return arr.GetFirst(); } } #endregion