use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt::Debug;
use std::cmp::Ord;
// 定义图的节点和边
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Node<T> {
value: T,
}
impl<T> Node<T> {
pub fn new(value: T) -> Self {
Node { value }
}
}
#[derive(Debug)]
pub struct Edge<T> {
weight: T,
}
impl<T> Edge<T> {
pub fn new(weight: T) -> Self {
Edge { weight }
}
}
// 定义图数据结构
pub struct Graph<N, E> {
nodes: HashMap<N, HashSet<(N, E)>>,
}
impl<N, E> Graph<N, E>
where
N: Clone + Eq + Hash,
E: Debug,
{
pub fn new() -> Self {
Graph {
nodes: HashMap::new(),
}
}
// 添加节点
pub fn add_node(&mut self, node: N) {
self.nodes.entry(node.clone()).or_insert(HashSet::new());
}
// 添加边
pub fn add_edge(&mut self, from: N, to: N, edge: E) {
self.nodes.entry(from.clone()).or_insert(HashSet::new()).insert((to.clone(), edge.clone()));
self.nodes.entry(to).or_insert(HashSet::new()).insert((from, edge));
}
// 广度优先遍历
pub fn bfs(&self, start: &N) -> Vec<N> {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
let mut result = Vec::new();
queue.push_back(start.clone());
visited.insert(start.clone());
while let Some(node) = queue.pop_front() {
result.push(node.clone());
if let Some(neighbors) = self.nodes.get(&node) {
for (neighbor, _) in neighbors {
if!visited.contains(neighbor) {
queue.push_back(neighbor.clone());
visited.insert(neighbor.clone());
}
}
}
}
result
}
// 找到两个节点之间的最短路径
pub fn shortest_path(&self, start: &N, end: &N) -> Option<Vec<N>>
where
E: Ord,
{
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back((start.clone(), Vec::new()));
visited.insert(start.clone());
while let Some((current, path)) = queue.pop_front() {
if current == *end {
let mut full_path = path.clone();
full_path.push(current);
return Some(full_path);
}
if let Some(neighbors) = self.nodes.get(¤t) {
for (neighbor, _) in neighbors.iter().sorted_by_key(|(_, edge)| &edge.weight) {
if!visited.contains(neighbor) {
let mut new_path = path.clone();
new_path.push(current.clone());
queue.push_back((neighbor.clone(), new_path));
visited.insert(neighbor.clone());
}
}
}
}
None
}
}
泛型约束和trait bound的作用
-
N: Clone + Eq + Hash
:
Clone
:使得节点可以被复制,在添加节点和边,以及遍历和路径查找过程中,经常需要复制节点。例如在add_node
、add_edge
、bfs
和shortest_path
函数中,都可能涉及到节点的复制操作。
Eq
和Hash
:用于在HashMap
和HashSet
中作为键使用。HashMap
和HashSet
依赖Hash
和Eq
来高效地存储和查找节点,在Graph
结构的nodes
字段(HashMap
)以及visited
集合(HashSet
)中使用到。
-
E: Debug
:
- 允许对边进行调试打印。在实际开发中,调试时可能需要查看边的信息,比如边的权重等,实现
Debug
trait 方便开发者进行调试输出,如在add_edge
函数中,虽然当前代码未使用,但为后续调试提供了便利。
-
E: Ord
in shortest_path
:
- 在寻找最短路径时,需要对边的权重进行排序,
Ord
trait 提供了比较的能力,使得可以根据边的权重进行排序,如在shortest_path
函数中对邻居节点按边权重排序neighbors.iter().sorted_by_key(|(_, edge)| &edge.weight)
。
实际应用中的优势
- 灵活性:通过泛型,
Graph
数据结构可以处理各种类型的节点和边,适用于不同领域的图问题,比如社交网络(节点可能是用户,边可能是关系),地图导航(节点可能是地点,边可能是距离)等。
- 代码复用:相同的图操作(添加节点、边,遍历等)可以在不同类型的图上复用,提高开发效率。
- 类型安全:trait bound 确保了在使用图的各种操作时,节点和边类型具备必要的功能,避免运行时错误。
可能遇到的问题
- 性能问题:在某些情况下,泛型的使用可能导致代码膨胀,因为编译器会为每种具体类型生成一份代码。如果图结构非常复杂且使用了多种不同类型的节点和边,可能会增加编译后的二进制文件大小。
- 类型不匹配问题:如果在使用图时没有满足泛型约束,编译时会报错。例如,若尝试在
shortest_path
中使用不实现Ord
的边类型,编译器会指出错误,但这需要开发者对trait bound有清晰的理解,否则定位和解决问题可能比较困难。
- 调试困难:由于泛型代码的复杂性,调试时可能不太直观,尤其是在trait bound未正确实现导致运行时错误的情况下,错误信息可能比较难以理解。