文章目录
🚇 寻找最便宜的地铁换乘方案(图的最短路径+0-1 BFS)
🧾 题目描述
A市运营了 N N N 条地铁线路,乘坐地铁时:
- 单条线路通票为 2 元;
- 每换乘一次加收 1 元;
每条地铁线路用一个站名列表表示,同一个站名可能出现在不同线路中,表示可以在该站换乘。每条线路不包含环路(即不会有重复站名)。
给定地铁线路和出发站-终点站,要求输出:
- 最便宜的乘坐方案(按
出发站-换乘站-终点站
格式); - 最低票价;
如果没有方案,输出 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[][]
回溯恢复路径; - 最终输出路径和最小票价。