DSA Notes
Solve the Fractional Knapsack problem using a greedy approach — sort by value-to-weight ratio, fill optimally, and understand why fractions make greedy work.
Problem Statement
Given N items, each with a weight and value, and a knapsack with maximum capacity W, maximize the total value you can carry. Unlike the 0/1 Knapsack, here you can take fractions of items — you might take half of an item, getting half its value at half its weight.
Why Greedy Works for Fractional (But Not 0/1)
When you can take fractions, there is no "wasted capacity" dilemma. You can always fill the knapsack completely by taking a fraction of the last item. This means the locally optimal choice (highest value per unit weight) never blocks a better option — the greedy choice property holds.
For 0/1 Knapsack, taking a high-ratio item might consume too much capacity, preventing two smaller items that together would be more valuable. Fractions eliminate this problem entirely.
Algorithm
- Compute the value-to-weight ratio for each item:
ratio[i] = value[i] / weight[i] - Sort items by ratio in descending order (most valuable per kg first)
- Greedily take items:
- If the full item fits, take it entirely
- If it does not fit, take the fraction that fills the remaining capacity
- Stop when the knapsack is full
Dry Run Example
| Knapsack capacity | W = 50 |
| Item A: weight=10, value=60 | ratio=6.0 |
| Item B: weight=20, value=100 | ratio=5.0 |
| Item C: weight=30, value=120 | ratio=4.0 |
| Sorted by ratio | A(6.0), B(5.0), C(4.0) |
| Step 1 | Take all of A. Remaining capacity = 50-10 = 40. Value = 60. |
| Step 2 | Take all of B. Remaining capacity = 40-20 = 20. Value = 60+100 = 160. |
| Step 3 | C weighs 30, but only 20 capacity left. |
| Result | Total value = 240 (knapsack is full at exactly 50). |
Compare with 0/1 Knapsack:
- Taking A+B = weight 30, value 160 → cannot take C (weight 30 > remaining 20)
- Taking B+C = weight 50, value 220
- Optimal 0/1 = 220. But fractional = 240 (always ≥ 0/1 solution).
Python Implementation
C++ Implementation
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
struct Item {
int weight, value;
double ratio() const { return (double)value / weight; }
};
double fractionalKnapsack(vector<Item>& items, int capacity) {
sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
return a.ratio() > b.ratio();
});
double totalValue = 0.0;
int remaining = capacity;
for (const Item& item : items) {
if (remaining <= 0) break;
if (item.weight <= remaining) {
totalValue += item.value;
remaining -= item.weight;
} else {
double fraction = (double)remaining / item.weight;
totalValue += item.value * fraction;
remaining = 0;
}
}
return totalValue;
}
int main() {
vector<Item> items = {{10, 60}, {20, 100}, {30, 120}};
cout << "Max value: " << fractionalKnapsack(items, 50) << endl; // 240
}Detailed Implementation with Item Tracking
Often you need to know *what* was taken, not just the total value:
Proof of Optimality
Claim: Sorting by value/weight ratio and greedily filling gives maximum value.
Proof (exchange argument):
- Let items be sorted: r₁ ≥ r₂ ≥ ... ≥ rₙ (ratios).
- Suppose optimal solution O takes fraction fᵢ of item i, and greedy solution G takes fraction gᵢ.
- Find the first item j where gⱼ > fⱼ (greedy takes more of a high-ratio item).
- In O, increase fⱼ and decrease some fₖ (k > j, lower ratio) by the same weight.
- Value change: +Δw×rⱼ - Δw×rₖ ≥ 0 (since rⱼ ≥ rₖ). Solution improves or stays equal.
- Repeat until O matches G. Therefore G is optimal. ∎
Complexity Analysis
| Operation | Time | Space |
|---|---|---|
| Compute ratios | O(n) | O(n) |
| Sort by ratio | O(n log n) | O(1) extra (in-place) |
| Greedy selection | O(n) | O(1) |
| Total | O(n log n) | O(n) |
Fractional vs 0/1 Knapsack Comparison
| Aspect | Fractional | 0/1 |
|---|---|---|
| Items divisible? | Yes | No |
| Algorithm | Greedy | Dynamic Programming |
| Time complexity | O(n log n) | O(n × W) |
| Greedy works? | Yes | No |
| Solution ≥ | Always ≥ 0/1 optimal | — |
The fractional solution is always an upper bound on the 0/1 solution, which is useful in branch-and-bound algorithms for the 0/1 variant.
Real-World Applications
- Investment portfolios: Allocate budget across stocks (can buy fractional shares)
- Resource loading: Fill a truck with divisible goods (grains, liquids)
- CPU scheduling: Allocate fractional time slices to tasks by priority/value
- Memory allocation: Allocate proportional memory to processes by importance
- Ad slot allocation: Divide advertising time by revenue rate
Key Takeaways
The Fractional Knapsack is the simplest greedy optimization problem: sort by value density, fill greedily. The ability to take fractions eliminates the "blocking" problem that makes 0/1 Knapsack hard. Remember: fractional solution upper-bounds 0/1 solution, O(n log n) time, and the exchange argument provides a clean optimality proof.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Fractional Knapsack.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, greedy, fractional, knapsack
Related Data Structures & Algorithms Topics