图论迷宫 vs 队列交响曲:算法世界中的路径探寻与秩序之美

#王者杯·14天创作挑战营·第4期#

在算法的宇宙中,问题如星辰般璀璨各异。有的如迷宫般错综复杂,需巧破路径之谜;有的如精密齿轮,需构建秩序之链。今日,我们将深入两个经典问题:"节点间通路"的图论迷宫与"动物收容所"的队列交响曲。它们看似天壤之别,却在算法设计的琴弦上奏出共鸣——搜索与秩序。我们将以庖丁解牛之姿剖析其内核,用对比图表揭示本质,带您领略算法设计的艺术与哲学。

第一章:图论迷宫——节点间通路的搜索艺术
节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。

问题本质:给定有向图,判断从起点 start 到终点 target 是否存在路径。图中含自环(节点指向自身)与平行边(节点间多条同向边),需应对高达 105105 节点的规模挑战。

算法策略与时空权衡

  1. 邻接表建图

    • 使用稀疏矩阵压缩技术,将边集 graph 转化为邻接表(Adjacency List),避免邻接矩阵的 O(n2)O(n2) 空间灾难。

    • 处理平行边:邻接表天然支持重边存储,不影响算法正确性。

  2. 搜索算法选型

    • BFS(广度优先搜索)

      • 核心思想:逐层扩散,用队列管理待访问节点。

      • 优势:天然找到最短路径(本题无需),无递归栈溢出风险。

      • 时间复杂度:O(n+e)O(n+e)(ee 为边数),完美匹配 n≤105n≤105 的约束。

    • DFS(深度优先搜索)

      • 核心思想:递归深入路径末端,回溯探索分支。

      • 风险:图深度过大时易栈溢出,需显式栈实现迭代DFS。

  3. 关键优化——访问标记

    • 使用布尔数组 visited 标记已访问节点,避免重复访问与循环陷阱。

    • 自环处理:自环边 [u,u] 不影响连通性,但需防止无限循环——visited 数组可拦截此风险。

示例解析

示例1:n=3, graph=[[0,1],[0,2],[1,2],[1,2]], start=0, target=2  
邻接表:0→[1,2], 1→[2,2]  # 重边不影响  
BFS路径:0 → 1 → 2 或 0 → 2,存在通路 → 输出 true  

题目程序:

#include <stdio.h>   // 标准输入输出头文件
#include <stdlib.h>  // 标准库头文件(内存分配、动态数组等)
#include <stdbool.h> // 布尔类型支持

// 定义邻接表节点结构
struct AdjListNode {
    int dest;                // 目标节点编号
    struct AdjListNode* next; // 指向下一个邻接节点的指针
};

// 定义邻接表结构
struct AdjList {
    struct AdjListNode* head; // 链表头指针
};

// 定义图结构
struct Graph {
    int numVertices;        // 图中节点总数
    struct AdjList* array;  // 邻接表数组(每个元素对应一个节点)
};

// 创建新的邻接表节点
struct AdjListNode* newAdjListNode(int dest) {
    // 分配节点内存
    struct AdjListNode* newNode = (struct AdjListNode*)malloc(sizeof(struct AdjListNode));
    newNode->dest = dest;    // 设置目标节点
    newNode->next = NULL;    // 初始化next指针
    return newNode;
}

// 创建图结构
struct Graph* createGraph(int numVertices) {
    // 分配图结构内存
    struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
    graph->numVertices = numVertices; // 设置节点总数

    // 分配邻接表数组内存(大小 = 节点数)
    graph->array = (struct AdjList*)malloc(numVertices * sizeof(struct AdjList));

    // 初始化每个邻接表头指针
    for (int i = 0; i < numVertices; ++i)
        graph->array[i].head = NULL; // 置空链表头

    return graph;
}

// 添加边到图中(有向边)
void addEdge(struct Graph* graph, int src, int dest) {
    // 创建新节点(目标为dest)
    struct AdjListNode* newNode = newAdjListNode(dest);
    // 将新节点插入邻接表头部
    newNode->next = graph->array[src].head;
    graph->array[src].head = newNode;
}

// BFS搜索路径是否存在
bool BFS(struct Graph* graph, int start, int target) {
    // 边界情况:起点等于终点
    if (start == target)
        return true;

    // 创建访问标记数组(初始化为false)
    bool* visited = (bool*)calloc(graph->numVertices, sizeof(bool));
    // 创建循环队列(大小 = 节点数)
    int* queue = (int*)malloc(graph->numVertices * sizeof(int));
    int front = 0; // 队列头指针
    int rear = 0;  // 队列尾指针

    // 起点入队并标记
    queue[rear++] = start;
    visited[start] = true;

    while (front < rear) { // 队列非空时循环
        int current = queue[front++]; // 出队当前节点

        // 遍历当前节点的所有邻接节点
        struct AdjListNode* neighbor = graph->array[current].head;
        while (neighbor != NULL) {
            int adjVertex = neighbor->dest; // 获取邻接节点

            // 若找到目标节点则返回成功
            if (adjVertex == target) {
                free(visited);
                free(queue);
                return true;
            }

            // 若邻接节点未访问过
            if (!visited[adjVertex]) {
                visited[adjVertex] = true; // 标记已访问
                queue[rear++] = adjVertex; // 入队
            }
            neighbor = neighbor->next; // 移动到下一个邻接节点
        }
    }

    // 队列空且未找到目标节点
    free(visited);
    free(queue);
    return false;
}

// 释放图内存
void freeGraph(struct Graph* graph) {
    // 释放每个邻接表的节点
    for (int v = 0; v < graph->numVertices; ++v) {
        struct AdjListNode* current = graph->array[v].head;
        while (current != NULL) {
            struct AdjListNode* temp = current;
            current = current->next;
            free(temp);
        }
    }
    // 释放邻接表数组和图结构
    free(graph->array);
    free(graph);
}

int main() {
    int n, m; // n=节点数, m=边数
    scanf("%d", &n); // 读取节点数
    scanf("%d", &m); // 读取边数

    // 创建图结构
    struct Graph* graph = createGraph(n);

    // 读取所有边
    for (int i = 0; i < m; ++i) {
        int u, v;
        scanf("%d %d", &u, &v); // 读取边的起点和终点
        addEdge(graph, u, v);   // 添加到图中
    }

    int start, target;
    scanf("%d %d", &start, &target); // 读取起点和终点

    // 执行BFS搜索并输出结果
    if (BFS(graph, start, target)) {
        printf("true\n");
    } else {
        printf("false\n");
    }

    // 释放图内存
    freeGraph(graph);
    return 0;
}

输出结果:

第二章:队列交响曲——动物收容所的秩序法则


动物收容所。有家动物收容所只收容狗与猫,且严格遵守“先进先出”的原则。在收养该收容所的动物时,收养人只能收养所有动物中“最老”(由其进入收容所的时间长短而定)的动物,或者可以挑选猫或狗(同时必须收养此类动物中“最老”的)。换言之,收养人不能自由挑选想收养的对象。请创建适用于这个系统的数据结构,实现各种操作方法,比如enqueue、dequeueAny、dequeueDog和dequeueCat。允许使用Java内置的LinkedList数据结构。

enqueue方法有一个animal参数,animal[0]代表动物编号,animal[1]代表动物种类,其中 0 代表猫,1 代表狗。

dequeue*方法返回一个列表[动物编号, 动物种类],若没有可以收养的动物,则返回[-1,-1]。

问题本质:设计队列系统,实现严格FIFO(先进先出)的动物收养规则。核心操作包括入队(enqueue)和三种出队方式(dequeueAnydequeueDogdequeueCat),需处理最大20000容量的动态序列。

