Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions knapsack_library/src/algorithms_impls/FPTAS.rs
Original file line number Diff line number Diff line change
@@ -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<u64, String> {
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<Item> = 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)
}
}
82 changes: 82 additions & 0 deletions knapsack_library/src/algorithms_impls/branch_Bound.rs
Original file line number Diff line number Diff line change
@@ -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<u64, String> {
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)
}
}
70 changes: 70 additions & 0 deletions knapsack_library/src/algorithms_impls/meet_in_the_middle.rs
Original file line number Diff line number Diff line change
@@ -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<u64, String> {
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
}
5 changes: 4 additions & 1 deletion knapsack_library/src/algorithms_impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
pub mod greedy;
pub mod FPTAS;
pub mod meet_in_the_middle;
pub mod branch_Bound;
3 changes: 3 additions & 0 deletions knapsack_library/src/algorithms_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ impl AlgorithmsService {
Box::new(DynamicKnapsackSolver),
Box::new(LazyDynamicKnapsackSolver),
Box::new(GreedyKnapsackSolver),
Box::new(BranchAndBoundKnapsackSolver),
Box::new(MeetInTheMiddleKnapsackSolver),
Box::new(FptasKnapsackSolver)
]
}

Expand Down
55 changes: 55 additions & 0 deletions knapsack_library/src/tests/FPTAS_tests.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
52 changes: 52 additions & 0 deletions knapsack_library/src/tests/branch_Bound_tests.rs
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading