diff --git a/tig-challenges/src/knapsack.rs b/tig-challenges/src/knapsack.rs index 9e8fe49..6968a98 100644 --- a/tig-challenges/src/knapsack.rs +++ b/tig-challenges/src/knapsack.rs @@ -195,12 +195,128 @@ impl SubInstance { // Step 1: Initial solution obtained by greedily selecting items based on value-weight ratio let mut selected_items = Vec::with_capacity(difficulty.num_items); + let mut unselected_items = Vec::with_capacity(difficulty.num_items); let mut total_weight = 0; + let mut total_value = 0; + let mut is_selected = vec![false; difficulty.num_items]; for &(item, _) in &item_values { if total_weight + weights[item] <= max_weight { total_weight += weights[item]; + total_value += values[item] as i32; + + for &prev_item in &selected_items { + total_value += interaction_values[item][prev_item]; + } selected_items.push(item); + is_selected[item] = true; + } else { + unselected_items.push(item); + } + } + + // Step 2: Improvement of solution with Local Search and Tabu-List + // Precompute sum of interaction values with each selected item for all items + let mut interaction_sum_list = vec![0; difficulty.num_items]; + for x in 0..difficulty.num_items { + interaction_sum_list[x] = values[x] as i32; + for &item in &selected_items { + interaction_sum_list[x] += interaction_values[x][item]; + } + } + + let mut min_selected_item_values = i32::MAX; + for x in 0..difficulty.num_items { + if is_selected[x] { + min_selected_item_values = min_selected_item_values.min(interaction_sum_list[x]); + } + } + + // Optimized local search with tabu list + let max_iterations = 100; + let mut tabu_list = vec![0; difficulty.num_items]; + + for _ in 0..max_iterations { + let mut best_improvement = 0; + let mut best_swap = None; + + for i in 0..unselected_items.len() { + let new_item = unselected_items[i]; + if tabu_list[new_item] > 0 { + continue; + } + + let new_item_values_sum = interaction_sum_list[new_item]; + if new_item_values_sum < best_improvement + min_selected_item_values { + continue; + } + + // Compute minimal weight of remove_item required to put new_item + let min_weight = + weights[new_item] as i32 - (max_weight as i32 - total_weight as i32); + for j in 0..selected_items.len() { + let remove_item = selected_items[j]; + if tabu_list[remove_item] > 0 { + continue; + } + + // Don't check the weight if there is enough remaining capacity + if min_weight > 0 { + // Skip a remove_item if the remaining capacity after removal is insufficient to push a new_item + let removed_item_weight = weights[remove_item] as i32; + if removed_item_weight < min_weight { + continue; + } + } + + let remove_item_values_sum = interaction_sum_list[remove_item]; + let value_diff = new_item_values_sum + - remove_item_values_sum + - interaction_values[new_item][remove_item]; + + if value_diff > best_improvement { + best_improvement = value_diff; + best_swap = Some((i, j)); + } + } + } + + if let Some((unselected_index, selected_index)) = best_swap { + let new_item = unselected_items[unselected_index]; + let remove_item = selected_items[selected_index]; + + selected_items.swap_remove(selected_index); + unselected_items.swap_remove(unselected_index); + selected_items.push(new_item); + unselected_items.push(remove_item); + + is_selected[new_item] = true; + is_selected[remove_item] = false; + + total_value += best_improvement; + total_weight = total_weight + weights[new_item] - weights[remove_item]; + + // Update sum of interaction values after swapping items + min_selected_item_values = i32::MAX; + for x in 0..difficulty.num_items { + interaction_sum_list[x] += + interaction_values[x][new_item] - interaction_values[x][remove_item]; + if is_selected[x] { + min_selected_item_values = + min_selected_item_values.min(interaction_sum_list[x]); + } + } + + // Update tabu list + tabu_list[new_item] = 3; + tabu_list[remove_item] = 3; + } else { + break; // No improvement found, terminate local search + } + + // Decrease tabu counters + for t in tabu_list.iter_mut() { + *t = if *t > 0 { *t - 1 } else { 0 }; } }