数据结构与算法设计

  1. 双队列+时间戳范式

    • 独立猫狗队列

      • cat_queue 存储猫的入队记录 [id, timestamp]

      • dog_queue 存储狗的入队记录 [id, timestamp]

    • 全局时序计数器:每次入队递增,为动物分配唯一时序值,实现跨队列年龄比较。

  2. 操作详解

    • enqueue(animal)

      python

      if animal[1] == 0:  # 猫  
          cat_queue.append([animal[0], global_timestamp])  
      else:                # 狗  
          dog_queue.append([animal[0], global_timestamp])  
      global_timestamp += 1  
    • dequeueAny()

      • 比较猫狗队列头部时序值,弹出时序更小者(即更早入队)。

    • dequeueDog()/dequeueCat()

      • 直接弹出对应队列头部,空队列返回 [-1, -1]

  3. 时空复杂度

    • 时间:所有操作 O(1)O(1)(队列头部操作均摊常数时间)。

    • 空间:O(n)O(n)(双队列存储动物记录)。

示例解析

示例2操作序列:  
enqueue([0,0]) → 猫队列: [[0,0]]  
enqueue([1,0]) → 猫队列: [[0,0], [1,1]]  
enqueue([2,1]) → 狗队列: [[2,2]]  
dequeueDog() → 狗队列头部 [2,1] → 返回 [2,1]  
dequeueCat() → 猫队列头部 [0,0] → 返回 [0,0]  
dequeueAny() → 猫队头时序=1 vs 狗队空 → 弹出猫队 [1,0] → 返回 [1,0]  

题目程序:

#include <stdio.h>   // 标准输入输出函数库
#include <stdlib.h>  // 动态内存管理函数库
#include <string.h>  // 字符串操作函数库

// 定义动物节点结构体
typedef struct AnimalNode {
    int id;                 // 动物编号
    int species;            // 动物种类 (0=猫, 1=狗)
    int timestamp;          // 入队时间戳
    struct AnimalNode* next; // 指向下一个节点的指针
} AnimalNode;

// 定义动物队列结构体
typedef struct {
    AnimalNode* head;       // 队列头指针
    AnimalNode* tail;       // 队列尾指针
} AnimalQueue;

// 定义动物收容所结构体
typedef struct {
    AnimalQueue* catQueue;  // 猫队列
    AnimalQueue* dogQueue;  // 狗队列
    int timestamp;          // 全局时间戳计数器
} AnimalShelter;

// 函数声明
AnimalShelter* createAnimalShelter();
void enqueue(AnimalShelter* shelter, int id, int species);
int* dequeueAny(AnimalShelter* shelter);
int* dequeueDog(AnimalShelter* shelter);
int* dequeueCat(AnimalShelter* shelter);
void freeAnimalShelter(AnimalShelter* shelter);

// 主函数:程序入口
int main() {
    // 创建动物收容所实例
    AnimalShelter* shelter = createAnimalShelter();
    // 检查内存分配是否成功
    if (shelter == NULL) {
        fprintf(stderr, "内存分配失败\n");  // 输出错误信息
        return 1;  // 异常退出
    }

    // 示例操作序列
    printf("操作序列:\n");
    printf("1. enqueue([0,0])\n");
    printf("2. enqueue([1,0])\n");
    printf("3. enqueue([2,1])\n");
    printf("4. dequeueDog()\n");
    printf("5. dequeueCat()\n");
    printf("6. dequeueAny()\n");

    // 执行示例操作
    enqueue(shelter, 0, 0);  // 入队猫0
    enqueue(shelter, 1, 0);  // 入队猫1
    enqueue(shelter, 2, 1);  // 入队狗2

    // 执行出队操作并打印结果
    int* result;
    result = dequeueDog(shelter);  // 出队狗
    printf("dequeueDog() -> [%d, %d]\n", result[0], result[1]);
    free(result);  // 释放结果数组
    
    result = dequeueCat(shelter);  // 出队猫
    printf("dequeueCat() -> [%d, %d]\n", result[0], result[1]);
    free(result);  // 释放结果数组

    result = dequeueAny(shelter);  // 出队任意
    printf("dequeueAny() -> [%d, %d]\n", result[0], result[1]);
    free(result);  // 释放结果数组

    // 释放收容所内存
    freeAnimalShelter(shelter);
    return 0;  // 程序正常退出
}

