每日一题——寻找最便宜的地铁换乘方案

🚇 寻找最便宜的地铁换乘方案(图的最短路径+0-1 BFS)

🧾 题目描述

A市运营了 N N N 条地铁线路,乘坐地铁时:

  • 单条线路通票为 2 元;
  • 每换乘一次加收 1 元;

每条地铁线路用一个站名列表表示,同一个站名可能出现在不同线路中,表示可以在该站换乘。每条线路不包含环路(即不会有重复站名)。

给定地铁线路和出发站-终点站,要求输出:

  1. 最便宜的乘坐方案(按出发站-换乘站-终点站格式);
  2. 最低票价;

如果没有方案,输出 NA

📥 输入格式

第一行:N(1 <= N <= 100)地铁线路数量  
接下来的 N 行:每条地铁线路的站名(最多 100 个站名,每个站名最长 20 个字符)  
第 N+2 行:出发站 终点站

📤 输出格式

若存在方案:

站点1-换乘站-站点n
票价

若不存在方案:

NA

💡 解题思路

✅ 图建模

  • 每个站点在每条线上的位置都视作一个唯一状态;
  • 同一条线上的相邻站点之间连一条权重为0的边(不换乘);
  • 同一站名在不同线路上的位置之间连一条权重为1的边(换乘);

✅ 最短路径搜索

使用0-1 BFS(双端队列),优先走权重为0的边,这样就能确保最少换乘次数。

✅ 路径恢复

记录每个节点的前驱节点,最终回溯得到完整路径,再提取换乘站。

✅ 票价计算

票价 = 2 + (换乘次数) × 1 票价 = 2 + (换乘次数)×1 票价=2+(换乘次数)×1


🧱 数据结构说明

名称类型说明
Station结构体表示每个站的位置信息
Line数组每条线路的所有站名
Map哈希映射站名 -> 所在的线路和位置数组
dist[][]整型数组表示最短路径中每个位置的最小换乘次数
prev[][]整型数组记录前驱节点以回溯路径
queue[]队列实现 0-1 BFS

🧾 C语言代码实现

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_LINE 100         // 最多支持的地铁线路数
#define MAX_STATION 100      // 每条线路最多站点数
#define MAX_NAME 25          // 每个站点名最大长度
#define INF 1e9              // 表示无穷大,用于初始化距离

// 表示一个站点的位置:在哪条线路的第几个站
typedef struct {
    int line;  // 线路编号
    int pos;   // 在线路中的站点索引
} Node;

// 自定义双端队列结构体,用于 0-1 BFS
typedef struct {
    Node data[MAX_LINE * MAX_STATION]; // 存放节点
    int front, back;                   // 双端队列的头尾指针
} Deque;

// 从队列前端插入(用于权重为0的边,如同一条线的移动)
void push_front(Deque* dq, Node n) {
    dq->data[--dq->front] = n;
}

// 从队列后端插入(用于权重为1的边,如换乘)
void push_back(Deque* dq, Node n) {
    dq->data[dq->back++] = n;
}

// 从队列前端弹出
Node pop_front(Deque* dq) {
    return dq->data[dq->front++];
}

// 判断队列是否为空
int is_empty(Deque* dq) {
    return dq->front >= dq->back;
}

int N;  // 地铁线路数量
char line[MAX_LINE][MAX_STATION][MAX_NAME]; // 存储所有地铁线路和站点名
int line_len[MAX_LINE];                     // 每条线路的站点数量
int dist[MAX_LINE][MAX_STATION];            // 最少换乘次数
Node prev[MAX_LINE][MAX_STATION];           // 路径回溯信息
char start[MAX_NAME], end[MAX_NAME];        // 起点站和终点站名

// 哈希映射结构,用于存储同一站点名出现在多条线路的哪个位置
typedef struct {
    char name[MAX_NAME];          // 站名
    Node pos[20];                 // 这个站名在多个线路的位置
    int cnt;                      // 该站点出现的次数
} StationMap;

StationMap station_map[10000];  // 所有站点映射
int station_map_cnt = 0;        // 当前记录的站点数量

// 在哈希表中查找站点名,若不存在则新建一个
int get_station_id(const char* name) {
    for (int i = 0; i < station_map_cnt; ++i) {
        if (strcmp(name, station_map[i].name) == 0)
            return i;  // 找到了,返回索引
    }
    // 没找到,添加新的
    strcpy(station_map[station_map_cnt].name, name);
    return station_map_cnt++;
}

// 将某个站点加入站点哈希表映射
void add_station(const char* name, int line, int pos) {
    int id = get_station_id(name);                  // 查找/获取该站名在 map 中的位置
    station_map[id].pos[station_map[id].cnt].line = line;  // 记录线路
    station_map[id].pos[station_map[id].cnt].pos = pos;    // 记录在线路中的位置
    station_map[id].cnt++;                         // 出现次数+1
}

