旅游推荐
基于 Top-K 堆算法的个性化景点推荐,评分可解释,支持类别与关键词检索
基于 Rust 自定义数据结构与算法,覆盖推荐、路线规划、
设施查询、旅游日记、社区搜索与美食推荐的全旅程平台
覆盖完整旅行生命周期
基于 Top-K 堆算法的个性化景点推荐,评分可解释,支持类别与关键词检索
Dijkstra 最短路径 + 最近邻启发式多点旅游路线,2-opt 局部优化
有界 Dijkstra 扩展,基于图路径距离(非欧氏)的周边设施分类搜索
日记创建与管理,支持 Huffman 无损压缩存储,降低磁盘占用
2-gram 倒排索引全文搜索,综合 gram 命中、评分与热度的复合排名
菜系偏好 × 图路径距离 × 热度融合的 Top-K 美食排名推荐
现有旅游 App 各司其职、数据割裂,本系统覆盖完整旅行生命周期
三款 App 各自孤岛,行前推荐的景点无法关联行中导航,游记也无法与目的地数据互通
目的地数据与日记社区完全打通,推荐与游记互相增强,一个系统管理全旅程
三个场景化智能体,共享同一份目的地数据图谱,协同串联旅行的每个阶段。
这是使用三个独立 App 无法实现的能力:数据在阶段之间自由流动。
告诉它目的地、天数和偏好,它从景点库检索 Top-K 推荐、自动规划多点游览路线, 同时从社区日记里找到走过同类路线旅行者的真实经历作为参考—— 一次对话完成过去需要三个 App 才能做的事。
旅途中直接用自然语言提问。"附近有没有素食餐厅"调用路网距离搜索; "从这里去颐和园怎么走最快"调用最短路径;"周边评分最高的景点是什么"调用推荐引擎。 所有问答背后是同一份地图数据,回答考虑实际路网而非直线距离。
回家后它已经知道你走过哪些地方。基于行程记录自动生成日记草稿、 补充景点背景介绍,推荐社区里相似路线的游记供参考—— 因为行中的导航轨迹与行后的日记内容共享同一个目的地图谱,创作不再从零开始。
在大型景区、博物馆、园区室内展馆等场景中,传统地图常常无法解决“我现在到底在哪”的问题。 本系统提出一个可扩展的视觉导览方案:让智能体先整合公开景区观览地图、楼层图、常见迷路节点与社交媒体攻略, 再结合视觉大语言模型识别用户现场照片中的路标、建筑立面、展陈信息与环境特征,推断用户大致位置并给出导览建议。
Agent 自动抓取景区公开导览图、分区说明、园内设施位置、常见岔路口信息, 同时从小红书等攻略内容中提炼“容易走错”“适合拍照”“排队时间长”等经验标签, 构建一张面向游客理解而非仅面向机器导航的语义地图。
用户上传当前拍摄的照片后,视觉模型识别画面中的指示牌、店铺名、建筑轮廓、展馆编号和道路结构, 将视觉线索与语义地图匹配,输出“你大概率位于哪个区域、附近有哪些关键节点、下一步应朝哪里走”的判断结果。
系统不仅返回位置,还会用自然语言解释依据,例如“你左侧是游客服务中心,前方指示牌显示你靠近东门环线”, 并进一步推荐最近卫生间、出口、热门打卡点或下一展厅,适合地图感较弱、容易迷路的游客使用。
根据你上传的照片,我识别到右侧木质指示牌上的“兰亭展区”和前方岔路口的水景装置, 你当前大概率位于景区中部环线与兰亭支路的交汇处。继续直行约 120 米可到主展馆, 左转 80 米可到最近卫生间;如果你想去热门拍照点,建议沿水景方向前往观景平台。
该能力定位为本项目的创新拓展方向:它把“公开地图 + 游客经验 + 多模态识别”结合起来, 使系统从传统路线规划延伸到复杂场景下的弱定位导览,体现智能体在旅游系统中的主动感知与解释能力。
传统路线规划以景点为起点——给定目的地,算出最优路径。本系统翻转这个逻辑: 先从数千篇游记里挖掘出哪些地点、哪些时段反复触发深度叙事,再以此驱动行程推荐。 景点相同,时机不同,体验天壤之别。而这张「幸福热点地图」只存在于叙事语料里, 任何只有评分和浏览量的系统都看不见它。
以「去哪里」为核心,忽略「什么时候去」带来的体验差异
以「哪些瞬间让人写下最难忘的文字」为核心,将集体经验转化为个人行程
对全库游记进行 2-gram 全文索引,识别高频出现的地点×时段组合,提取字数、情感密度、被引用次数等叙事质量信号
将叙事质量信号叠加到目的地图谱上,形成「旅行幸福热点地图」——不是评分高的景点,而是在此人们往往写下最难忘文字的节点与时段
智能体在你规划行程时将热点时机注入推荐结果;旅途中提前告知前方节点的最佳体验时段,不等你发问
颐和园的昆明湖畔在周三下午 3—5 点触发了游记库中质量最高的 47 篇叙事,字数中位数是同景点其他时段的 2.3 倍。您当前计划的到访时间为上午 10 点——是否考虑调整至下午,与长廊游览连排?届时光线和人流也更适合拍摄。
五层分层设计,职责边界清晰,算法层完全自实现
全部以 Rust 自定义实现;每个结构直接支撑系统中的具体功能模块
本项目最基础的数据结构,被所有其他算法复用。Dijkstra 用它管理待访问节点的优先队列;Top-K 推荐引擎用它流式筛选最高分景点和美食;Huffman 树构建时用它按频率贪心合并节点。维护最小元素在根部,push/pop 均 O(log n)。
struct MinHeap<T: Ord> { data: Vec<T> }
impl<T: Ord> MinHeap<T> {
fn push(&mut self, val: T) {
self.data.push(val);
self.sift_up(self.data.len() - 1);
}
fn pop(&mut self) -> Option<T> {
if self.data.is_empty() { return None; }
let last = self.data.pop().unwrap();
if self.data.is_empty() { return Some(last); }
let min = std::mem::replace(&mut self.data[0], last);
self.sift_down(0);
Some(min)
}
fn sift_up(&mut self, mut i: usize) {
while i > 0 {
let p = (i - 1) / 2;
if self.data[p] <= self.data[i] { break; }
self.data.swap(p, i);
i = p;
}
}
}
存储景区路网的核心结构。路线规划调用 shortest_path() 在路网图上跑 Dijkstra,找到两点间最短行走路径;地点查询调用 shortest_distances_within(start, max_dist),从当前位置向外有界扩展,找出真实路网距离(而非直线距离)范围内的所有设施。支持有向/无向加权边。
struct Graph {
adj: Vec<Vec<(usize, f64)>>,
node_count: usize,
}
fn shortest_path(&self, start: usize, goal: usize)
-> Option<PathResult>
{
let mut dist = vec![f64::INFINITY; self.node_count];
let mut prev = vec![None; self.node_count];
let mut heap = MinHeap::new();
dist[start] = 0.0;
heap.push((OrderedFloat(0.0), start));
while let Some((d, u)) = heap.pop() {
if u == goal { return Some(reconstruct(prev, u)); }
if d.0 > dist[u] { continue; }
for &(v, w) in &self.adj[u] {
let nd = dist[u] + w;
if nd < dist[v] {
dist[v] = nd;
prev[v] = Some(u);
heap.push((OrderedFloat(nd), v));
}
}
}
None
}
邻接表结构示意 — 每个节点维护一个 (邻居, 权重) 列表
日记社区的全文检索后端。用户在社区搜索"颐和园夕阳"时,系统将查询词切成字符对("颐和""和园""园夕""夕阳"),在倒排表中查出每篇日记的命中次数,再融合标题匹配加分、评分和热度,排出最相关的游记。中英文文本均支持,无需分词器。
fn build_index(docs: &[DiaryEntry])
-> HashMap<String, Vec<usize>>
{
let mut idx: HashMap<String, Vec<usize>>
= HashMap::new();
for (id, doc) in docs.iter().enumerate() {
let text = format!("{} {}", doc.title, doc.content);
let chars: Vec<char> = text.chars().collect();
for w in chars.windows(2) {
let gram: String = w.iter().collect();
idx.entry(gram).or_default().push(id);
}
}
idx
}
// 评分:融合相关性 + 热度 + 评分
score = gram_hits * 1.5
+ if title_match { 2.0 } else { 0.0 }
+ diary.rating * 2.0
+ (diary.views as f64 + 1.0).ln();
搜索「旅行」的倒排检索与评分过程
旅游日记存储时的压缩引擎。写日记时系统统计文本字符频率,用 MinHeap 贪心构建 Huffman 树,将高频汉字用短码表示——"的""在""了"等频繁出现的字比低频字节省更多比特。/api/diaries/compress 端点可实时演示任意文本的压缩率和编码表,解压后与原文完全一致(可逆验证)。
fn build_huffman_tree(
freq: &HashMap<char, usize>
) -> HuffmanNode {
// 用 MinHeap 以频率为键构建优先队列
let mut heap = MinHeap::new();
for (&ch, &f) in freq {
heap.push(HuffmanNode::leaf(ch, f));
}
// 贪心合并:每次取两个最小节点合并
while heap.len() > 1 {
let a = heap.pop().unwrap();
let b = heap.pop().unwrap();
heap.push(HuffmanNode::merge(a, b));
}
heap.pop().unwrap() // 根节点即完整编码树
}
字符串 "TRAVEL" 的 Huffman 编码树(叶节点显示编码)
交互式逐步动画,直观感受核心算法执行过程
系统中两处直接调用此算法:路线规划(/api/routes/shortest)在路网图上求两点间最短行程;地点查询(/api/facilities/nearby)用有界版本向外扩展,找出路网距离 ≤ 阈值的所有设施。下方演示中节点 A→G 可类比"从酒店出发到目标景点"的一次查询,使用自定义 MinHeap 作优先队列。
| 节点 | A | B | C | D | E | F | G |
|---|---|---|---|---|---|---|---|
| dist | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ |
系统中两处直接调用此算法:旅游推荐(/api/recommend/destinations)从全量景点中流式筛选评分最高的 k 个;美食推荐(/api/foods/live)将菜系偏好 × 路网距离 × 热度融合成综合分后取 Top-K。下方演示中每个数字代表一个景点/美食的综合得分,维护大小为 k 的最小堆保留得分最高的 k 个,O(n log k) 优于全排序 O(n log n)。
Rust + Axum 实现,11 个端点,JSON 统一响应格式
| 端点路径 | 方法 | 功能描述 | 核心算法 |
|---|---|---|---|
/api/recommend/destinations |
POST | 个性化景点推荐(请求驱动) | Top-K 堆 |
/api/recommend/destinations/live |
GET | Top-K 推荐(数据库驱动) | Top-K 堆 |
/api/routes/shortest |
POST | 单源最短路径查询 | Dijkstra |
/api/facilities/nearby |
POST | 周边设施分类搜索 | 有界 Dijkstra |
/api/diaries |
POST | 创建新旅游日记 | — |
/api/diaries/search |
POST | 全文日记检索(请求驱动) | 2-gram 倒排索引 |
/api/diaries/search/live |
POST | 全文检索(数据库驱动) | 2-gram 倒排索引 |
/api/diaries/compress |
POST | Huffman 压缩演示 | Huffman 编码 |
/api/benchmarks |
GET | 算法性能对比报告 | 堆选择 vs 全排序 |
/healthz |
GET | 健康检查 | — |
北京邮电大学 2026 届 · 数据结构课程项目组
后端架构 · 核心算法实现
前端开发 · API 接口设计
数据处理 · 测试验证