// 创建动物收容所
AnimalShelter* createAnimalShelter() {
    // 分配收容所内存
    AnimalShelter* shelter = (AnimalShelter*)malloc(sizeof(AnimalShelter));
    if (shelter == NULL) return NULL;  // 内存分配失败

    // 分配猫队列内存
    shelter->catQueue = (AnimalQueue*)malloc(sizeof(AnimalQueue));
    if (shelter->catQueue == NULL) {
        free(shelter);  // 释放已分配内存
        return NULL;    // 内存分配失败
    }
    // 初始化猫队列
    shelter->catQueue->head = NULL;
    shelter->catQueue->tail = NULL;

    // 分配狗队列内存
    shelter->dogQueue = (AnimalQueue*)malloc(sizeof(AnimalQueue));
    if (shelter->dogQueue == NULL) {
        free(shelter->catQueue);  // 释放已分配内存
        free(shelter);             // 释放已分配内存
        return NULL;               // 内存分配失败
    }
    // 初始化狗队列
    shelter->dogQueue->head = NULL;
    shelter->dogQueue->tail = NULL;

    // 初始化时间戳
    shelter->timestamp = 0;
    return shelter;  // 返回创建的收容所
}

// 动物入队操作
void enqueue(AnimalShelter* shelter, int id, int species) {
    // 创建新动物节点
    AnimalNode* newAnimal = (AnimalNode*)malloc(sizeof(AnimalNode));
    if (newAnimal == NULL) return;  // 内存分配失败

    // 设置动物属性
    newAnimal->id = id;
    newAnimal->species = species;
    newAnimal->timestamp = shelter->timestamp;  // 分配当前时间戳
    newAnimal->next = NULL;  // 新节点在队尾,next置空

    // 更新全局时间戳
    shelter->timestamp++;

    // 根据动物种类选择队列
    AnimalQueue* targetQueue;
    if (species == 0) {  // 猫
        targetQueue = shelter->catQueue;
    } else {  // 狗
        targetQueue = shelter->dogQueue;
    }

    // 将动物加入队列
    if (targetQueue->tail == NULL) {  // 队列为空
        targetQueue->head = newAnimal;
        targetQueue->tail = newAnimal;
    } else {  // 队列非空
        targetQueue->tail->next = newAnimal;  // 当前尾节点指向新节点
        targetQueue->tail = newAnimal;        // 更新尾指针
    }
}

// 出队任意动物
int* dequeueAny(AnimalShelter* shelter) {
    // 分配结果数组内存 (2个整数)
    int* result = (int*)malloc(2 * sizeof(int));
    if (result == NULL) return NULL;  // 内存分配失败

    // 检查猫队列和狗队列是否都为空
    if (shelter->catQueue->head == NULL && shelter->dogQueue->head == NULL) {
        result[0] = -1;  // 返回-1表示无动物
        result[1] = -1;
        return result;
    }

    // 比较两个队列头部的时间戳
    int catTime = (shelter->catQueue->head != NULL) ?
                 shelter->catQueue->head->timestamp : __INT_MAX__;  // 猫队列时间戳
    int dogTime = (shelter->dogQueue->head != NULL) ?
                 shelter->dogQueue->head->timestamp : __INT_MAX__;  // 狗队列时间戳

    // 选择时间戳更小的动物出队(更早入队)
    if (catTime <= dogTime) {
        return dequeueCat(shelter);  // 出队猫
    } else {
        return dequeueDog(shelter);  // 出队狗
    }
}

// 出队狗
int* dequeueDog(AnimalShelter* shelter) {
    // 分配结果数组内存 (2个整数)
    int* result = (int*)malloc(2 * sizeof(int));
    if (result == NULL) return NULL;  // 内存分配失败

    // 检查狗队列是否为空
    if (shelter->dogQueue->head == NULL) {
        result[0] = -1;  // 返回-1表示无狗
        result[1] = -1;
        return result;
    }

    // 获取队列头部狗节点
    AnimalNode* dog = shelter->dogQueue->head;

    // 设置返回结果
    result[0] = dog->id;
    result[1] = dog->species;

    // 更新队列头指针
    shelter->dogQueue->head = dog->next;

    // 如果队列变为空,更新尾指针
    if (shelter->dogQueue->head == NULL) {
        shelter->dogQueue->tail = NULL;
    }

    // 释放出队节点内存
    free(dog);
    return result;  // 返回结果
}

