竞争性编程的队列

2025年3月17日 | 阅读 7 分钟

引言

对于喜欢快节奏、竞技性强的程序员来说,竞争性编程是一个令人兴奋的领域,他们可以在这里展示解决问题的能力。为了有效地处理复杂的算法问题,需要利用多种数据结构的能力,其中简单队列因其突出而备受青睐。队列是一种遵循“先进先出”(FIFO)原则的基本数据结构。可以把它想象成一条排队等待服务的队伍,排在前面的人先得到服务。队列在竞争性编程中非常有用,因为它们简单、易于实现,并且在各种场景下都非常有效。

广度优先搜索 (BFS)

在竞争性编程中,队列最常见的用途之一是与图算法相关,特别是广度优先搜索(BFS)。BFS逐层地检查图,并且借助队列,它能优雅地完成这项工作。下面提供了一个用队列实现BFS的C语言代码示例。

代码

输出

Queue for Competitive Programming

代码解释

输入

  • 程序将输入图中节点和边的数量。
  • 然后,假设这是一个无向图,它将输入每条边。

BFS 函数

  • bfs函数通过从指定的节点开始执行BFS遍历。
  • 在遍历过程中,它使用一个队列(queue)来入队和出队节点。
  • visited数组跟踪已访问的节点,以防止重复访问。

主函数

  • main函数根据用户输入初始化图。
  • 提示用户输入BFS的起始节点。
  • 最后,它调用bfs函数,并打印从指定节点开始的BFS遍历。

模拟问题

任务调度和事件处理是竞争性编程中经常模拟的两个现实世界场景的例子。由于它们非常直观,队列在这些类型的模拟中工作得很好。下面是一个用于基本任务调度模拟的简单C语言代码示例。

代码

输出

Queue for Competitive Programming

代码解释

任务结构

  • 该代码定义了一个名为 struct Task 的结构来表示每个任务。
  • 每个任务包含两个组成部分:任务标识符 (id) 和持续时间 (完成任务所需的时间)。

任务数组

  • 在名为 tasks 的数组中声明了 struct Task 的实例。
  • MAX_TASKS 设置了数组的最大大小。

任务调度器函数 (scheduleTasks)

  • 该函数接受一个整数参数 n,表示任务的数量。
  • 它使用一个名为 queue 的数组实现的队列来入队任务。
  • 根据数组索引,任务按接收顺序入队。

任务入队

  • 循环遍历任务,并将每个任务的索引添加到队列中。

处理任务

  • 然后,函数在一个 while 循环中按入队顺序执行任务。
  • 从队列的前端出队,表示将要处理的任务。
  • 处理过的任务的 ID 和持续时间被打印到控制台。

主函数

  • main函数中初始化了一些任务用于演示。
  • 将初始化任务的数量传递给 scheduleTasks 函数以模拟任务调度。

结果

  • 程序的输出显示了任务完成的顺序及其对应的持续时间。

滑动窗口问题

在分析固定大小的子数组或子序列时,如滑动窗口问题,队列被证明是一种有效的工具。下面的C语言代码示例说明了如何使用滑动窗口方法查找子数组中的最大元素。

代码

输出

Queue for Competitive Programming

代码解释

  • 函数: slidingWindowMax

参数

  • arr: 输入数组
  • n: 数组的维度。
  • k: 滑动窗口的大小。
  • 该函数设置一个队列来存储数组的元素索引。
  • 为了初始化队列,它独立处理前 k 个元素,从后面删除小于当前元素的元素。
  • 处理完剩余元素后,打印每个窗口的最大元素,并相应地更新队列。

主函数

  • 给定的数组 arr 的元素为 {1, 3, -1, -3, 5, 3, 6, 7}。
  • 窗口大小为 3。

实施

  • 使用 slidingWindowMax 函数确定大小为 k 的子数组中的最大元素,程序将打印输入数组。
  • 当窗口在输入数组中移动时,输出会显示每个窗口的最大元素。

动态连通性问题

动态连通性问题侧重于管理一组周期性连接或断开的元素。并查集(Disjoint Set Union)数据结构是解决这类问题的传统数据结构。它可以有效地处理查找(find)和合并(union)不相交集合。下面是一个用于解决动态连通性问题的简单C语言并查集数据结构实现。

代码

输出

Queue for Competitive Programming

代码解释

初始化

  • initialize函数通过初始化parent数组来配置并查集数据结构。parent数组用于保存不相交集合的结构,其中每个元素最初都是自己的集合。

查找操作(带路径压缩)

  • find函数负责查找给定元素所属集合的根(代表元)。它在查找操作期间更新到根路径上的父指针,因为它使用路径压缩。这有助于优化后续的查找操作。

合并操作(按秩合并)

  • merge函数通过将一个集合的根作为另一个集合根的父节点来合并两个集合。使用简单的按秩合并启发式方法来优化合并过程并保持平衡的树结构。

显示集合

  • displaySets函数打印每个元素的索引及其当前代表(根)。这使得可以看到集合。

主函数

  • 在 main 函数中,元素数量 (n) 被设置为 6。
  • 使用 initialize 函数在并查集数据结构中为每个元素创建初始的单例集合。
  • 在执行任何合并操作之前,使用 displaySets 函数显示集合。
  • 将元素 0 到 2 合并,并将元素 3 和 4 合并,以模拟动态场景中元素的连接方式。
  • 合并集合后,再次显示它们。
  • 使用 find 函数执行连通性检查,并确定特定元素是否连接。

输出

  • 输出显示初始集合(单例集合),其中每个元素都处于一个独立的集合中。
  • 在合并操作之后,集合被更新,反映了它们之间的连接。
  • 连通性检查确保特定元素属于不同的集合(不连接)或相同的集合(连接)。