队列(Queue)是插入操作限定在表的尾部而其它操作限定在表的头部进行的线性表。把进行插入操作的表尾称为队尾(Rear),把进行其它操作的头部称为队头(Front)。当对列中没有数据元素时称为空对列(Empty Queue)。对列的操作是按照先进先出(First In First Out)或后进后出( Last In Last Out)的原则进行的,因此,队列又称为FIFO表或LILO表。队列Q的操作示意图如图所示。
队列的形式化定义为:队列(Queue)简记为 Q,是一个二元组,
Q = (D, R)
其中:D 是数据元素的有限集合;
R 是数据元素之间关系的有限集合。
队列的操作是线性表操作的一个子集。队列的操作主要包括在队尾插入元素、在队头删除元素、取队头元素和判断队列是否为空等。与栈一样,队列的运算是定义在逻辑结构层次上的,而运算的具体实现是建立在物理存储结构层次上的。因此,把队列的操作作为逻辑结构的一部分,每个操作的具体实现只有在确定了队列的存储结构之后才能完成。
队列接口 IQueue<T>的定义如下所示。
public interface IQueue<T>//队列接口的定义 { int GetLength(); //求队列的长度 bool IsEmpty(); //判断对列是否为空 void Clear(); //清空队列 void In(T item); //入队 T Out(); //出队 T GetFront(); //取对头元素 }
队列的存储和运算实现
- 顺序队列
用一片连续的存储空间来存储队列中的数据元素, 这样的队列称为顺序队列 (Sequence Queue)。类似于顺序栈,用一维数组来存放顺序队列中的数据元素。队头位置设在数组下标为 0 的端,用 front 表示;队尾位置设在数组的另一端,用 rear 表示。 front 和 rear 随着插入和删除而变化。 当队列为空时, front=rear=-1。图是顺序队列的两个指示器与队列中数据元素的关系图。
当有数据元素入队时,队尾指示器 rear 加 1,当有数据元素出队时,队头指示器 front 加 1。当 front=rear 时,表示队列为空,队尾指示器 rear 到达数组的上限处而 front 为-1 时,队列为满,如图 (c)所示。队尾指示器 rear 的值大于队头指示器 front 的值,队列中元素的个数可以由 rear-front 求得。由图 (d)可知,如果再有一个数据元素入队就会出现溢出。但事实上队列中并未满,还有空闲空间,把这种现象称为“假溢出” 。这是由于队列“队尾入队头出”的操作原则造成的。解决假溢出的方法是将顺序队列看成是首尾相接的循环结构,头尾指示器的关系不变,这种队列叫循环顺序队列( Circular sequenceQueue )。循环队列如图所示。
当队尾指示器 rear 到达数组的上限时, 如果还有数据元素入队并且数组的第0 个空间空闲时,队尾指示器 rear 指向数组的 0 端。所以,队尾指示器的加 1 操作修改为:rear = (rear + 1) % maxsize。队头指示器的操作也是如此。当队头指示器 front 到达数组的上限时,如果还有数据元素出队,队头指示器 front 指向数组的 0 端。所以,队头指示器的加1 操作修改为:front = (front + 1) % maxsize。循环顺序队列操作示意图如图所示。
由图可知,队尾指示器 rear 的值不一定大于队头指示器 front 的值,并且队满和队空时都有 rear=front。也就是说,队满和队空的条件都是相同的。解决这个问题的方法一般是少用一个空间, 如图 (d)所示, 把这种情况视为队满。所以,判断队空的条件是:rear==front,判断队满的条件是:(rear + 1) %maxsize==front。求循环队列中数据元素的个数可由(rear-front+maxsize)%maxsize公式求得。
把循环顺序队列看作是一个泛型类,用数组来存储循环顺序队列中的元素,在类中用字段 data 来表示。用字段maxsize 表示循环顺序队列的容量, maxsize 的值可以根据实际需要修改,这通过类的构造器中的参数 size 来实现, 循环顺序队列中的元素由 data[0]开始依次顺序存放。字段 front 表示队头, front 的范围是 0 到 maxsize-1。 字段rear表示队尾, rear 的范围也是 0 到 maxsize-1。 如果循环顺序队列为空, front=rear=-1。当执行入队列操作时需要判断循环顺序队列是否已满,如果循环顺序队列已满,(rear + 1) % maxsize==front,循环顺序队列已满不能插入元素。
循环顺序队列类 CSeqQueue<T>的实现说明如下所示。
public class CSeqQueue<T> : IQueue<T>//循环顺序队列类的实现 { private int maxsize; private T[] data; private int front; private int rear; public int Maxsize { get { return maxsize; } set { maxsize = value; } } public int Front { get { return front; } set { front = value; } } public int Rear { get { return rear; } set { rear = value; } } public T this[int index] { get { return data[index]; } set { data[index] = value; } } public CSeqQueue(int size) { data = new T[size]; maxsize = size; front = rear = -1; } /* 循环顺序队列的长度取决于队尾指示器 rear 和队头指示器 front。一般情 况下,rear 大于 front,因为入队的元素肯定比出队的元素多。特殊的情况是 rear 到达数组的上限之后又从数组的低端开始,此时,rear 是小于 front 的。 所以,rear 的大小要加上 maxsize。因此,循环顺序队列的长度应该是: (rear-front+maxsize)%maxsize。 */ public int GetLength() { return (rear - front + maxsize) % maxsize; } public bool IsEmpty() { return front == rear; } public void Clear() { rear = front = -1; } public bool IsFull() { return (rear + 1) % maxsize == front; } public void In(T item) { if (IsFull()) { Console.WriteLine("Queue is full"); return; } data[++rear] = item; } public T Out() { T tmp = default(T); if (IsEmpty()) { Console.WriteLine("Queue is empty"); return tmp; } tmp = data[++front]; return tmp; } public T GetFront() { if (IsEmpty()) { Console.WriteLine("Queue is empty!"); return default(T); } return data[front + 1]; } }
2、链队列
队列的另外一种存储方式是链式存储,这样的队列称为链队列(Linked Queue)。同链栈一样,链队列通常用单链表来表示,它的实现是单链表的简化。所以,链队列的结点的结构与单链表一样。由于链队列的操作只是在一端进行,为了操作方便,把队头设在链表的头部,并且不需要头结点。
链队列结点类(Node<T>)的实现如下所示:
public class Node<T>//链队列结点类 { private T data; //数据域 private Node<T> next; //引用域 //构造器 public Node(T val, Node<T> p) { data = val; next = p; } //构造器 public Node(Node<T> p) { next = p; } //构造器 public Node(T val) { data = val; next = null; } //构造器 public Node() { data = default(T); next = null; } //数据域属性 public T Data { get { return data; } set { data = value; } } //引用域属性 public Node<T> Next { get { return next; } set { next = value; } } }
把链队列看作一个泛型类,类中有两个字段 front 和 rear,表示队头指示器和队尾指示器。由于队列只能访问队头的数据元素,而链队列的队头指示器和队尾指示器又不能指示队列的元素个数,所以,与链栈一样,在类增设一个字段 num 表示链队列中结点的个数。
链队列类 LinkQueue<T>的实现说明如下所示。
public class LinkQueue<T> : IQueue<T>//链队列类的实现 { private Node<T> front; //队列头指示器 private Node<T> rear; //队列尾指示器 private int num; //队列结点个数 //队头属性 public Node<T> Front { get { return front; } set { front = value; } } //队尾属性 public Node<T> Rear { get { return rear; } set { rear = value; } } //队列结点个数属性 public int Num { get { return num; } set { num = value; } } public LinkQueue() { front = rear = null; num = 0; } public int GetLength() { return num; } public bool IsEmpty() { return front == rear && num == 0; } public void Clear() { front = rear = null; num = 0; } public void In(T item) { Node<T> node = new Node<T>(item); if (rear == null) { rear = node; } rear.Next = node; rear = node; ++num; } public T Out() { if (IsEmpty()) { Console.WriteLine("Queue is empty!"); return default(T); } Node<T> node = front; front = front.Next; if (front == null) { rear = null; } --num; return front.Data; } public T GetFront() { if (IsEmpty()) { Console.WriteLine("Queue is empty!"); return default(T); } return front.Data; } }