hello-algo/en/docs/chapter_greedy/fractional_knapsack_problem.md

227 lines
8.1 KiB
Markdown
Raw Normal View History

2024-05-01 06:47:36 +08:00
---
comments: true
---
# 15.2   Fractional knapsack problem
!!! question
2024-05-01 07:30:10 +08:00
Given $n$ items, the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with a capacity of $cap$. Each item can be chosen only once, **but a part of the item can be selected, with its value calculated based on the proportion of the weight chosen**, what is the maximum value of the items in the knapsack under the limited capacity? An example is shown in Figure 15-3.
2024-05-01 06:47:36 +08:00
![Example data of the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_example.png){ class="animation-figure" }
<p align="center"> Figure 15-3 &nbsp; Example data of the fractional knapsack problem </p>
The fractional knapsack problem is very similar overall to the 0-1 knapsack problem, involving the current item $i$ and capacity $c$, aiming to maximize the value within the limited capacity of the knapsack.
2024-05-01 07:30:10 +08:00
The difference is that, in this problem, only a part of an item can be chosen. As shown in Figure 15-4, **we can arbitrarily split the items and calculate the corresponding value based on the weight proportion**.
2024-05-01 06:47:36 +08:00
1. For item $i$, its value per unit weight is $val[i-1] / wgt[i-1]$, referred to as the unit value.
2. Suppose we put a part of item $i$ with weight $w$ into the knapsack, then the value added to the knapsack is $w \times val[i-1] / wgt[i-1]$.
![Value per unit weight of the item](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png){ class="animation-figure" }
<p align="center"> Figure 15-4 &nbsp; Value per unit weight of the item </p>
### 1. &nbsp; Greedy strategy determination
2024-05-02 01:46:14 +08:00
Maximizing the total value of the items in the knapsack **essentially means maximizing the value per unit weight**. From this, the greedy strategy shown in Figure 15-5 can be deduced.
2024-05-01 06:47:36 +08:00
1. Sort the items by their unit value from high to low.
2. Iterate over all items, **greedily choosing the item with the highest unit value in each round**.
3. If the remaining capacity of the knapsack is insufficient, use part of the current item to fill the knapsack.
![Greedy strategy of the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png){ class="animation-figure" }
<p align="center"> Figure 15-5 &nbsp; Greedy strategy of the fractional knapsack problem </p>
### 2. &nbsp; Code implementation
We have created an `Item` class in order to sort the items by their unit value. We loop and make greedy choices until the knapsack is full, then exit and return the solution:
=== "Python"
```python title="fractional_knapsack.py"
class Item:
2024-05-06 05:27:10 +08:00
"""Item"""
2024-05-01 06:47:36 +08:00
def __init__(self, w: int, v: int):
2024-05-06 05:27:10 +08:00
self.w = w # Item weight
self.v = v # Item value
2024-05-01 06:47:36 +08:00
def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:
2024-05-06 05:27:10 +08:00
"""Fractional knapsack: Greedy"""
# Create an item list, containing two properties: weight, value
2024-05-01 06:47:36 +08:00
items = [Item(w, v) for w, v in zip(wgt, val)]
2024-05-06 05:27:10 +08:00
# Sort by unit value item.v / item.w from high to low
2024-05-01 06:47:36 +08:00
items.sort(key=lambda item: item.v / item.w, reverse=True)
2024-05-06 05:27:10 +08:00
# Loop for greedy selection
2024-05-01 06:47:36 +08:00
res = 0
for item in items:
if item.w <= cap:
2024-05-06 05:27:10 +08:00
# If the remaining capacity is sufficient, put the entire item into the knapsack
2024-05-01 06:47:36 +08:00
res += item.v
cap -= item.w
else:
2024-05-06 05:27:10 +08:00
# If the remaining capacity is insufficient, put part of the item into the knapsack
2024-05-01 06:47:36 +08:00
res += (item.v / item.w) * cap
2024-05-06 05:27:10 +08:00
# No remaining capacity left, thus break the loop
2024-05-01 06:47:36 +08:00
break
return res
```
=== "C++"
```cpp title="fractional_knapsack.cpp"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
2024-05-01 06:47:36 +08:00
2024-05-06 05:27:10 +08:00
[class]{}-[func]{fractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "Java"
```java title="fractional_knapsack.java"
2024-05-06 05:27:10 +08:00
/* Item */
2024-05-01 06:47:36 +08:00
class Item {
2024-05-06 05:27:10 +08:00
int w; // Item weight
int v; // Item value
2024-05-01 06:47:36 +08:00
public Item(int w, int v) {
this.w = w;
this.v = v;
}
}
2024-05-06 05:27:10 +08:00
/* Fractional knapsack: Greedy */
2024-05-01 06:47:36 +08:00
double fractionalKnapsack(int[] wgt, int[] val, int cap) {
2024-05-06 05:27:10 +08:00
// Create an item list, containing two properties: weight, value
2024-05-01 06:47:36 +08:00
Item[] items = new Item[wgt.length];
for (int i = 0; i < wgt.length; i++) {
items[i] = new Item(wgt[i], val[i]);
}
2024-05-06 05:27:10 +08:00
// Sort by unit value item.v / item.w from high to low
2024-05-01 06:47:36 +08:00
Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w)));
2024-05-06 05:27:10 +08:00
// Loop for greedy selection
2024-05-01 06:47:36 +08:00
double res = 0;
for (Item item : items) {
if (item.w <= cap) {
2024-05-06 05:27:10 +08:00
// If the remaining capacity is sufficient, put the entire item into the knapsack
2024-05-01 06:47:36 +08:00
res += item.v;
cap -= item.w;
} else {
2024-05-06 05:27:10 +08:00
// If the remaining capacity is insufficient, put part of the item into the knapsack
2024-05-01 06:47:36 +08:00
res += (double) item.v / item.w * cap;
2024-05-06 05:27:10 +08:00
// No remaining capacity left, thus break the loop
2024-05-01 06:47:36 +08:00
break;
}
}
return res;
}
```
=== "C#"
```csharp title="fractional_knapsack.cs"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
2024-05-01 06:47:36 +08:00
2024-05-06 05:27:10 +08:00
[class]{fractional_knapsack}-[func]{FractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "Go"
```go title="fractional_knapsack.go"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
2024-05-01 06:47:36 +08:00
2024-05-06 05:27:10 +08:00
[class]{}-[func]{fractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "Swift"
```swift title="fractional_knapsack.swift"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
2024-05-01 06:47:36 +08:00
2024-05-06 05:27:10 +08:00
[class]{}-[func]{fractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "JS"
```javascript title="fractional_knapsack.js"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
2024-05-01 06:47:36 +08:00
2024-05-06 05:27:10 +08:00
[class]{}-[func]{fractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "TS"
```typescript title="fractional_knapsack.ts"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
2024-05-01 06:47:36 +08:00
2024-05-06 05:27:10 +08:00
[class]{}-[func]{fractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "Dart"
```dart title="fractional_knapsack.dart"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
2024-05-01 06:47:36 +08:00
2024-05-06 05:27:10 +08:00
[class]{}-[func]{fractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "Rust"
```rust title="fractional_knapsack.rs"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
2024-05-01 06:47:36 +08:00
2024-05-06 05:27:10 +08:00
[class]{}-[func]{fractional_knapsack}
2024-05-01 06:47:36 +08:00
```
=== "C"
```c title="fractional_knapsack.c"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
[class]{}-[func]{fractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "Kotlin"
```kotlin title="fractional_knapsack.kt"
2024-05-06 05:27:10 +08:00
[class]{Item}-[func]{}
[class]{}-[func]{fractionalKnapsack}
2024-05-01 06:47:36 +08:00
```
=== "Ruby"
```ruby title="fractional_knapsack.rb"
[class]{Item}-[func]{}
[class]{}-[func]{fractional_knapsack}
```
=== "Zig"
```zig title="fractional_knapsack.zig"
[class]{Item}-[func]{}
[class]{}-[func]{fractionalKnapsack}
```
Apart from sorting, in the worst case, the entire list of items needs to be traversed, **hence the time complexity is $O(n)$**, where $n$ is the number of items.
Since an `Item` object list is initialized, **the space complexity is $O(n)$**.
### 3. &nbsp; Correctness proof
Using proof by contradiction. Suppose item $x$ has the highest unit value, and some algorithm yields a maximum value `res`, but the solution does not include item $x$.
Now remove a unit weight of any item from the knapsack and replace it with a unit weight of item $x$. Since the unit value of item $x$ is the highest, the total value after replacement will definitely be greater than `res`. **This contradicts the assumption that `res` is the optimal solution, proving that the optimal solution must include item $x$**.
For other items in this solution, we can also construct the above contradiction. Overall, **items with greater unit value are always better choices**, proving that the greedy strategy is effective.
2024-05-01 07:30:10 +08:00
As shown in Figure 15-6, if the item weight and unit value are viewed as the horizontal and vertical axes of a two-dimensional chart respectively, the fractional knapsack problem can be transformed into "seeking the largest area enclosed within a limited horizontal axis range". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective.
2024-05-01 06:47:36 +08:00
![Geometric representation of the fractional knapsack problem](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png){ class="animation-figure" }
<p align="center"> Figure 15-6 &nbsp; Geometric representation of the fractional knapsack problem </p>