BUPT · 数据结构课程项目 · 2026

个性化旅游系统

基于 Rust 自定义数据结构与算法,覆盖推荐、路线规划、
设施查询、旅游日记、社区搜索与美食推荐的全旅程平台

谢佳恒
王逸晨
蔡芝林
Rust Axum SQLite React Tauri
扫码访问 dscp.meltyland.dev
dscp.meltyland.dev 扫码在线访问

六大核心功能

覆盖完整旅行生命周期

01

旅游推荐

基于 Top-K 堆算法的个性化景点推荐,评分可解释,支持类别与关键词检索

02

路线规划

Dijkstra 最短路径 + 最近邻启发式多点旅游路线,2-opt 局部优化

03

地点查询

有界 Dijkstra 扩展,基于图路径距离(非欧氏)的周边设施分类搜索

04

旅游日记

日记创建与管理,支持 Huffman 无损压缩存储,降低磁盘占用

05

日记社区

2-gram 倒排索引全文搜索,综合 gram 命中、评分与热度的复合排名

06

美食推荐

菜系偏好 × 图路径距离 × 热度融合的 Top-K 美食排名推荐

解决旅游信息碎片化

现有旅游 App 各司其职、数据割裂,本系统覆盖完整旅行生命周期

现状困境
行前
小红书 / 马蜂窝
查攻略 · 看推荐
数据断层
行中
百度地图 / 高德
导航 · 查周边
数据断层
行后
微博 / 朋友圈
发日记 · 晒照片

三款 App 各自孤岛,行前推荐的景点无法关联行中导航,游记也无法与目的地数据互通

本系统方案
行前
景点 · 美食推荐
Top-K 个性化筛选
数据打通
行中
路线规划 · 场所查询
实际路网距离导航
数据打通
行后
日记管理 · 社区交流
全文检索 · 目的地关联

目的地数据与日记社区完全打通,推荐与游记互相增强,一个系统管理全旅程

智能体如何打通全流程

三个场景化智能体,共享同一份目的地数据图谱,协同串联旅行的每个阶段。
这是使用三个独立 App 无法实现的能力:数据在阶段之间自由流动。

行前
行程规划助手

告诉它目的地、天数和偏好,它从景点库检索 Top-K 推荐、自动规划多点游览路线, 同时从社区日记里找到走过同类路线旅行者的真实经历作为参考—— 一次对话完成过去需要三个 App 才能做的事。

景点推荐 API 路线规划 API 日记社区搜索
行中
实时导游助手

旅途中直接用自然语言提问。"附近有没有素食餐厅"调用路网距离搜索; "从这里去颐和园怎么走最快"调用最短路径;"周边评分最高的景点是什么"调用推荐引擎。 所有问答背后是同一份地图数据,回答考虑实际路网而非直线距离。

周边查询 API 最短路径 API 美食推荐 API
行后
游记创作助手

回家后它已经知道你走过哪些地方。基于行程记录自动生成日记草稿、 补充景点背景介绍,推荐社区里相似路线的游记供参考—— 因为行中的导航轨迹与行后的日记内容共享同一个目的地图谱,创作不再从零开始。

日记管理 API 社区全文搜索 Huffman 压缩存储
创新拓展

视觉导览 Agent:面向复杂景区与室内场馆的轻量导航

在大型景区、博物馆、园区室内展馆等场景中,传统地图常常无法解决“我现在到底在哪”的问题。 本系统提出一个可扩展的视觉导览方案:让智能体先整合公开景区观览地图、楼层图、常见迷路节点与社交媒体攻略, 再结合视觉大语言模型识别用户现场照片中的路标、建筑立面、展陈信息与环境特征,推断用户大致位置并给出导览建议。

01

多源资料自动整合

Agent 自动抓取景区公开导览图、分区说明、园内设施位置、常见岔路口信息, 同时从小红书等攻略内容中提炼“容易走错”“适合拍照”“排队时间长”等经验标签, 构建一张面向游客理解而非仅面向机器导航的语义地图。

02

照片定位与场景理解

用户上传当前拍摄的照片后,视觉模型识别画面中的指示牌、店铺名、建筑轮廓、展馆编号和道路结构, 将视觉线索与语义地图匹配,输出“你大概率位于哪个区域、附近有哪些关键节点、下一步应朝哪里走”的判断结果。

03

导览解释而非单纯报坐标