// 出队猫
int* dequeueCat(AnimalShelter* shelter) {
    // 分配结果数组内存 (2个整数)
    int* result = (int*)malloc(2 * sizeof(int));
    if (result == NULL) return NULL;  // 内存分配失败

    // 检查猫队列是否为空
    if (shelter->catQueue->head == NULL) {
        result[0] = -1;  // 返回-1表示无猫
        result[1] = -1;
        return result;
    }

    // 获取队列头部猫节点
    AnimalNode* cat = shelter->catQueue->head;

    // 设置返回结果
    result[0] = cat->id;
    result[1] = cat->species;

    // 更新队列头指针
    shelter->catQueue->head = cat->next;

    // 如果队列变为空,更新尾指针
    if (shelter->catQueue->head == NULL) {
        shelter->catQueue->tail = NULL;
    }

    // 释放出队节点内存
    free(cat);
    return result;  // 返回结果
}

// 释放动物收容所内存
void freeAnimalShelter(AnimalShelter* shelter) {
    // 释放猫队列中的剩余节点
    AnimalNode* current = shelter->catQueue->head;
    while (current != NULL) {
        AnimalNode* next = current->next;  // 保存下一个节点
        free(current);                     // 释放当前节点
        current = next;                    // 移动到下一个节点
    }
    // 释放狗队列中的剩余节点
    current = shelter->dogQueue->head;
    while (current != NULL) {
        AnimalNode* next = current->next;  // 保存下一个节点
        free(current);                     // 释放当前节点
        current = next;                    // 移动到下一个节点
    }

    // 释放队列结构体
    free(shelter->catQueue);
    free(shelter->dogQueue);

    // 释放收容所结构体
    free(shelter);
}

输出结果:


第三章:双星对比——搜索与秩序的算法哲学

对比图表:问题本质与解法核心

维度节点间通路(图搜索)动物收容所(队列管理)
问题类型连通性判定(图论)有序资源调度(数据结构)
核心操作路径搜索(BFS/DFS)入队/出队(FIFO策略)
数据结构邻接表(稀疏图高效存储)双队列+时间戳(时序维护)
时间复杂度O(n+e)O(n+e)(遍历节点与边)O(1)O(1)(队列操作)
空间复杂度O(n+e)O(n+e)(邻接表+访问标记)O(n)O(n)(双队列存储)
关键挑战自环/重边处理、大规模图优化跨队列时序比较、空队列处理
算法思想状态空间探索(穷举可能路径)秩序维护(严格时序优先级)

本质联系与分岔

  • 共性:二者均依赖高效的数据组织(邻接表 vs 双队列)与状态管理visited 数组 vs 时间戳)。

  • 分岔

    • 图搜索是横向扩展(BFS的层序遍历)或纵向深入(DFS的路径回溯),目标是覆盖可能性

    • 队列管理是时序维护,通过时间戳将多维选择(猫/狗/任意)压缩为一维时序比较,实现决策简化


第四章:思维升华——从算法到系统设计
  1. 图搜索的工程映射

    • 社交网络好友关系(节点=用户,边=关注),判断用户A是否间接关注用户B(路径存在性)。

    • 优化启示:大规模图需分布式BFS(如Pregel模型)。

  2. 队列管理的现实镜像

    • 多优先级任务调度(如操作系统进程队列),dequeueAny 对应选择最早提交的任务。

    • 扩展思考:若增加动物优先级(如伤病紧急度),可引入优先队列替代FIFO队列。

节点间通路,是图论中一曲穿越迷宫的探戈;动物收容所,是队列间一首秩序井然的交响诗。算法之美,正在于将混沌抽象为模型,用数学谱写秩序。当我们以BFS之刃劈开图论荆棘,以双队列之梭编织时序经纬,便是在问题与解的交界处——雕刻出计算机科学的理性丰碑。

后记:每一个算法问题,都是对现实世界的一次隐喻。下一次当你迷失在生活的迷宫中,不妨想想BFS的勇气;当你面对纷杂选择时,请回忆时间戳的公正。这便是算法赠予我们的智慧箴言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

司铭鸿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值