diff --git a/knapsack_library/src/algorithms_impls/FPTAS.rs b/knapsack_library/src/algorithms_impls/FPTAS.rs new file mode 100644 index 0000000..300937b --- /dev/null +++ b/knapsack_library/src/algorithms_impls/FPTAS.rs @@ -0,0 +1,70 @@ +use crate::models::knapsack::Knapsack; +use crate::models::knapsack_solver::KnapsackSolver; + +/// FPTAS implementation of the Knapsack solver +/// +/// This solver scales down the values of items to reduce precision and applies dynamic programming. +/// The approximation factor is controlled by ε. +/// Time complexity: O(nW/ε) +pub struct FptasKnapsackSolver { + epsilon: f64, +} + +impl FptasKnapsackSolver { + /// Creates a new FPTAS solver with the given ε value + /// + /// # Arguments + /// + /// * `epsilon` - The approximation factor (0 < ε < 1) + pub fn new(epsilon: f64) -> Self { + assert!(epsilon > 0.0 && epsilon < 1.0, "Epsilon must be between 0 and 1"); + Self { epsilon } + } +} + +impl KnapsackSolver for FptasKnapsackSolver { + fn get_name(&self) -> String { + format!("FPTAS (ε = {:.3})", self.epsilon) + } + + fn solve(&self, knapsack: &Knapsack) -> Result { + let n = knapsack.get_items_len(); + let capacity = knapsack.get_capacity(); + + if n == 0 || capacity == 0 { + return Ok(0); + } + + // Find the maximum value among all items + let max_value = knapsack + .items + .iter() + .map(|item| item.get_value()) + .max() + .unwrap_or(0); + + // Scale down the values + let k = (self.epsilon * max_value as f64).ceil() as u64; + if k == 0 { + return Ok(0); + } + + let scaled_items: Vec = knapsack + .items + .iter() + .map(|item| Item::new(item.get_weight(), item.get_value() / k)) + .collect(); + + let mut dp = vec![0; (capacity + 1) as usize]; + + // Dynamic programming + for item in scaled_items { + for w in (item.get_weight()..=capacity).rev() { + dp[w as usize] = dp[w as usize].max(dp[(w - item.get_weight()) as usize] + item.get_value()); + } + } + + // Scale up the result + Ok(dp[capacity as usize] * k) + } +} \ No newline at end of file diff --git a/knapsack_library/src/algorithms_impls/branch_Bound.rs b/knapsack_library/src/algorithms_impls/branch_Bound.rs new file mode 100644 index 0000000..3010c48 --- /dev/null +++ b/knapsack_library/src/algorithms_impls/branch_Bound.rs @@ -0,0 +1,82 @@ +use crate::models::knapsack::Knapsack; +use crate::models::knapsack_solver::KnapsackSolver; + +/// Branch-and-Bound implementation of the Knapsack solver +/// +/// This solver uses recursion with pruning based on an upper bound estimate. +/// Time complexity: Depends on the pruning efficiency, but typically much faster than brute force. +pub struct BranchAndBoundKnapsackSolver; + +impl KnapsackSolver for BranchAndBoundKnapsackSolver { + fn get_name(&self) -> String { + "Branch and Bound".to_string() + } + + fn solve(&self, knapsack: &Knapsack) -> Result { + let n = knapsack.get_items_len(); + let capacity = knapsack.get_capacity(); + + if n == 0 || capacity == 0 { + return Ok(0); + } + + // Sort items by value-to-weight ratio in descending order + let mut sorted_items = knapsack.items.clone(); + sorted_items.sort_by(|a, b| { + let ratio_a = a.get_value() as f64 / a.get_weight() as f64; + let ratio_b = b.get_value() as f64 / b.get_weight() as f64; + ratio_b.partial_cmp(&ratio_a).unwrap() + }); + + let mut best_value = 0; + let mut current_weight = 0; + let mut current_value = 0; + + // Recursive function to explore the tree + fn branch_and_bound( + items: &[Item], + index: usize, + capacity: u64, + current_weight: u64, + current_value: u64, + best_value: &mut u64, + ) { + if index >= items.len() { + return; + } + + // Include the current item if it fits + if current_weight + items[index].get_weight() <= capacity { + let new_weight = current_weight + items[index].get_weight(); + let new_value = current_value + items[index].get_value(); + if new_value > *best_value { + *best_value = new_value; + } + branch_and_bound(items, index + 1, capacity, new_weight, new_value, best_value); + } + + // Exclude the current item + branch_and_bound(items, index + 1, capacity, current_weight, current_value, best_value); + + // Prune using upper bound + let mut remaining_capacity = capacity - current_weight; + let mut upper_bound = current_value; + for i in index..items.len() { + if remaining_capacity >= items[i].get_weight() { + upper_bound += items[i].get_value(); + remaining_capacity -= items[i].get_weight(); + } else { + upper_bound += (remaining_capacity as f64 * items[i].get_value() as f64 / items[i].get_weight() as f64) as u64; + break; + } + } + if upper_bound <= *best_value { + return; + } + } + + branch_and_bound(&sorted_items, 0, capacity, current_weight, current_value, &mut best_value); + + Ok(best_value) + } +} \ No newline at end of file diff --git a/knapsack_library/src/algorithms_impls/meet_in_the_middle.rs b/knapsack_library/src/algorithms_impls/meet_in_the_middle.rs new file mode 100644 index 0000000..ada6f43 --- /dev/null +++ b/knapsack_library/src/algorithms_impls/meet_in_the_middle.rs @@ -0,0 +1,70 @@ +use crate::models::knapsack::Knapsack; +use crate::models::knapsack_solver::KnapsackSolver; +use std::collections::BTreeSet; + +/// Meet-in-the-Middle implementation of the Knapsack solver +/// +/// This solver splits the items into two halves, generates all possible subsets for each half, +/// and then combines the results using binary search to find the optimal solution. +/// Time complexity: O(2^(n/2) * n) +pub struct MeetInTheMiddleKnapsackSolver; + +impl KnapsackSolver for MeetInTheMiddleKnapsackSolver { + fn get_name(&self) -> String { + "Meet in the Middle".to_string() + } + + fn solve(&self, knapsack: &Knapsack) -> Result { + let n = knapsack.get_items_len(); + let capacity = knapsack.get_capacity(); + + if n == 0 || capacity == 0 { + return Ok(0); + } + + // Split items into two halves + let mid = n / 2; + let (first_half, second_half) = knapsack.items.split_at(mid); + + // Generate all subsets for the first half + let first_subsets = generate_subsets(first_half); + + // Generate all subsets for the second half and sort by weight + let mut second_subsets = generate_subsets(second_half); + second_subsets.sort_by_key(|&(weight, _)| weight); + + // Find the maximum value using binary search + let mut max_value = 0; + for &(weight1, value1) in &first_subsets { + if weight1 > capacity { + continue; + } + let remaining_capacity = capacity - weight1; + + // Binary search for the best subset in the second half + let (_, value2) = match second_subsets.binary_search_by_key(&remaining_capacity, |&(w, _)| w) { + Ok(idx) => second_subsets[idx], + Err(idx) => if idx > 0 { second_subsets[idx - 1] } else { (0, 0) }, + }; + + max_value = max_value.max(value1 + value2); + } + + Ok(max_value) + } +} + +/// Generates all possible subsets of items with their total weight and value +fn generate_subsets(items: &[Item]) -> Vec<(u64, u64)> { + let mut subsets = vec![(0, 0)]; + for item in items { + let mut new_subsets = Vec::new(); + for &(weight, value) in &subsets { + let new_weight = weight + item.get_weight(); + let new_value = value + item.get_value(); + new_subsets.push((new_weight, new_value)); + } + subsets.extend(new_subsets); + } + subsets +} \ No newline at end of file diff --git a/knapsack_library/src/algorithms_impls/mod.rs b/knapsack_library/src/algorithms_impls/mod.rs index ecc112a..edf703a 100644 --- a/knapsack_library/src/algorithms_impls/mod.rs +++ b/knapsack_library/src/algorithms_impls/mod.rs @@ -2,4 +2,7 @@ pub mod full_iteration_with_recursion; pub mod dynamic; pub mod lazy_dynamic; pub mod full_iteration_with_bit_mask; -pub mod greedy; \ No newline at end of file +pub mod greedy; +pub mod FPTAS; +pub mod meet_in_the_middle; +pub mod branch_Bound; \ No newline at end of file diff --git a/knapsack_library/src/algorithms_service.rs b/knapsack_library/src/algorithms_service.rs index 6360871..66c7f93 100644 --- a/knapsack_library/src/algorithms_service.rs +++ b/knapsack_library/src/algorithms_service.rs @@ -25,6 +25,9 @@ impl AlgorithmsService { Box::new(DynamicKnapsackSolver), Box::new(LazyDynamicKnapsackSolver), Box::new(GreedyKnapsackSolver), + Box::new(BranchAndBoundKnapsackSolver), + Box::new(MeetInTheMiddleKnapsackSolver), + Box::new(FptasKnapsackSolver) ] } diff --git a/knapsack_library/src/tests/FPTAS_tests.rs b/knapsack_library/src/tests/FPTAS_tests.rs new file mode 100644 index 0000000..5193047 --- /dev/null +++ b/knapsack_library/src/tests/FPTAS_tests.rs @@ -0,0 +1,55 @@ +#[cfg(test)] +mod tests { + use super::*; + use crate::models::item::Item; + use crate::models::knapsack::Knapsack; + use crate::models::knapsack_solver::KnapsackSolver; + + #[test] + fn test_fptas_basic_case() { + let solver = FptasKnapsackSolver::new(0.1); + let items = vec![ + Item::new(2, 3), + Item::new(3, 4), + Item::new(4, 5), + Item::new(5, 6), + ]; + let knapsack = Knapsack::new(5, items); + + let result = solver.solve(&knapsack).unwrap(); + assert!(result >= 6 && result <= 7); // Approximation within ε + } + + #[test] + fn test_fptas_zero_capacity() { + let solver = FptasKnapsackSolver::new(0.1); + let items = vec![Item::new(1, 1), Item::new(2, 2)]; + let knapsack = Knapsack::new(0, items); + + assert_eq!(solver.solve(&knapsack), Ok(0)); + } + + #[test] + fn test_fptas_no_items() { + let solver = FptasKnapsackSolver::new(0.1); + let items = vec![]; + let knapsack = Knapsack::new(10, items); + + assert_eq!(solver.solve(&knapsack), Ok(0)); + } + + #[test] + fn test_fptas_small_epsilon() { + let solver = FptasKnapsackSolver::new(0.01); + let items = vec![ + Item::new(2, 3), + Item::new(3, 4), + Item::new(4, 5), + Item::new(5, 6), + ]; + let knapsack = Knapsack::new(5, items); + + let result = solver.solve(&knapsack).unwrap(); + assert!(result >= 7 && result <= 7); // More precise approximation + } +} \ No newline at end of file diff --git a/knapsack_library/src/tests/branch_Bound_tests.rs b/knapsack_library/src/tests/branch_Bound_tests.rs new file mode 100644 index 0000000..9049107 --- /dev/null +++ b/knapsack_library/src/tests/branch_Bound_tests.rs @@ -0,0 +1,52 @@ +#[cfg(test)] +mod tests { + use super::*; + use crate::models::item::Item; + use crate::models::knapsack::Knapsack; + use crate::models::knapsack_solver::KnapsackSolver; + + #[test] + fn test_branch_and_bound_basic_case() { + let solver = BranchAndBoundKnapsackSolver; + let items = vec![ + Item::new(2, 3), + Item::new(3, 4), + Item::new(4, 5), + Item::new(5, 6), + ]; + let knapsack = Knapsack::new(5, items); + + assert_eq!(solver.solve(&knapsack), Ok(7)); + } + + #[test] + fn test_branch_and_bound_zero_capacity() { + let solver = BranchAndBoundKnapsackSolver; + let items = vec![Item::new(1, 1), Item::new(2, 2)]; + let knapsack = Knapsack::new(0, items); + + assert_eq!(solver.solve(&knapsack), Ok(0)); + } + + #[test] + fn test_branch_and_bound_no_items() { + let solver = BranchAndBoundKnapsackSolver; + let items = vec![]; + let knapsack = Knapsack::new(10, items); + + assert_eq!(solver.solve(&knapsack), Ok(0)); + } + + #[test] + fn test_branch_and_bound_pruning_effectiveness() { + let solver = BranchAndBoundKnapsackSolver; + let items = vec![ + Item::new(10, 60), + Item::new(20, 100), + Item::new(30, 120), + ]; + let knapsack = Knapsack::new(50, items); + + assert_eq!(solver.solve(&knapsack), Ok(220)); + } +} \ No newline at end of file diff --git a/knapsack_library/src/tests/meet_tests.rs b/knapsack_library/src/tests/meet_tests.rs new file mode 100644 index 0000000..e0492a2 --- /dev/null +++ b/knapsack_library/src/tests/meet_tests.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +mod tests { + use super::*; + use crate::models::item::Item; + use crate::models::knapsack::Knapsack; + use crate::models::knapsack_solver::KnapsackSolver; + + #[test] + fn test_meet_in_middle_basic_case() { + let solver = MeetInTheMiddleKnapsackSolver; + let items = vec![ + Item::new(2, 3), + Item::new(3, 4), + Item::new(4, 5), + Item::new(5, 6), + ]; + let knapsack = Knapsack::new(5, items); + + assert_eq!(solver.solve(&knapsack), Ok(7)); + } + + #[test] + fn test_meet_in_middle_zero_capacity() { + let solver = MeetInTheMiddleKnapsackSolver; + let items = vec![Item::new(1, 1), Item::new(2, 2)]; + let knapsack = Knapsack::new(0, items); + + assert_eq!(solver.solve(&knapsack), Ok(0)); + } + + #[test] + fn test_meet_in_middle_no_items() { + let solver = MeetInTheMiddleKnapsackSolver; + let items = vec![]; + let knapsack = Knapsack::new(10, items); + + assert_eq!(solver.solve(&knapsack), Ok(0)); + } +} \ No newline at end of file diff --git a/knapsack_library/src/tests/mod.rs b/knapsack_library/src/tests/mod.rs index 3f16e42..d0a1a64 100644 --- a/knapsack_library/src/tests/mod.rs +++ b/knapsack_library/src/tests/mod.rs @@ -3,4 +3,7 @@ pub mod dynamic_tests; pub mod full_iteration_with_recursion_tests; pub mod full_iteration_with_bit_mask_tests; pub mod lazy_dynamic_tests; -pub mod greedy_tests; \ No newline at end of file +pub mod greedy_tests; +pub mod branch_Bound_tests; +pub mod meet_tests; +pub mod FPTAS_tests; \ No newline at end of file