系统不仅返回位置,还会用自然语言解释依据,例如“你左侧是游客服务中心,前方指示牌显示你靠近东门环线”, 并进一步推荐最近卫生间、出口、热门打卡点或下一展厅,适合地图感较弱、容易迷路的游客使用。

视觉导览交互示例
AI
视觉导览 Agent

根据你上传的照片,我识别到右侧木质指示牌上的“兰亭展区”和前方岔路口的水景装置, 你当前大概率位于景区中部环线与兰亭支路的交汇处。继续直行约 120 米可到主展馆, 左转 80 米可到最近卫生间;如果你想去热门拍照点,建议沿水景方向前往观景平台。

该能力定位为本项目的创新拓展方向:它把“公开地图 + 游客经验 + 多模态识别”结合起来, 使系统从传统路线规划延伸到复杂场景下的弱定位导览,体现智能体在旅游系统中的主动感知与解释能力。

智能体主动介入

反向规划:从「最值得记录的瞬间」出发

传统路线规划以景点为起点——给定目的地,算出最优路径。本系统翻转这个逻辑: 先从数千篇游记里挖掘出哪些地点、哪些时段反复触发深度叙事,再以此驱动行程推荐。 景点相同,时机不同,体验天壤之别。而这张「幸福热点地图」只存在于叙事语料里, 任何只有评分和浏览量的系统都看不见它。

传统规划逻辑
给定景点 算最优路径 出行

以「去哪里」为核心,忽略「什么时候去」带来的体验差异

vs
本系统反向规划
挖掘游记 提炼幸福热点 驱动行程

以「哪些瞬间让人写下最难忘的文字」为核心,将集体经验转化为个人行程

01

语料分析

对全库游记进行 2-gram 全文索引,识别高频出现的地点×时段组合,提取字数、情感密度、被引用次数等叙事质量信号

02

热点提炼

将叙事质量信号叠加到目的地图谱上,形成「旅行幸福热点地图」——不是评分高的景点,而是在此人们往往写下最难忘文字的节点与时段

03

主动介入

智能体在你规划行程时将热点时机注入推荐结果;旅途中提前告知前方节点的最佳体验时段,不等你发问

智能体介入示例
AI
行程规划助手

颐和园的昆明湖畔在周三下午 3—5 点触发了游记库中质量最高的 47 篇叙事,字数中位数是同景点其他时段的 2.3 倍。您当前计划的到访时间为上午 10 点——是否考虑调整至下午,与长廊游览连排?届时光线和人流也更适合拍摄。

系统架构

五层分层设计,职责边界清晰,算法层完全自实现

展示层
React 页面 地图可视化 算法基准测试UI 日记编辑器
Tauri IPC / HTTP
桌面容器层
Tauri Shell 跨平台打包
REST API
服务层
Rust + Axum 11 个 REST 端点 SQLite 持久化 Tokio 异步运行时
函数调用
算法层 核心
MinHeap<T> AdjacencyListGraph Top-K 引擎 2-gram 搜索引擎 Huffman 编码树
读写
数据管道层
OpenStreetMap 提取 数据归一化脚本 种子数据加载
200+
目的地节点
200+
道路边
11
API 端点
5
自定义数据结构

核心数据结构

全部以 Rust 自定义实现;每个结构直接支撑系统中的具体功能模块

MinHeap<T: Ord>

功能01 旅游推荐 功能02 路线规划 功能03 地点查询 功能04 日记压缩 功能06 美食推荐

本项目最基础的数据结构,被所有其他算法复用。Dijkstra 用它管理待访问节点的优先队列;Top-K 推荐引擎用它流式筛选最高分景点和美食;Huffman 树构建时用它按频率贪心合并节点。维护最小元素在根部,push/pop 均 O(log n)。

时间复杂度
push O(log n) · pop O(log n) · peek O(1)
核心操作
sift_up · sift_down
泛型约束
T: Ord(支持任意可比较类型)
应用场景
Dijkstra 优先队列 · Top-K 排名 · Huffman 树构建
min_heap.rs
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;
        }
    }
}
堆当前为空,点击「插入随机数」开始

邻接表有权图

功能02 路线规划 功能03 地点查询

存储景区路网的核心结构。路线规划调用 shortest_path() 在路网图上跑 Dijkstra,找到两点间最短行走路径;地点查询调用 shortest_distances_within(start, max_dist),从当前位置向外有界扩展,找出真实路网距离(而非直线距离)范围内的所有设施。支持有向/无向加权边。

