赞
踩
二叉堆是一种特殊的完全二叉树,它可以分为最大堆和最小堆。在最大堆中,每个节点的值都大于或等于其子节点的值;而在最小堆中,每个节点的值都小于或等于其子节点的值。这种性质使得二叉堆可以有效地支持优先队列的操作,如插入、删除最大元素(最大堆)或删除最小元素(最小堆)。二叉堆通常通过数组来实现。
二叉堆按照节点的值的排列方式可以分为两种类型:最大堆和最小堆。
在最大堆中,任何一个父节点的值都大于或等于它的子节点的值。这意味着,树的根节点存储着整个树中的最大值。
与最大堆相反,在最小堆中,任何一个父节点的值都小于或等于它的子节点的值。这表示,树的根节点存储着整个树中的最小值。
结构性:二叉堆总是一棵完全二叉树。这意味着除了最后一层外,每一层都被完全填满,并且所有节点都尽可能地向左对齐。
有序性:对于最大堆而言,任意节点的值总是不小于其子节点的值;对于最小堆而言,则总是不大于其子节点的值。
二叉堆通常通过数组来实现。对于数组中的任意元素,其左子节点的位置是 2i+1,右子节点的位置是 2i+2,其中 i 是当前元素在数组中的索引(索引从0开始)。同样,任意节点的父节点的位置是 (i-1)/2。
插入:新元素被添加到数组的末尾,然后通过一系列上浮操作(如果它比父节点更大/更小),保持堆的有序性。
删除最大元素/最小元素(取决于是最大堆还是最小堆):通常删除根节点元素,然后将数组最后一个元素移到根节点位置,并通过一系列下沉操作保持堆的有序性。
查找最大元素/最小元素:直接返回根节点元素。
struct MinHeap<T> { elements: Vec<T>, } impl<T: Ord> MinHeap<T> { // 创建一个新的空堆 fn new() -> Self { MinHeap { elements: Vec::new() } } // 插入元素 fn push(&mut self, item: T) { self.elements.push(item); self.sift_up(self.elements.len() - 1); } // 弹出最小元素 fn pop(&mut self) -> Option<T> { if self.elements.is_empty() { None } else { self.elements.swap(0, self.elements.len() - 1); let result = self.elements.pop(); self.sift_down(0); result } } // 上浮调整 fn sift_up(&mut self, mut idx: usize) { while idx > 0 { let parent = (idx - 1) / 2; if self.elements[idx] < self.elements[parent] { self.elements.swap(idx, parent); idx = parent; } else { break; } } } // 下沉调整 fn sift_down(&mut self, mut idx: usize) { let len = self.elements.len(); loop { let left_child = 2 * idx + 1; let right_child = 2 * idx + 2; let mut smallest = idx; if left_child < len && self.elements[left_child] < self.elements[smallest] { smallest = left_child; } if right_child < len && self.elements[right_child] < self.elements[smallest] { smallest = right_child; } if smallest != idx { self.elements.swap(idx, smallest); idx = smallest; } else { break; } } } } fn main() { let mut heap = MinHeap::new(); heap.push(5); heap.push(2); heap.push(8); heap.push(3); while let Some(min) = heap.pop() { println!("{}", min); } } #[cfg(test)] mod tests { use super::MinHeap; #[test] fn test_push_and_pop() { let mut heap = MinHeap::new(); heap.push(3); heap.push(2); heap.push(1); assert_eq!(heap.pop(), Some(1)); assert_eq!(heap.pop(), Some(2)); assert_eq!(heap.pop(), Some(3)); assert_eq!(heap.pop(), None); } #[test] fn test_peek_min() { let mut heap = MinHeap::new(); assert_eq!(heap.pop(), None); // 测试空堆 heap.push(5); heap.push(1); heap.push(3); assert_eq!(heap.pop(), Some(1)); // 最小值应该是1 assert_eq!(heap.pop(), Some(3)); // 接下来是3 assert_eq!(heap.pop(), Some(5)); // 然后是5 } }
邻接矩阵是表示图的一种方法,适用于存储稠密图。它是一个二维数组,其中的元素表示图中顶点之间是否存在边。对于无权图,矩阵中的元素为0(表示没有边)或1(表示有边);对于有权图,则存储边的权重。邻接矩阵的主要缺点是空间复杂度较高,特别是对于稀疏图。
对于一个包含n个顶点的图,其邻接矩阵是一个n×n的矩阵,记为A。如果顶点i和顶点j之间存在一条边,则矩阵中的元素A[i][j]表示这条边的权重;如果不存在边,则A[i][j]的值通常设为0(对于无权图)或某个特定的非负值,如无穷大(对于有权图),表示两个顶点之间没有直接的连接。
邻接矩阵适合用于表示稠密图(边数接近顶点数平方),或者当需要频繁查询两个顶点之间的连接关系时。对于稀疏图,邻接表可能是更好的选择,因为它能更高效地利用空间。
#[derive(Debug)] struct Graph { adj_matrix: Vec<Vec<Option<u32>>>, } impl Graph { // 创建一个新图 fn new(size: usize) -> Self { let adj_matrix = vec![vec![None; size]; size]; Graph { adj_matrix } } // 添加边 fn add_edge(&mut self, src: usize, dest: usize) { if src < self.adj_matrix.len() && dest < self.adj_matrix.len() { self.adj_matrix[src][dest] = Some(1); // 对于无向图,还需要添加下面这行: // self.adj_matrix[dest][src] = Some(1); } } // 删除边 fn remove_edge(&mut self, src: usize, dest: usize) { if src < self.adj_matrix.len() && dest < self.adj_matrix.len() { self.adj_matrix[src][dest] = None; // 对于无向图,还需要添加下面这行: // self.adj_matrix[dest][src] = None; } } // 检查两个顶点之间是否存在边 fn has_edge(&self, src: usize, dest: usize) -> bool { if src < self.adj_matrix.len() && dest < self.adj_matrix.len() { self.adj_matrix[src][dest].is_some() } else { false } } } fn main() { let mut graph = Graph::new(5); graph.add_edge(0, 1); graph.add_edge(1, 2); graph.add_edge(2, 3); graph.add_edge(3, 4); graph.add_edge(4, 0); println!("{:?}", graph); println!("Has edge between 0 and 1: {}", graph.has_edge(0, 1)); println!("Has edge between 1 and 3: {}", graph.has_edge(1, 3)); graph.remove_edge(0, 1); println!("Has edge between 0 and 1 after removal: {}", graph.has_edge(0, 1)); } #[cfg(test)] mod tests { use super::Graph; #[test] fn test_add_and_check_edge() { let mut graph = Graph::new(5); assert_eq!(graph.has_edge(0, 1), false); graph.add_edge(0, 1); assert_eq!(graph.has_edge(0, 1), true); } #[test] fn test_remove_edge() { let mut graph = Graph::new(5); graph.add_edge(0, 1); assert_eq!(graph.has_edge(0, 1), true); graph.remove_edge(0, 1); assert_eq!(graph.has_edge(0, 1), false); } #[test] fn test_edge_does_not_exist() { let graph = Graph::new(5); // 测试一个不存在的边 assert_eq!(graph.has_edge(1, 4), false); } }
优先队列是一种特殊的队列,其中的元素被赋予优先级。当访问元素时,具有最高优先级的元素最先被删除。优先队列通常通过堆(特别是二叉堆)来实现,以支持高效的插入和删除操作。在最小堆实现的优先队列中,具有最小值的元素具有最高优先级。
优先队列可以通过不同的数据结构实现,包括但不限于:
优先队列在多个领域都有广泛应用,包括:
主要操作包括:
use std::collections::BinaryHeap; use std::cmp::Reverse; struct PriorityQueue<T> { heap: BinaryHeap<Reverse<T>>, } impl<T: Ord> PriorityQueue<T> { // 创建一个新的空的优先队列 fn new() -> Self { PriorityQueue { heap: BinaryHeap::new(), } } // 向优先队列中添加元素 fn push(&mut self, item: T) { self.heap.push(Reverse(item)); } // 移除并返回优先级最高的元素 fn pop(&mut self) -> Option<T> { self.heap.pop().map(|Reverse(item)| item) } // 查看优先级最高的元素 fn peek(&self) -> Option<&T> { self.heap.peek().map(|Reverse(item)| item) } } fn main() { let mut pq = PriorityQueue::new(); pq.push(3); pq.push(5); pq.push(1); println!("{:?}", pq.pop()); // 输出:Some(5) println!("{:?}", pq.peek()); // 输出:Some(3) println!("{:?}", pq.pop()); // 输出:Some(3) println!("{:?}", pq.pop()); // 输出:Some(1) println!("{:?}", pq.pop()); // 输出:None } #[cfg(test)] mod tests { use super::PriorityQueue; #[test] fn test_priority_queue() { let mut pq = PriorityQueue::new(); assert_eq!(pq.pop(), None); pq.push(3); pq.push(5); pq.push(1); assert_eq!(pq.peek(), Some(&1)); assert_eq!(pq.pop(), Some(1)); assert_eq!(pq.pop(), Some(3)); assert_eq!(pq.pop(), Some(5)); assert_eq!(pq.pop(), None); } }
斐波那契堆是一种优先队列的实现,它是一种近似完全无序的数据结构,由一组堆有序树组成。相比于二叉堆,斐波那契堆在删除最小元素和合并两个堆时提供了更好的平摊分析性能。它支持多种操作,包括插入、合并、减少键值和删除节点。斐波那契堆的优点在于其操作的低摊销成本,特别适用于那些插入和删除操作频繁的场景。
斐波那契堆的主要特点包括:
非严格的二叉堆结构:斐波那契堆由一组树的集合组成,这些树不一定是二叉的,也不遵循严格的堆性质。但每棵树都遵循最小堆性质,即父节点的键值总是小于或等于其任何子节点的键值。
延迟合并:斐波那契堆在执行操作时,往往延迟树的合并过程,直到绝对必要时才进行。这种策略使得斐波那契堆可以在常数时间内完成多种操作,如插入新元素、减小键值和合并两个堆。
减少无谓的工作:通过允许树结构更加自由,斐波那契堆避免了在每次操作时维护严格堆结构的开销。
斐波那契堆提供了一系列操作,其时间复杂度如下:
插入新元素:O(1) 的摊销时间复杂度。
合并两个堆:O(1) 的摊销时间复杂度。
删除最小元素:O(logn) 的摊销时间复杂度。
减小键值:O(1) 的摊销时间复杂度。
由于其高效的操作特性,斐波那契堆在需要频繁合并堆或者更新键值操作的场景下特别有用,如图算法中计算最短路径和最小生成树。
use std::cell::RefCell; use std::rc::{Rc, Weak}; // 斐波那契堆节点的定义 struct FibNode<T> { key: T, parent: Weak<RefCell<FibNode<T>>>, children: Vec<Rc<RefCell<FibNode<T>>>>, marked: bool, } impl<T> FibNode<T> { fn new(key: T) -> Rc<RefCell<Self>> { Rc::new(RefCell::new(Self { key, parent: Weak::new(), children: Vec::new(), marked: false, })) } } // 斐波那契堆的定义 struct FibonacciHeap<T> { min: Option<Rc<RefCell<FibNode<T>>>>, count: usize, } impl<T: Ord> FibonacciHeap<T> { fn new() -> Self { Self { min: None, count: 0, } } // 向堆中插入一个值 fn insert(&mut self, key: T) { let new_node = FibNode::new(key); self.min = match self.min.take() { Some(current_min) => { if key < current_min.borrow().key { Some(new_node) } else { Some(current_min) } }, None => Some(new_node), }; self.count += 1; } // 寻找堆中的最小值 fn find_min(&self) -> Option<T> where T: Copy, { self.min.as_ref().map(|node| node.borrow().key) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_insert_and_find_min() { let mut heap = FibonacciHeap::new(); heap.insert(3); heap.insert(1); heap.insert(4); assert_eq!(heap.find_min(), Some(1)); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。