Fractional Knapsack — When Greedy Fails on 0/1 Constraints
- Sort by value/weight density descending. Take full items while they fit, take a fraction of the next item to fill remaining capacity. O(n log n) dominated by sort.
- The exchange argument is the key: any solution not taking the highest-density item first can be improved by swapping — this is the formal proof, not just intuition.
- Fractional knapsack: greedy optimal. 0/1 knapsack: DP required. Knowing which you are facing is the first question to ask in any packing/allocation problem.
- Fractional knapsack lets you take partial items — greedy by value/weight density is optimal.
- Sort items by value/weight ratio descending; take full items until the bag fills, then take a fraction of the next item.
- Exchange argument proves optimality: any optimal solution can be tweaked to include the highest-density item without losing value.
- Sorting dominates runtime at O(n log n); selection is O(n).
- Real-world: resource allocation like bandwidth or CPU time where fractional units are meaningful.
Greedy Knapsack Quick Debug
Suspiciously low total value
print([(value/weight, taken_fraction) for item in items])Check that the sum of taken weights equals original capacity.Algorithm fills capacity but value is worse than brute force
Run brute-force 0/1 knapsack on the same data — if it gives higher value, you have a fractional-vs-0/1 mismatch.Check item divisibility constraints in the problem statement.Production Incident
Production Debug GuideSymptom → Action guide for when your resource allocation looks wrong
Fractional knapsack is where most engineers first encounter the concept of greedy correctness — the proof that taking the highest value-density item first is provably optimal, not just intuitively reasonable. The exchange argument used here is a template that appears throughout algorithm design: assume the greedy choice is not in an optimal solution, then show you can swap it in without making things worse.
In practice, fractional knapsack models continuous resource allocation: bandwidth allocation across network flows, portfolio optimisation with divisible assets, CPU time sharing. The 0/1 version (items must be whole) requires dynamic programming and is a completely different beast — knowing which version you're facing is the first question to ask.
The Greedy Strategy
Sort all items by value/weight ratio in descending order. Take items greedily — full items when they fit, a fraction of the next item when the bag is almost full.
def fractional_knapsack(capacity: float, weights: list, values: list) -> float: n = len(weights) # Create (value_density, weight, value) tuples items = sorted(zip(values, weights), key=lambda x: x[0]/x[1], reverse=True) total_value = 0.0 remaining = capacity for value, weight in items: if remaining <= 0: break if weight <= remaining: total_value += value remaining -= weight else: # Take fraction total_value += value * (remaining / weight) remaining = 0 return total_value weights = [10, 20, 30] values = [60, 100, 120] print(fractional_knapsack(50, weights, values)) # 240.0
Why Greedy Works Here But Not for 0/1 Knapsack
The greedy approach works for fractional knapsack because of the greedy choice property: taking the highest value-density item first is always locally and globally optimal — you can always 'undo' a partial choice by trading a fraction.
For 0/1 knapsack, taking the highest density item might leave a gap that no remaining item can fill optimally. Example: - Capacity = 10 - Item A: weight=6, value=10 (density 1.67) - Item B: weight=5, value=8 (density 1.60) - Item C: weight=5, value=8 (density 1.60)
Greedy picks A (density 1.67) then can only fit one of B/C — total = 10+8 = 18. Optimal picks B+C — total = 8+8 = 16. Wait — greedy wins here. But with capacity=10, items [{w=6,v=6},{w=5,v=5},{w=5,v=5}]: greedy picks first (density 1), then one of the others = 11. Optimal: pick both w=5 items = 10. Greedy wins again for fractional.
The key: with fractional items, the exchange argument holds perfectly — you can always swap parts of lower-density items for parts of higher-density ones to improve value.
Proof of Optimality — Exchange Argument Formalised
The exchange argument is the formal proof that greedy is optimal. We'll walk through it step by step for fractional knapsack.
Let the items be sorted by value/weight ratio: v1/w1 >= v2/w2 >= ... >= vn/wn. Let G be the greedy solution. Let O be an optimal solution that differs from G at the first index i where they take different fractions.
Since G takes the maximum possible amount of item i, O takes less (or zero) of item i. O must compensate with some later item j (j > i) that has lower density. Replace a unit weight of item j with a unit weight of item i in O. The value of O increases (or stays same) because vi/wi >= vj/wj. If it stays same, density ties don't matter.
Thus we can transform O into G without decreasing value, proving G is optimal.
This argument is the foundation for greedy optimality proofs in many other problems: activity selection, Huffman coding, coin change (canonical systems).
Complexity and Space Considerations
Sorting: O(n log n) — dominated by sort. Selection pass: O(n) — one scan. Total time: O(n log n). Space: O(1) extra (if sorting in place), otherwise O(n) for a copy.
Compare with 0/1 knapsack dynamic programming: O(nW) time and O(W) space (with 1D array). For large W, fractional knapsack's O(n log n) is much faster.
Trick: If weights are small integers (W <= n log n), you can use counting sort for O(n + W) time, but in practice the built-in sort is fine.
Real-World Applications and Variations
Fractional knapsack appears in many continuous resource allocation problems: - Bandwidth allocation: Splitting bandwidth among flows proportionally to their 'value' (e.g., priority * throughput). - CPU time scheduling: Weighted fair queuing (WFQ) uses a similar greedy approach — allocate CPU to the highest 'density' process first. - Portfolio optimisation: When assets are perfectly divisible (e.g., stocks in large markets), the greedy by expected return per unit risk gives the optimal mix. - Mixing raw materials: In manufacturing, you have limited capacity and want to maximise profit by blending ingredients of different value densities.
Variations: - Bounded fractional knapsack: Each item has a maximum fraction you can take (e.g., you cannot take more than 50% of a shared resource). Still solvable by greedy after adjusting densities? Not necessarily — requires a more complex approach. - Multiple knapsacks: Have several bags. Greedy still works? Not in general — becomes a variant of bin packing. - Knapsack with penalties: Taking a fraction incurs overhead (e.g., setup time per item). Breaks the greedy property.
Common Mistakes and Edge Cases
Even for a simple greedy algorithm, several mistakes slip into code:
- Sorting by value only: Forgetting to divide by weight. Takes expensive heavy items first, not the best value per unit weight.
- Integer division: In languages like C++ or Java, dividing two integers gives an integer, messing up density order. Use double or compare cross-multiplication.
- Floating point equality: When two densities are equal, stable sort matters for reproducibility but not optimality.
- Off-by-one in capacity check: Taking a fraction when the item fits exactly but you compare weight < remaining instead of <=.
- Not handling zero-weight items: Division by zero if weight = 0. Check if weight > 0 before dividing.
- Assuming capacity is integer: If capacity is integer but weights are floats, capacity comparison can drift.
Comparison with 0/1 Knapsack
Understanding when to use each is critical. Here's a direct comparison:
- Fractional Knapsack: Items divisible, greedy gives optimal solution, O(n log n).
- 0/1 Knapsack: Items indivisible, DP required, O(nW) time.
- Unbounded Knapsack: Items can be taken multiple times, DP also used but with different recurrence.
The fundamental difference is the fraction. In fractional, the exchange argument works because you can always swap a small piece of a low-density item for an equal piece of a high-density one. In 0/1, you can't swap a piece — you must swap whole items, which may not fit in the remaining space.
🎯 Key Takeaways
- Sort by value/weight density descending. Take full items while they fit, take a fraction of the next item to fill remaining capacity. O(n log n) dominated by sort.
- The exchange argument is the key: any solution not taking the highest-density item first can be improved by swapping — this is the formal proof, not just intuition.
- Fractional knapsack: greedy optimal. 0/1 knapsack: DP required. Knowing which you are facing is the first question to ask in any packing/allocation problem.
- Real-world: continuous resource allocation — bandwidth, CPU cycles, capital allocation where fractional units are meaningful.
- Follow-up interview question: if weights and values are integers, does fractional knapsack still give a fractional answer? (Yes, if no item fits perfectly in remaining space.)
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhy does greedy work for fractional knapsack but not 0/1 knapsack?SeniorReveal
- QWhat is the time and space complexity of fractional knapsack?Mid-levelReveal
- QProve that sorting by value/weight ratio gives the optimal solution.SeniorReveal
- QCan the fractional knapsack greedy algorithm be implemented without sorting?SeniorReveal
- QHow would you handle a fractional knapsack where each item has a bound on how much you can take?SeniorReveal
Frequently Asked Questions
What if two items have the same value density?
Tie-breaking order doesn't matter — any ordering of equal-density items gives the same total value. However, for deterministic output, sort by a secondary key (e.g., higher weight first or use stable sort).
Can fractional knapsack be solved without sorting?
Yes, using a selection algorithm (like quickselect with median-of-medians) to find the 'breakpoint' item. That yields O(n) worst-case but is more complex. Sorting is simpler and typically fast enough.
Does fractional knapsack have any real-world use outside of educational contexts?
Absolutely. It models any continuous resource allocation: bandwidth throttling, CPU scheduling (WFQ), blending raw materials, investment with divisible assets. Any scenario where you can take a percentage of an item is fractional.
What if the capacity is extremely large (e.g., 10^12)?
Fractional knapsack still works in O(n log n) because capacity doesn't affect time complexity. DP for 0/1 would be impossible. Big capacity is a green flag for fractional greedy.
How do I test whether my greedy solution is correct?
Write a brute-force solution for small n (n ≤ 12) and compare. Also test edge cases: all same density, one huge item, negative weights (though not typical), zero values, and items that exactly fill capacity.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.