存储方式
Vec<Vec<(NodeId, f64)>>
节点操作
add_node · add_directed/undirected_edge
最短路径
Dijkstra + 自定义 MinHeap,O((V+E) log V)
有界扩展
shortest_distances_within(start, max_dist)
graph.rs
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
}

邻接表结构示意 — 每个节点维护一个 (邻居, 权重) 列表

2-gram 倒排索引

功能05 日记社区搜索

日记社区的全文检索后端。用户在社区搜索"颐和园夕阳"时,系统将查询词切成字符对("颐和""和园""园夕""夕阳"),在倒排表中查出每篇日记的命中次数,再融合标题匹配加分、评分和热度,排出最相关的游记。中英文文本均支持,无需分词器。

索引结构
HashMap<String, Vec<DocId>>
分词方式
滑动窗口,连续字符对(中英均支持)
评分公式
hits×1.5 + title_boost + rating×2 + ln(views+1)
查询复杂度
O(gram_count × posting_list_len)
search.rs
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();

搜索「旅行」的倒排检索与评分过程

Huffman 编码树

功能04 旅游日记

旅游日记存储时的压缩引擎。写日记时系统统计文本字符频率,用 MinHeap 贪心构建 Huffman 树,将高频汉字用短码表示——"的""在""了"等频繁出现的字比低频字节省更多比特。/api/diaries/compress 端点可实时演示任意文本的压缩率和编码表,解压后与原文完全一致(可逆验证)。

构建算法
贪心合并,频率最小优先,O(n log n)
编码方式
左边 0,右边 1;变长前缀编码
存储格式
EncodedPayload { bytes, bit_len, code_table }
验证
解压后与原文完全一致(可逆验证)
search.rs — build_huffman_tree
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 编码树(叶节点显示编码)

算法演示

交互式逐步动画,直观感受核心算法执行过程

Dijkstra 最短路径

功能02 路线规划 功能03 地点查询

系统中两处直接调用此算法:路线规划/api/routes/shortest)在路网图上求两点间最短行程;地点查询/api/facilities/nearby)用有界版本向外扩展,找出路网距离 ≤ 阈值的所有设施。下方演示中节点 A→G 可类比"从酒店出发到目标景点"的一次查询,使用自定义 MinHeap 作优先队列。

时间 O((V+E) log V) 空间 O(V)
点击「开始演示」观察算法执行过程
节点ABCDEFG
dist
未访问
队列中
当前节点
已访问
最短路径

Top-K 堆选择

功能01 旅游推荐 功能06 美食推荐

系统中两处直接调用此算法:旅游推荐/api/recommend/destinations)从全量景点中流式筛选评分最高的 k 个;美食推荐/api/foods/live)将菜系偏好 × 路网距离 × 热度融合成综合分后取 Top-K。下方演示中每个数字代表一个景点/美食的综合得分,维护大小为 k 的最小堆保留得分最高的 k 个,O(n log k) 优于全排序 O(n log n)。

时间 O(n log k) 空间 O(k)
数据流 (k=3)
堆内部 (最小堆)
当前 Top-3
点击「下一个元素」开始流入数据

REST API 接口

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 健康检查

技术栈与性能目标

后端核心

  • Rust — 内存安全的系统语言
  • Axum — 异步 Web 框架
  • SQLite / rusqlite — 嵌入式持久化
  • Tokio — 异步运行时

前端与桌面

  • React + Vite — 前端框架
  • Tailwind CSS — 原子化样式
  • Tauri — 跨平台桌面封装
  • D3.js — 数据可视化

数据与工具链

  • OpenStreetMap — 地理数据源
  • Cargo Workspace — 2 个 crate
  • pnpm — 前端包管理
  • rusqlite — 种子数据入库

自定义数据结构

  • MinHeap<T> — 通用最小堆
  • AdjacencyListGraph — 有权图
  • TopKEngine — 排名引擎
  • DiarySearchEngine — 2-gram 搜索
  • HuffmanTree — 压缩编码树
< 300ms
推荐 / 美食查询
< 200ms
单源最短路径
< 300ms
全文日记搜索
< 10s
桌面冷启动

开发团队

北京邮电大学 2026 届 · 数据结构课程项目组

谢佳恒

后端架构 · 核心算法实现

RustAxum算法设计

王逸晨

前端开发 · API 接口设计

ReactTauriUI设计

蔡芝林

数据处理 · 测试验证

数据工程测试OSM