队列是具有先入先出特征的线性数据结构。

排队是生活中一个十分典型的队列,先到的人可以到达队列的队首,后到的则跟在后面成为队尾

比如说,在银行中,柜台会处理位于队首的客户的业务。完成后,队列的下一个客户会到达柜台办理。

位于队首的客户办完业务后离开队列被称为出队,而新客户加入等待的队列则是入队

常用操作

不同于,队列的一端可以放入元素,另一端则可以弹出元素。

元素入队、出队和访问队首元素的时间复杂度均为O(1)

C#中,内置的System.Collections.Queue提供了队列的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 初始化
Queue<int> queue = new();

// 元素入队
queue.Enqueue(1);
queue.Enqueue(1);
queue.Enqueue(4);
queue.Enqueue(5);
queue.Enqueue(1);
queue.Enqueue(4);

// 元素出队
int dequeue = queue.Dequeue(); // 1

// 获取队首元素
int peek = queue.Peek(); // 1

// 获取队列的长度
int size = queue.Count; // 5

// 判断队列是否为空
bool isEmpty = queue.Count == 0; // false

实现

与栈类似,队列也可以通过数组、链表或列表实现。

.NET是基于数组实现的

基于列表

对于列表和数组,应当记录队首的索引。在出队时改变索引即可。

同时,将列表的尾部元素定义为队尾,在列表的尾部添加元素是入队。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ListQueue<T>
{
List<T> queue = []; // 初始化

private int FrontIndex = 0; // 维护一个字段以记录队首元素索引

public void Enqueue(T element) => queue.Add(element); // 入队

public int Size() => queue.Count - FrontIndex; // 获取队列长度

public bool IsEmpty() => Size() == 0; // 判断队列是否为空

// 访问队首元素
public T Peek()
{
if (IsEmpty())
throw new Exception();
return queue[FrontIndex];
}

// 出队
public T Dequeue()
{
if (IsEmpty())
throw new Exception();
FrontIndex++;
return Peek();
}
}

基于链表

将链表的头节点视为队首,在链表的头部移除元素是出队,从尾节点添加元素则是入队。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 节点类
public class Node<T>(T item)
{
public T Item { get; set; } = item;
public Node<T> Next { get; set; }
}

class LinkedQueue<T> {
Node<T> front; // 队首
Node<T> rear; // 队尾

int size = 0; // 队列长度
public int Size() => size;

public bool IsEmpty() => Size() == 0; // 判断队列是否为空

// 入队
public void Enqueue(T element) {
Node<T> node = new(element);
if (front == null) // 如果队列为空,头节点指向该节点
{
front = node;
}
else if (rear != null) // 否则将该节点添加到尾节点后
{
rear.Next = node;
}
rear = node; // 让尾节点指向刚入队的节点
size++;
}

// 出队
public T Dequeue()
{
if (IsEmpty())
throw new Exception();
T element = Peek();
front = front.Next;
size--;
return element;
}

// 访问队首元素
public T Peek()
{
if (IsEmpty())
throw new Exception();
return front.Item;
}
}

对比

与栈类似,在不触发扩容操作的情况下,列表的实现效率更高。

基于链表实现的效率更加稳定,不会因为扩容操作而突然降低效率。

双向队列

C#中,双向队列即是LinkedList<T>,关于链表

基本应用

  • 排队
  • 进入游戏服务器队列
  • Todo 待办事项