// 输出最短路径
void print_path(Node n) {
    Node path[MAX_LINE * MAX_STATION];
    int path_len = 0;
    // 回溯prev数组,恢复路径
    while (n.line != -1) {
        path[path_len++] = n;
        n = prev[n.line][n.pos];
    }
    // 倒序打印路径
    for (int i = path_len - 1; i >= 0; --i) {
        printf("%s", line[path[i].line][path[i].pos]);
        if (i > 0) {
            if (path[i].line != path[i - 1].line)
                printf("-");  // 如果换乘,打印 -
        }
    }
    printf("\n");
}

int main() {
    scanf("%d", &N); // 读取线路数量
    getchar();       // 读取换行符
    for (int i = 0; i < N; ++i) {
        char temp[1000];
        fgets(temp, sizeof(temp), stdin); // 读取整条线路
        int idx = 0, len = 0;
        // 使用 sscanf 和字符串偏移提取站名
        while (sscanf(temp + idx, "%s", line[i][len]) == 1) {
            add_station(line[i][len], i, len); // 将该站点加入哈希表
            idx += strlen(line[i][len]);       // 移动索引到下一个单词
            while (temp[idx] == ' ') ++idx;    // 跳过空格
            len++; // 站点数增加
        }
        line_len[i] = len; // 记录当前线路的站点数量
    }

    // 读取起点和终点
    scanf("%s %s", start, end);

    // 初始化距离数组为无穷大,路径为无效
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < line_len[i]; ++j) {
            dist[i][j] = INF;
            prev[i][j].line = -1;
        }

    // 初始化双端队列,中间开始防止越界
    Deque dq = {5000, 5000};

    // 将所有与起点名字相同的站点加入队列,作为搜索起点
    int start_id = get_station_id(start);
    for (int k = 0; k < station_map[start_id].cnt; ++k) {
        Node s = station_map[start_id].pos[k];
        dist[s.line][s.pos] = 0;       // 起点的距离为0
        push_front(&dq, s);           // 加入队列
    }

    Node dest = {-1, -1};  // 目标站点
    int found = 0;         // 是否找到目标

    // 0-1 BFS搜索
    while (!is_empty(&dq)) {
        Node cur = pop_front(&dq); // 取出当前节点
        char* name = line[cur.line][cur.pos];

        // 如果到了终点
        if (strcmp(name, end) == 0) {
            dest = cur;
            found = 1;
            break;
        }

        // 向左移动(同一线路)
        if (cur.pos > 0 && dist[cur.line][cur.pos - 1] > dist[cur.line][cur.pos]) {
            dist[cur.line][cur.pos - 1] = dist[cur.line][cur.pos];
            prev[cur.line][cur.pos - 1] = cur;
            push_front(&dq, (Node){cur.line, cur.pos - 1});
        }

        // 向右移动(同一线路)
        if (cur.pos + 1 < line_len[cur.line] && dist[cur.line][cur.pos + 1] > dist[cur.line][cur.pos]) {
            dist[cur.line][cur.pos + 1] = dist[cur.line][cur.pos];
            prev[cur.line][cur.pos + 1] = cur;
            push_front(&dq, (Node){cur.line, cur.pos + 1});
        }

        // 在不同线路间换乘(相同站名,线路不同)
        int id = get_station_id(name);
        for (int k = 0; k < station_map[id].cnt; ++k) {
            Node next = station_map[id].pos[k];
            if (next.line != cur.line && dist[next.line][next.pos] > dist[cur.line][cur.pos] + 1) {
                dist[next.line][next.pos] = dist[cur.line][cur.pos] + 1;  // 换乘成本 +1
                prev[next.line][next.pos] = cur;
                push_back(&dq, next); // 换乘放队尾
            }
        }
    }

    // 输出结果
    if (!found) {
        printf("NA\n"); // 没有路径
    } else {
        print_path(dest);                         // 打印路径
        printf("%d\n", 2 + dist[dest.line][dest.pos]);  // 起点和终点都需要上车下车,加2
    }

    return 0;
}


🧪 示例

输入1

3
A B C D F
C E G H
B G I J
A J

输出1

A-B-J
3

输入2

3
A B C D F
E G H
G I J
A J

输出2

NA

🧠 总结

  • 利用了图建模 + 0-1 BFS策略快速找到最优路径;
  • dist[][] 记录最小换乘次数;
  • prev[][] 回溯恢复路径;
  • 最终输出路径和最小票价。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tt555555555555

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

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

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

打赏作者

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

抵扣说明:

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

余额充值