This commit is contained in:
krahets
2026-03-29 02:26:00 +08:00
parent 63276d36d9
commit 37523d4ceb
118 changed files with 74250 additions and 21 deletions
@@ -0,0 +1,580 @@
---
comments: true
---
# 15.2   Задача о дробном рюкзаке
!!! question
Дано $n$ предметов. Вес предмета $i$ равен $wgt[i-1]$, ценность равна $val[i-1]$, также дан рюкзак вместимостью $cap$. Каждый предмет можно выбрать только один раз, **но разрешается взять лишь часть предмета, а ценность вычисляется пропорционально взятому весу**. Требуется найти максимальную ценность предметов в рюкзаке при ограниченной вместимости. Пример показан на рисунке 15-3.
![Пример данных для задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_example.png){ class="animation-figure" }
<p align="center"> Рисунок 15-3 &nbsp; Пример данных для задачи о дробном рюкзаке </p>
Задача о дробном рюкзаке в целом очень похожа на задачу о рюкзаке 0-1: состояние включает текущий предмет $i$ и вместимость $c$, а цель состоит в нахождении максимальной ценности при заданной вместимости рюкзака.
Отличие в том, что здесь разрешено брать только часть предмета. Как показано на рисунке 15-4, **мы можем произвольно делить предмет и вычислять соответствующую ценность пропорционально весу**.
1. Для предмета $i$ его ценность на единицу веса равна $val[i-1] / wgt[i-1]$, сокращенно - удельная ценность.
2. Если взять часть предмета $i$ весом $w$, то ценность рюкзака увеличится на $w \times val[i-1] / wgt[i-1]$.
![Ценность предмета на единицу веса](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png){ class="animation-figure" }
<p align="center"> Рисунок 15-4 &nbsp; Ценность предмета на единицу веса </p>
### 1. &nbsp; Определение жадной стратегии
Максимизация общей ценности предметов в рюкзаке **по сути равносильна максимизации ценности на единицу веса**. Отсюда естественно выводится следующая жадная стратегия.
1. Отсортировать предметы по убыванию удельной ценности.
2. Перебирать все предметы и **на каждом шаге жадно выбирать предмет с наибольшей удельной ценностью**.
3. Если оставшейся вместимости рюкзака недостаточно, взять часть текущего предмета, чтобы заполнить рюкзак.
![Жадная стратегия для задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png){ class="animation-figure" }
<p align="center"> Рисунок 15-5 &nbsp; Жадная стратегия для задачи о дробном рюкзаке </p>
### 2. &nbsp; Код реализации
Мы вводим класс `Item`, чтобы можно было сортировать предметы по удельной ценности. Далее циклически выполняем жадный выбор и, когда рюкзак заполнен, выходим и возвращаем ответ:
=== "Python"
```python title="fractional_knapsack.py"
class Item:
"""Предмет"""
def __init__(self, w: int, v: int):
self.w = w # Вес предмета
self.v = v # Стоимость предмета
def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:
"""Дробный рюкзак: жадный алгоритм"""
# Создать список предметов с двумя свойствами: вес и стоимость
items = [Item(w, v) for w, v in zip(wgt, val)]
# Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sort(key=lambda item: item.v / item.w, reverse=True)
# Циклический жадный выбор
res = 0
for item in items:
if item.w <= cap:
# Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v
cap -= item.w
else:
# Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (item.v / item.w) * cap
# Свободной вместимости больше не осталось, поэтому выйти из цикла
break
return res
```
=== "C++"
```cpp title="fractional_knapsack.cpp"
/* Предмет */
class Item {
public:
int w; // Вес предмета
int v; // Стоимость предмета
Item(int w, int v) : w(w), v(v) {
}
};
/* Дробный рюкзак: жадный алгоритм */
double fractionalKnapsack(vector<int> &wgt, vector<int> &val, int cap) {
// Создать список предметов с двумя свойствами: вес и стоимость
vector<Item> items;
for (int i = 0; i < wgt.size(); i++) {
items.push_back(Item(wgt[i], val[i]));
}
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; });
// Циклический жадный выбор
double res = 0;
for (auto &item : items) {
if (item.w <= cap) {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v;
cap -= item.w;
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (double)item.v / item.w * cap;
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break;
}
}
return res;
}
```
=== "Java"
```java title="fractional_knapsack.java"
/* Предмет */
class Item {
int w; // Вес предмета
int v; // Стоимость предмета
public Item(int w, int v) {
this.w = w;
this.v = v;
}
}
/* Дробный рюкзак: жадный алгоритм */
double fractionalKnapsack(int[] wgt, int[] val, int cap) {
// Создать список предметов с двумя свойствами: вес и стоимость
Item[] items = new Item[wgt.length];
for (int i = 0; i < wgt.length; i++) {
items[i] = new Item(wgt[i], val[i]);
}
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w)));
// Циклический жадный выбор
double res = 0;
for (Item item : items) {
if (item.w <= cap) {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v;
cap -= item.w;
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (double) item.v / item.w * cap;
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break;
}
}
return res;
}
```
=== "C#"
```csharp title="fractional_knapsack.cs"
/* Предмет */
class Item(int w, int v) {
public int w = w; // Вес предмета
public int v = v; // Стоимость предмета
}
/* Дробный рюкзак: жадный алгоритм */
double FractionalKnapsack(int[] wgt, int[] val, int cap) {
// Создать список предметов с двумя свойствами: вес и стоимость
Item[] items = new Item[wgt.Length];
for (int i = 0; i < wgt.Length; i++) {
items[i] = new Item(wgt[i], val[i]);
}
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w));
// Циклический жадный выбор
double res = 0;
foreach (Item item in items) {
if (item.w <= cap) {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v;
cap -= item.w;
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (double)item.v / item.w * cap;
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break;
}
}
return res;
}
```
=== "Go"
```go title="fractional_knapsack.go"
/* Предмет */
type Item struct {
w int // Вес предмета
v int // Стоимость предмета
}
/* Дробный рюкзак: жадный алгоритм */
func fractionalKnapsack(wgt []int, val []int, cap int) float64 {
// Создать список предметов с двумя свойствами: вес и стоимость
items := make([]Item, len(wgt))
for i := 0; i < len(wgt); i++ {
items[i] = Item{wgt[i], val[i]}
}
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
sort.Slice(items, func(i, j int) bool {
return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w)
})
// Циклический жадный выбор
res := 0.0
for _, item := range items {
if item.w <= cap {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += float64(item.v)
cap -= item.w
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += float64(item.v) / float64(item.w) * float64(cap)
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break
}
}
return res
}
```
=== "Swift"
```swift title="fractional_knapsack.swift"
/* Предмет */
class Item {
var w: Int // Вес предмета
var v: Int // Стоимость предмета
init(w: Int, v: Int) {
self.w = w
self.v = v
}
}
/* Дробный рюкзак: жадный алгоритм */
func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double {
// Создать список предметов с двумя свойствами: вес и стоимость
var items = zip(wgt, val).map { Item(w: $0, v: $1) }
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) }
// Циклический жадный выбор
var res = 0.0
var cap = cap
for item in items {
if item.w <= cap {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += Double(item.v)
cap -= item.w
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += Double(item.v) / Double(item.w) * Double(cap)
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break
}
}
return res
}
```
=== "JS"
```javascript title="fractional_knapsack.js"
/* Предмет */
class Item {
constructor(w, v) {
this.w = w; // Вес предмета
this.v = v; // Стоимость предмета
}
}
/* Дробный рюкзак: жадный алгоритм */
function fractionalKnapsack(wgt, val, cap) {
// Создать список предметов с двумя свойствами: вес и стоимость
const items = wgt.map((w, i) => new Item(w, val[i]));
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sort((a, b) => b.v / b.w - a.v / a.w);
// Циклический жадный выбор
let res = 0;
for (const item of items) {
if (item.w <= cap) {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v;
cap -= item.w;
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (item.v / item.w) * cap;
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break;
}
}
return res;
}
```
=== "TS"
```typescript title="fractional_knapsack.ts"
/* Предмет */
class Item {
w: number; // Вес предмета
v: number; // Стоимость предмета
constructor(w: number, v: number) {
this.w = w;
this.v = v;
}
}
/* Дробный рюкзак: жадный алгоритм */
function fractionalKnapsack(wgt: number[], val: number[], cap: number): number {
// Создать список предметов с двумя свойствами: вес и стоимость
const items: Item[] = wgt.map((w, i) => new Item(w, val[i]));
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sort((a, b) => b.v / b.w - a.v / a.w);
// Циклический жадный выбор
let res = 0;
for (const item of items) {
if (item.w <= cap) {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v;
cap -= item.w;
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (item.v / item.w) * cap;
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break;
}
}
return res;
}
```
=== "Dart"
```dart title="fractional_knapsack.dart"
/* Предмет */
class Item {
int w; // Вес предмета
int v; // Стоимость предмета
Item(this.w, this.v);
}
/* Дробный рюкзак: жадный алгоритм */
double fractionalKnapsack(List<int> wgt, List<int> val, int cap) {
// Создать список предметов с двумя свойствами: вес и стоимость
List<Item> items = List.generate(wgt.length, (i) => Item(wgt[i], val[i]));
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w));
// Циклический жадный выбор
double res = 0;
for (Item item in items) {
if (item.w <= cap) {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v;
cap -= item.w;
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += item.v / item.w * cap;
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break;
}
}
return res;
}
```
=== "Rust"
```rust title="fractional_knapsack.rs"
/* Предмет */
struct Item {
w: i32, // Вес предмета
v: i32, // Стоимость предмета
}
impl Item {
fn new(w: i32, v: i32) -> Self {
Self { w, v }
}
}
/* Дробный рюкзак: жадный алгоритм */
fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 {
// Создать список предметов с двумя свойствами: вес и стоимость
let mut items = wgt
.iter()
.zip(val.iter())
.map(|(&w, &v)| Item::new(w, v))
.collect::<Vec<Item>>();
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sort_by(|a, b| {
(b.v as f64 / b.w as f64)
.partial_cmp(&(a.v as f64 / a.w as f64))
.unwrap()
});
// Циклический жадный выбор
let mut res = 0.0;
for item in &items {
if item.w <= cap {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v as f64;
cap -= item.w;
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += item.v as f64 / item.w as f64 * cap as f64;
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break;
}
}
res
}
```
=== "C"
```c title="fractional_knapsack.c"
/* Предмет */
typedef struct {
int w; // Вес предмета
int v; // Стоимость предмета
} Item;
/* Дробный рюкзак: жадный алгоритм */
float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) {
// Создать список предметов с двумя свойствами: вес и стоимость
Item *items = malloc(sizeof(Item) * itemCount);
for (int i = 0; i < itemCount; i++) {
items[i] = (Item){.w = wgt[i], .v = val[i]};
}
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity);
// Циклический жадный выбор
float res = 0.0;
for (int i = 0; i < itemCount; i++) {
if (items[i].w <= cap) {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += items[i].v;
cap -= items[i].w;
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (float)cap / items[i].w * items[i].v;
cap = 0;
break;
}
}
free(items);
return res;
}
```
=== "Kotlin"
```kotlin title="fractional_knapsack.kt"
/* Предмет */
class Item(
val w: Int, // Предмет
val v: Int // Стоимость предмета
)
/* Дробный рюкзак: жадный алгоритм */
fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double {
// Создать список предметов с двумя свойствами: вес и стоимость
var cap = c
val items = arrayOfNulls<Item>(wgt.size)
for (i in wgt.indices) {
items[i] = Item(wgt[i], _val[i])
}
// Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) }
// Циклический жадный выбор
var res = 0.0
for (item in items) {
if (item!!.w <= cap) {
// Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v
cap -= item.w
} else {
// Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += item.v.toDouble() / item.w * cap
// Свободной вместимости больше не осталось, поэтому выйти из цикла
break
}
}
return res
}
```
=== "Ruby"
```ruby title="fractional_knapsack.rb"
=begin
File: fractional_knapsack.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Предмет ###
class Item
attr_accessor :w # Вес предмета
attr_accessor :v # Стоимость предмета
def initialize(w, v)
@w = w
@v = v
end
end
=begin
File: fractional_knapsack.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Предмет ###
class Item
attr_accessor :w # Вес предмета
attr_accessor :v # Стоимость предмета
def initialize(w, v)
@w = w
@v = v
end
end
# ## Дробный рюкзак: жадный алгоритм ###
def fractional_knapsack(wgt, val, cap)
# Создать список предметов с двумя свойствами: вес и стоимость
items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) }
# Отсортировать по удельной стоимости item.v / item.w в порядке убывания
items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) }
# Циклический жадный выбор
res = 0
for item in items
if item.w <= cap
# Если оставшейся вместимости достаточно, положить в рюкзак текущий предмет целиком
res += item.v
cap -= item.w
else
# Если оставшейся вместимости недостаточно, положить в рюкзак часть текущего предмета
res += (item.v.to_f / item.w) * cap
# Свободной вместимости больше не осталось, поэтому выйти из цикла
break
end
end
res
end
```
??? pythontutor "Визуализация кода"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20w%3A%20int%2C%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%D0%92%D0%B5%D1%81%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%D0%A1%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%94%D1%80%D0%BE%D0%B1%D0%BD%D1%8B%D0%B9%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D1%81%20%D0%B4%D0%B2%D1%83%D0%BC%D1%8F%20%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0%D0%BC%D0%B8%3A%20%D0%B2%D0%B5%D1%81%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20items%20%3D%20%5BItem%28w%2C%20v%29%20for%20w%2C%20v%20in%20zip%28wgt%2C%20val%29%5D%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BF%D0%BE%20%D1%83%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20item.v%20%2F%20item.w%20%D0%B2%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%20%D1%83%D0%B1%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20%2F%20item.w%2C%20reverse%3DTrue%29%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B5%D0%B9%D1%81%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D1%87%D0%BD%D0%BE%2C%20%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20%D1%86%D0%B5%D0%BB%D0%B8%D0%BA%D0%BE%D0%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B5%D0%B9%D1%81%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%BD%D0%B5%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D1%87%D0%BD%D0%BE%2C%20%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%20%D1%87%D0%B0%D1%81%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20%2F%20item.w%29%20%2A%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D0%BE%D1%81%D1%8C%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%20%D0%B8%D0%B7%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=8&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20w%3A%20int%2C%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%D0%92%D0%B5%D1%81%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%D0%A1%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%94%D1%80%D0%BE%D0%B1%D0%BD%D1%8B%D0%B9%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D1%81%20%D0%B4%D0%B2%D1%83%D0%BC%D1%8F%20%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0%D0%BC%D0%B8%3A%20%D0%B2%D0%B5%D1%81%20%D0%B8%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20items%20%3D%20%5BItem%28w%2C%20v%29%20for%20w%2C%20v%20in%20zip%28wgt%2C%20val%29%5D%0A%20%20%20%20%23%20%D0%9E%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BF%D0%BE%20%D1%83%D0%B4%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20item.v%20%2F%20item.w%20%D0%B2%20%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B5%20%D1%83%D0%B1%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20%2F%20item.w%2C%20reverse%3DTrue%29%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B5%D0%B9%D1%81%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D1%87%D0%BD%D0%BE%2C%20%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B8%D0%B9%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%20%D1%86%D0%B5%D0%BB%D0%B8%D0%BA%D0%BE%D0%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D1%88%D0%B5%D0%B9%D1%81%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%BD%D0%B5%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D1%87%D0%BD%D0%BE%2C%20%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%20%D1%87%D0%B0%D1%81%D1%82%D1%8C%20%D1%82%D0%B5%D0%BA%D1%83%D1%89%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20%2F%20item.w%29%20%2A%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE%D0%B4%D0%BD%D0%BE%D0%B9%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D0%BE%D1%81%D1%8C%2C%20%D0%BF%D0%BE%D1%8D%D1%82%D0%BE%D0%BC%D1%83%20%D0%B2%D1%8B%D0%B9%D1%82%D0%B8%20%D0%B8%D0%B7%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%82%D0%BE%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D0%BF%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BE%D0%B2%20%D0%B1%D0%B5%D0%B7%20%D0%BF%D1%80%D0%B5%D0%B2%D1%8B%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8%20%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B0%20%3D%20%7Bres%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=8&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div>
Встроенный алгоритм сортировки обычно имеет временную сложность $O(\log n)$, а пространственная сложность обычно равна $O(\log n)$ или $O(n)$, в зависимости от конкретной реализации в языке программирования.
Помимо сортировки, в худшем случае потребуется пройти весь список предметов, **поэтому временная сложность равна $O(n)$**, где $n$ - число предметов.
Поскольку инициализируется список объектов `Item`, **пространственная сложность равна $O(n)$**.
### 3. &nbsp; Доказательство корректности
Используем доказательство от противного. Предположим, что предмет $x$ имеет наибольшую удельную ценность, некоторый алгоритм получил максимальную ценность `res`, но в найденном решении предмет $x$ отсутствует.
Теперь вынем из рюкзака произвольный предмет единичного веса и заменим его на предмет $x$ того же веса. Поскольку предмет $x$ имеет наибольшую удельную ценность, общая ценность после замены обязательно станет больше `res`. **Это противоречит тому, что `res` является оптимальным решением, а значит оптимальное решение обязательно содержит предмет $x$**.
Для других предметов в этом решении можно построить аналогичное противоречие. Иными словами, **предметы с большей удельной ценностью всегда являются более выгодным выбором**, а значит жадная стратегия корректна.
Как показано на рисунке 15-6, если рассматривать вес предметов и их удельную ценность как горизонтальную и вертикальную оси двумерной диаграммы, то задачу о дробном рюкзаке можно интерпретировать как «поиск максимальной площади, ограниченной конечным отрезком по горизонтали». Эта аналогия помогает понять корректность жадной стратегии с геометрической точки зрения.
![Геометрическая интерпретация задачи о дробном рюкзаке](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png){ class="animation-figure" }
<p align="center"> Рисунок 15-6 &nbsp; Геометрическая интерпретация задачи о дробном рюкзаке </p>
+414
View File
@@ -0,0 +1,414 @@
---
comments: true
---
# 15.1 &nbsp; Жадный алгоритм
<u>Жадный алгоритм (greedy algorithm)</u> - это распространенный подход к решению задач оптимизации. Его основная идея состоит в том, чтобы на каждом этапе принятия решения выбирать вариант, который выглядит наилучшим прямо сейчас, то есть жадно принимать локально оптимальные решения в надежде получить глобально оптимальный результат. Жадные алгоритмы лаконичны и эффективны, поэтому широко применяются во многих практических задачах.
Жадные алгоритмы и динамическое программирование часто используются для решения задач оптимизации. У них есть некоторое сходство, например оба опираются на свойство оптимальной подструктуры, но принципы работы различаются.
- Динамическое программирование учитывает все решения предыдущих этапов при выборе текущего решения и использует ответы для прошлых подзадач, чтобы построить ответ для текущей подзадачи.
- Жадный алгоритм не учитывает прошлые решения, а просто движется вперед, каждый раз делая жадный выбор, постепенно сужая область задачи, пока она не будет решена.
Сначала разберем принцип работы жадного алгоритма на примере задачи «размен монет». Эта задача уже встречалась в разделе «задача о полном рюкзаке», поэтому она наверняка вам знакома.
!!! question
Дано $n$ видов монет. Номинал монеты $i$ равен $coins[i - 1]$, целевая сумма равна $amt$, причем каждую монету можно брать неограниченное число раз. Требуется найти минимальное число монет, которыми можно набрать целевую сумму. Если набрать сумму невозможно, верните $-1$.
Жадная стратегия для этой задачи показана на рисунке 15-1. Для заданной целевой суммы **мы жадно выбираем монету, которая не превышает ее и находится к ней ближе всего**, и повторяем этот шаг, пока не получим нужную сумму.
![Жадная стратегия для задачи о размене монет](greedy_algorithm.assets/coin_change_greedy_strategy.png){ class="animation-figure" }
<p align="center"> Рисунок 15-1 &nbsp; Жадная стратегия для задачи о размене монет </p>
Код реализации выглядит следующим образом:
=== "Python"
```python title="coin_change_greedy.py"
def coin_change_greedy(coins: list[int], amt: int) -> int:
"""Размен монет: жадный алгоритм"""
# Предположить, что список coins упорядочен
i = len(coins) - 1
count = 0
# Циклически выполнять жадный выбор, пока не останется суммы
while amt > 0:
# Найти монету, которая меньше остатка суммы и наиболее к нему близка
while i > 0 and coins[i] > amt:
i -= 1
# Выбрать coins[i]
amt -= coins[i]
count += 1
# Если допустимое решение не найдено, вернуть -1
return count if amt == 0 else -1
```
=== "C++"
```cpp title="coin_change_greedy.cpp"
/* Размен монет: жадный алгоритм */
int coinChangeGreedy(vector<int> &coins, int amt) {
// Предположить, что список coins упорядочен
int i = coins.size() - 1;
int count = 0;
// Циклически выполнять жадный выбор, пока не останется суммы
while (amt > 0) {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while (i > 0 && coins[i] > amt) {
i--;
}
// Выбрать coins[i]
amt -= coins[i];
count++;
}
// Если допустимое решение не найдено, вернуть -1
return amt == 0 ? count : -1;
}
```
=== "Java"
```java title="coin_change_greedy.java"
/* Размен монет: жадный алгоритм */
int coinChangeGreedy(int[] coins, int amt) {
// Предположить, что список coins упорядочен
int i = coins.length - 1;
int count = 0;
// Циклически выполнять жадный выбор, пока не останется суммы
while (amt > 0) {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while (i > 0 && coins[i] > amt) {
i--;
}
// Выбрать coins[i]
amt -= coins[i];
count++;
}
// Если допустимое решение не найдено, вернуть -1
return amt == 0 ? count : -1;
}
```
=== "C#"
```csharp title="coin_change_greedy.cs"
/* Размен монет: жадный алгоритм */
int CoinChangeGreedy(int[] coins, int amt) {
// Предположить, что список coins упорядочен
int i = coins.Length - 1;
int count = 0;
// Циклически выполнять жадный выбор, пока не останется суммы
while (amt > 0) {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while (i > 0 && coins[i] > amt) {
i--;
}
// Выбрать coins[i]
amt -= coins[i];
count++;
}
// Если допустимое решение не найдено, вернуть -1
return amt == 0 ? count : -1;
}
```
=== "Go"
```go title="coin_change_greedy.go"
/* Размен монет: жадный алгоритм */
func coinChangeGreedy(coins []int, amt int) int {
// Предположить, что список coins упорядочен
i := len(coins) - 1
count := 0
// Циклически выполнять жадный выбор, пока не останется суммы
for amt > 0 {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
for i > 0 && coins[i] > amt {
i--
}
// Выбрать coins[i]
amt -= coins[i]
count++
}
// Если допустимое решение не найдено, вернуть -1
if amt != 0 {
return -1
}
return count
}
```
=== "Swift"
```swift title="coin_change_greedy.swift"
/* Размен монет: жадный алгоритм */
func coinChangeGreedy(coins: [Int], amt: Int) -> Int {
// Предположить, что список coins упорядочен
var i = coins.count - 1
var count = 0
var amt = amt
// Циклически выполнять жадный выбор, пока не останется суммы
while amt > 0 {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while i > 0 && coins[i] > amt {
i -= 1
}
// Выбрать coins[i]
amt -= coins[i]
count += 1
}
// Если допустимое решение не найдено, вернуть -1
return amt == 0 ? count : -1
}
```
=== "JS"
```javascript title="coin_change_greedy.js"
/* Размен монет: жадный алгоритм */
function coinChangeGreedy(coins, amt) {
// Предположить, что массив coins упорядочен
let i = coins.length - 1;
let count = 0;
// Циклически выполнять жадный выбор, пока не останется суммы
while (amt > 0) {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while (i > 0 && coins[i] > amt) {
i--;
}
// Выбрать coins[i]
amt -= coins[i];
count++;
}
// Если допустимое решение не найдено, вернуть -1
return amt === 0 ? count : -1;
}
```
=== "TS"
```typescript title="coin_change_greedy.ts"
/* Размен монет: жадный алгоритм */
function coinChangeGreedy(coins: number[], amt: number): number {
// Предположить, что массив coins упорядочен
let i = coins.length - 1;
let count = 0;
// Циклически выполнять жадный выбор, пока не останется суммы
while (amt > 0) {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while (i > 0 && coins[i] > amt) {
i--;
}
// Выбрать coins[i]
amt -= coins[i];
count++;
}
// Если допустимое решение не найдено, вернуть -1
return amt === 0 ? count : -1;
}
```
=== "Dart"
```dart title="coin_change_greedy.dart"
/* Размен монет: жадный алгоритм */
int coinChangeGreedy(List<int> coins, int amt) {
// Предположить, что список coins упорядочен
int i = coins.length - 1;
int count = 0;
// Циклически выполнять жадный выбор, пока не останется суммы
while (amt > 0) {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while (i > 0 && coins[i] > amt) {
i--;
}
// Выбрать coins[i]
amt -= coins[i];
count++;
}
// Если допустимое решение не найдено, вернуть -1
return amt == 0 ? count : -1;
}
```
=== "Rust"
```rust title="coin_change_greedy.rs"
/* Размен монет: жадный алгоритм */
fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 {
// Предположить, что список coins упорядочен
let mut i = coins.len() - 1;
let mut count = 0;
// Циклически выполнять жадный выбор, пока не останется суммы
while amt > 0 {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while i > 0 && coins[i] > amt {
i -= 1;
}
// Выбрать coins[i]
amt -= coins[i];
count += 1;
}
// Если допустимое решение не найдено, вернуть -1
if amt == 0 {
count
} else {
-1
}
}
```
=== "C"
```c title="coin_change_greedy.c"
/* Размен монет: жадный алгоритм */
int coinChangeGreedy(int *coins, int size, int amt) {
// Предположить, что список coins упорядочен
int i = size - 1;
int count = 0;
// Циклически выполнять жадный выбор, пока не останется суммы
while (amt > 0) {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while (i > 0 && coins[i] > amt) {
i--;
}
// Выбрать coins[i]
amt -= coins[i];
count++;
}
// Если допустимое решение не найдено, вернуть -1
return amt == 0 ? count : -1;
}
```
=== "Kotlin"
```kotlin title="coin_change_greedy.kt"
/* Размен монет: жадный алгоритм */
fun coinChangeGreedy(coins: IntArray, amt: Int): Int {
// Предположить, что список coins упорядочен
var am = amt
var i = coins.size - 1
var count = 0
// Циклически выполнять жадный выбор, пока не останется суммы
while (am > 0) {
// Найти монету, которая меньше остатка суммы и наиболее к нему близка
while (i > 0 && coins[i] > am) {
i--
}
// Выбрать coins[i]
am -= coins[i]
count++
}
// Если допустимое решение не найдено, вернуть -1
return if (am == 0) count else -1
}
```
=== "Ruby"
```ruby title="coin_change_greedy.rb"
=begin
File: coin_change_greedy.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Размен монет: жадный алгоритм ###
def coin_change_greedy(coins, amt)
# Предположить, что список coins упорядочен
i = coins.length - 1
count = 0
# Циклически выполнять жадный выбор, пока не останется суммы
while amt > 0
# Найти монету, которая меньше остатка суммы и наиболее к нему близка
while i > 0 && coins[i] > amt
i -= 1
end
# Выбрать coins[i]
amt -= coins[i]
count += 1
end
# Если допустимое решение не найдено, вернуть -1
amt == 0 ? count : -1
end
```
??? pythontutor "Визуализация кода"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%2C%20%D1%87%D1%82%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20coins%20%D1%83%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D1%87%D0%B5%D0%BD%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%2C%20%D0%BF%D0%BE%D0%BA%D0%B0%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BD%D0%B5%D1%82%D1%81%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%2C%20%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D0%B0%D1%8F%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BA%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%D0%B8%20%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D0%BA%20%D0%BD%D0%B5%D0%BC%D1%83%20%D0%B1%D0%BB%D0%B8%D0%B7%D0%BA%D0%B0%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%D0%BE%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%3A%20%D0%B3%D0%B0%D1%80%D0%B0%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20coins%20%3D%20%5B1%2C%205%2C%2010%2C%2020%2C%2050%2C%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%7Bamt%7D%20%3D%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%3A%20%D0%BD%D0%B5%20%D0%B3%D0%B0%D1%80%D0%B0%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20coins%20%3D%20%5B1%2C%2020%2C%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%7Bamt%7D%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%D0%9D%D0%B0%20%D1%81%D0%B0%D0%BC%D0%BE%D0%BC%20%D0%B4%D0%B5%D0%BB%D0%B5%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%203%3A%2020%20%2B%2020%20%2B%2020%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D0%BD%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9F%D1%80%D0%B5%D0%B4%D0%BF%D0%BE%D0%BB%D0%BE%D0%B6%D0%B8%D1%82%D1%8C%2C%20%D1%87%D1%82%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20coins%20%D1%83%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D1%87%D0%B5%D0%BD%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%D0%A6%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%20%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%2C%20%D0%BF%D0%BE%D0%BA%D0%B0%20%D0%BD%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D0%BD%D0%B5%D1%82%D1%81%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9D%D0%B0%D0%B9%D1%82%D0%B8%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%D1%83%2C%20%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D0%B0%D1%8F%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BA%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%D0%B8%20%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D0%BA%20%D0%BD%D0%B5%D0%BC%D1%83%20%D0%B1%D0%BB%D0%B8%D0%B7%D0%BA%D0%B0%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%B4%D0%BE%D0%BF%D1%83%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D0%B5%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B5%20%D0%BD%D0%B0%D0%B9%D0%B4%D0%B5%D0%BD%D0%BE%2C%20%D0%B2%D0%B5%D1%80%D0%BD%D1%83%D1%82%D1%8C%20-1%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%3A%20%D0%B3%D0%B0%D1%80%D0%B0%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20coins%20%3D%20%5B1%2C%205%2C%2010%2C%2020%2C%2050%2C%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%7Bamt%7D%20%3D%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%BF%D0%BE%D0%B4%D1%85%D0%BE%D0%B4%3A%20%D0%BD%D0%B5%20%D0%B3%D0%B0%D1%80%D0%B0%D0%BD%D1%82%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20coins%20%3D%20%5B1%2C%2020%2C%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D0%BC%D0%BE%D0%BD%D0%B5%D1%82%20%D0%B4%D0%BB%D1%8F%20%D0%BD%D0%B0%D0%B1%D0%BE%D1%80%D0%B0%20%D1%81%D1%83%D0%BC%D0%BC%D1%8B%20%7Bamt%7D%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%D0%9D%D0%B0%20%D1%81%D0%B0%D0%BC%D0%BE%D0%BC%20%D0%B4%D0%B5%D0%BB%D0%B5%20%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D1%83%D0%BC%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%203%3A%2020%20%2B%2020%20%2B%2020%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div>
У вас может невольно вырваться: So clean! Жадный алгоритм решает задачу размена монет всего примерно десятью строками кода.
## 15.1.1 &nbsp; Преимущества и ограничения жадного алгоритма
**Жадный алгоритм не только прост в действиях и реализации, но и обычно очень эффективен**. В приведенном выше коде обозначим минимальный номинал монеты через $\min(coins)$, тогда жадный выбор выполняется не более чем $amt / \min(coins)$ раз, а временная сложность равна $O(amt / \min(coins))$. Это на порядок меньше, чем временная сложность решения через динамическое программирование $O(n \times amt)$.
Однако **для некоторых наборов номиналов монет жадный алгоритм не может найти оптимальный ответ**. Ниже показаны два примера.
- **Положительный пример $coins = [1, 5, 10, 20, 50, 100]$**: для такого набора монет при любом $amt$ жадный алгоритм находит оптимальное решение.
- **Отрицательный пример $coins = [1, 20, 50]$**: пусть $amt = 60$. Жадный алгоритм найдет только комбинацию $50 + 1 \times 10$, то есть всего $11$ монет, тогда как динамическое программирование находит оптимум $20 + 20 + 20$, где требуется лишь $3$ монеты.
- **Отрицательный пример $coins = [1, 49, 50]$**: пусть $amt = 98$. Жадный алгоритм найдет только комбинацию $50 + 1 \times 48$, то есть всего $49$ монет, тогда как динамическое программирование находит оптимум $49 + 49$, где требуется лишь $2$ монеты.
![Примеры, где жадный алгоритм не находит оптимального решения](greedy_algorithm.assets/coin_change_greedy_vs_dp.png){ class="animation-figure" }
<p align="center"> Рисунок 15-2 &nbsp; Примеры, где жадный алгоритм не находит оптимального решения </p>
Иными словами, в задаче о размене монет жадный алгоритм не гарантирует нахождение глобально оптимального решения и иногда может приводить к очень плохому ответу. Для этой задачи больше подходит динамическое программирование.
В общем случае жадный алгоритм применим в двух следующих ситуациях.
1. **Можно гарантировать нахождение оптимального решения**: в таком случае жадный алгоритм часто является лучшим выбором, поскольку обычно он эффективнее, чем поиск с возвратом и динамическое программирование.
2. **Можно найти приближенно оптимальное решение**: в таком случае жадный алгоритм тоже полезен. Для многих сложных задач поиск глобального оптимума очень труден, и возможность быстро найти субоптимальный ответ уже весьма ценна.
## 15.1.2 &nbsp; Свойства жадного алгоритма
Тогда возникает вопрос: какие задачи подходят для решения жадным алгоритмом? Или, другими словами, в каких случаях жадный алгоритм может гарантировать оптимальный ответ?
По сравнению с динамическим программированием условия применения жадного алгоритма строже. В основном нас интересуют два свойства задачи.
- **Свойство жадного выбора**: только когда локально оптимальный выбор всегда может привести к глобально оптимальному решению, жадный алгоритм способен гарантировать оптимум.
- **Оптимальная подструктура**: оптимальное решение исходной задачи содержит оптимальные решения подзадач.
Оптимальная подструктура уже обсуждалась в главе «Динамическое программирование», поэтому здесь не будем повторяться. Стоит отметить, что у некоторых задач оптимальная подструктура не столь очевидна, но их все равно можно решать жадным алгоритмом.
Основное внимание мы уделяем тому, как определить свойство жадного выбора. Хотя формулировка выглядит довольно простой, **на практике для многих задач доказать свойство жадного выбора совсем не легко**.
Например, в задаче о размене монет легко привести контрпример и опровергнуть свойство жадного выбора, но вот доказать его истинность намного сложнее. Если спросить: **для каких наборов монет можно использовать жадный алгоритм**? - обычно удается дать лишь интуитивный или примерный ответ, а не строгое математическое доказательство.
!!! quote
Существует статья, в которой приводится алгоритм со временной сложностью $O(n^3)$ для определения того, можно ли с помощью жадного алгоритма находить оптимальный размен для любой суммы в заданной системе монет.
Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.
## 15.1.3 &nbsp; Этапы решения задач жадным алгоритмом
В общем виде процесс решения жадной задачи можно разбить на три шага.
1. **Анализ задачи**: разобраться в свойствах задачи, включая определение состояний, целевой функции и ограничений. Этот этап присутствует и в поиске с возвратом, и в динамическом программировании.
2. **Определение жадной стратегии**: определить, какой жадный выбор следует делать на каждом шаге. Эта стратегия должна уменьшать размер задачи на каждом этапе и в итоге привести к решению всей задачи.
3. **Доказательство корректности**: обычно требуется доказать, что задача обладает свойством жадного выбора и оптимальной подструктурой. На этом этапе может понадобиться математическое доказательство, например индукция или доказательство от противного.
Определение жадной стратегии - это ключевой этап решения, но на практике он часто оказывается непростым по следующим причинам.
- **Жадные стратегии для разных задач сильно различаются**. Для многих задач стратегия довольно очевидна, и до нее можно дойти за счет общих рассуждений и нескольких проб. Но в более сложных задачах жадная стратегия может быть очень скрытой, и тут уже многое зависит от опыта решения задач и алгоритмической подготовки.
- **Некоторые жадные стратегии выглядят убедительно, но оказываются обманчивыми**. Бывает, что мы с уверенностью придумали жадную стратегию, написали код и отправили его на проверку, а часть тестов не проходит. Причина в том, что спроектированная стратегия лишь «частично верна», и описанная выше задача о размене монет - типичный пример.
Чтобы гарантировать корректность, нужно дать строгое математическое доказательство жадной стратегии, **обычно с использованием доказательства от противного или математической индукции**.
Однако и доказательство корректности может оказаться непростой задачей. Если идей нет, мы обычно начинаем отлаживать код на тестовых примерах, постепенно меняя и проверяя жадную стратегию.
## 15.1.4 &nbsp; Типичные задачи для жадного алгоритма
Жадные алгоритмы часто применяются в задачах оптимизации, которые обладают свойством жадного выбора и оптимальной подструктурой. Ниже приведены некоторые типичные задачи, решаемые жадным подходом.
- **Задача о размене монет**: при некоторых системах монет жадный алгоритм всегда дает оптимальный ответ.
- **Задача о расписании интервалов**: пусть есть несколько задач, каждая выполняется в некотором временном интервале, и требуется завершить как можно больше задач. Если каждый раз выбирать задачу с самым ранним временем окончания, то жадный алгоритм дает оптимальный ответ.
- **Задача о дробном рюкзаке**: дана группа предметов и грузоподъемность. Требуется выбрать предметы так, чтобы их общий вес не превышал ограничение, а общая ценность была максимальной. Если каждый раз выбирать предмет с наилучшим отношением стоимости к весу, то в некоторых случаях жадный алгоритм дает оптимальный ответ.
- **Задача о покупке и продаже акций**: дана история цен акции. Можно совершать несколько сделок, но если акция уже куплена, то до продажи покупать снова нельзя. Цель - получить максимальную прибыль.
- **Код Хаффмана**: это жадный алгоритм для сжатия данных без потерь. Построив дерево Хаффмана и каждый раз объединяя два узла с наименьшей частотой, мы получаем дерево с минимальной взвешенной длиной пути, то есть минимальной длиной кодирования.
- **Алгоритм Дейкстры**: это жадный алгоритм решения задачи о кратчайших путях от заданной исходной вершины до всех остальных вершин.
+22
View File
@@ -0,0 +1,22 @@
---
comments: true
icon: material/head-heart-outline
---
# Глава 15. &nbsp; Жадность
![Жадность](../assets/covers/chapter_greedy.jpg){ class="cover-image" }
!!! abstract
Подсолнух поворачивается к солнцу, постоянно стремясь к наилучшим условиям для роста.
Жадная стратегия через цепочку простых выборов постепенно приводит к наилучшему ответу.
## Содержание главы
- [15.1 &nbsp; Жадный алгоритм](greedy_algorithm.md)
- [15.2 &nbsp; Задача о дробном рюкзаке](fractional_knapsack_problem.md)
- [15.3 &nbsp; Задача о максимальной вместимости](max_capacity_problem.md)
- [15.4 &nbsp; Задача о максимальном произведении разбиения](max_product_cutting_problem.md)
- [15.5 &nbsp; Резюме](summary.md)
@@ -0,0 +1,451 @@
---
comments: true
---
# 15.3 &nbsp; Задача о максимальной вместимости
!!! question
Дан массив $ht$, где каждый элемент обозначает высоту вертикальной перегородки. Любые две перегородки в массиве вместе с пространством между ними образуют контейнер.
Вместимость контейнера равна произведению высоты и ширины (площади), где высота определяется более короткой перегородкой, а ширина - разностью индексов двух перегородок в массиве.
Требуется выбрать две перегородки так, чтобы образованный ими контейнер имел максимальную вместимость. Пример показан на рисунке 15-7.
![Пример данных для задачи о максимальной вместимости](max_capacity_problem.assets/max_capacity_example.png){ class="animation-figure" }
<p align="center"> Рисунок 15-7 &nbsp; Пример данных для задачи о максимальной вместимости </p>
Контейнер образуется произвольными двумя перегородками, **поэтому состоянием задачи служит пара индексов этих перегородок, обозначим ее как $[i, j]$**.
Согласно условию, вместимость равна произведению высоты на ширину, где высота определяется короткой перегородкой, а ширина - разностью индексов двух перегородок. Обозначим вместимость через $cap[i, j]$, тогда формула принимает вид:
$$
cap[i, j] = \min(ht[i], ht[j]) \times (j - i)
$$
Пусть длина массива равна $n$. Тогда число пар перегородок, то есть общее число состояний, равно $C_n^2 = \frac{n(n - 1)}{2}$. Самый прямолинейный подход - **перебрать все состояния**, после чего найти максимальную вместимость. Его временная сложность равна $O(n^2)$.
### 1. &nbsp; Определение жадной стратегии
У этой задачи есть и более эффективное решение. Как показано на рисунке 15-8, рассмотрим состояние $[i, j]$, где индексы удовлетворяют $i < j$, а высоты - условию $ht[i] < ht[j]$, то есть $i$ - короткая перегородка, а $j$ - длинная.
![Начальное состояние](max_capacity_problem.assets/max_capacity_initial_state.png){ class="animation-figure" }
<p align="center"> Рисунок 15-8 &nbsp; Начальное состояние </p>
Как показано на рисунке 15-9, **если в этот момент сдвинуть длинную перегородку $j$ ближе к короткой перегородке $i$, то вместимость обязательно уменьшится**.
Причина в том, что после смещения длинной перегородки $j$ ширина $j-i$ обязательно станет меньше, а высота определяется короткой перегородкой, поэтому высота либо останется прежней (если $i$ останется короткой перегородкой), либо уменьшится (если сдвинутая $j$ станет короткой перегородкой).
![Состояние после перемещения длинной перегородки внутрь](max_capacity_problem.assets/max_capacity_moving_long_board.png){ class="animation-figure" }
<p align="center"> Рисунок 15-9 &nbsp; Состояние после перемещения длинной перегородки внутрь </p>
Рассуждая в обратную сторону, **только сдвигая короткую перегородку $i$ внутрь, мы можем получить шанс увеличить вместимость**. Хотя ширина при этом обязательно уменьшится, **высота может возрасти** (если после перемещения короткая перегородка $i$ станет выше). Например, на рисунке 15-10 после перемещения короткой перегородки площадь увеличивается.
![Состояние после перемещения короткой перегородки внутрь](max_capacity_problem.assets/max_capacity_moving_short_board.png){ class="animation-figure" }
<p align="center"> Рисунок 15-10 &nbsp; Состояние после перемещения короткой перегородки внутрь </p>
Отсюда и выводится жадная стратегия для этой задачи: инициализировать два указателя по краям контейнера и на каждом шаге сдвигать внутрь указатель, соответствующий короткой перегородке, пока указатели не встретятся.
На рисунках ниже показан процесс выполнения этой жадной стратегии.
1. В начальном состоянии указатели $i$ и $j$ стоят на двух концах массива.
2. Вычислить вместимость текущего состояния $cap[i, j]$ и обновить максимальную вместимость.
3. Сравнить высоты перегородок $i$ и $j$, после чего сдвинуть короткую перегородку на одну позицию внутрь.
4. Повторять шаги `2.` и `3.` до тех пор, пока $i$ и $j$ не встретятся.
=== "<1>"
![Жадный процесс решения задачи о максимальной вместимости](max_capacity_problem.assets/max_capacity_greedy_step1.png){ class="animation-figure" }
=== "<2>"
![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png){ class="animation-figure" }
=== "<3>"
![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png){ class="animation-figure" }
=== "<4>"
![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png){ class="animation-figure" }
=== "<5>"
![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png){ class="animation-figure" }
=== "<6>"
![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png){ class="animation-figure" }
=== "<7>"
![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png){ class="animation-figure" }
=== "<8>"
![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png){ class="animation-figure" }
=== "<9>"
![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png){ class="animation-figure" }
<p align="center"> Рисунок 15-11 &nbsp; Жадный процесс решения задачи о максимальной вместимости </p>
### 2. &nbsp; Код реализации
Цикл в коде выполняется не более $n$ раз, **поэтому временная сложность равна $O(n)$**.
Переменные $i$, $j$, $res$ используют дополнительную память постоянного размера, **поэтому пространственная сложность равна $O(1)$**.
=== "Python"
```python title="max_capacity.py"
def max_capacity(ht: list[int]) -> int:
"""Максимальная вместимость: жадный алгоритм"""
# Инициализировать i и j так, чтобы они располагались по двум концам массива
i, j = 0, len(ht) - 1
# Начальная максимальная вместимость равна 0
res = 0
# Выполнять жадный выбор в цикле, пока две доски не встретятся
while i < j:
# Обновить максимальную вместимость
cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
# Сдвигать внутрь более короткую сторону
if ht[i] < ht[j]:
i += 1
else:
j -= 1
return res
```
=== "C++"
```cpp title="max_capacity.cpp"
/* Максимальная вместимость: жадный алгоритм */
int maxCapacity(vector<int> &ht) {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
int i = 0, j = ht.size() - 1;
// Начальная максимальная вместимость равна 0
int res = 0;
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while (i < j) {
// Обновить максимальную вместимость
int cap = min(ht[i], ht[j]) * (j - i);
res = max(res, cap);
// Сдвигать внутрь более короткую сторону
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
```
=== "Java"
```java title="max_capacity.java"
/* Максимальная вместимость: жадный алгоритм */
int maxCapacity(int[] ht) {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
int i = 0, j = ht.length - 1;
// Начальная максимальная вместимость равна 0
int res = 0;
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while (i < j) {
// Обновить максимальную вместимость
int cap = Math.min(ht[i], ht[j]) * (j - i);
res = Math.max(res, cap);
// Сдвигать внутрь более короткую сторону
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
```
=== "C#"
```csharp title="max_capacity.cs"
/* Максимальная вместимость: жадный алгоритм */
int MaxCapacity(int[] ht) {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
int i = 0, j = ht.Length - 1;
// Начальная максимальная вместимость равна 0
int res = 0;
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while (i < j) {
// Обновить максимальную вместимость
int cap = Math.Min(ht[i], ht[j]) * (j - i);
res = Math.Max(res, cap);
// Сдвигать внутрь более короткую сторону
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
```
=== "Go"
```go title="max_capacity.go"
/* Максимальная вместимость: жадный алгоритм */
func maxCapacity(ht []int) int {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
i, j := 0, len(ht)-1
// Начальная максимальная вместимость равна 0
res := 0
// Выполнять жадный выбор в цикле, пока две доски не встретятся
for i < j {
// Обновить максимальную вместимость
capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i)
res = int(math.Max(float64(res), float64(capacity)))
// Сдвигать внутрь более короткую сторону
if ht[i] < ht[j] {
i++
} else {
j--
}
}
return res
}
```
=== "Swift"
```swift title="max_capacity.swift"
/* Максимальная вместимость: жадный алгоритм */
func maxCapacity(ht: [Int]) -> Int {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
var i = ht.startIndex, j = ht.endIndex - 1
// Начальная максимальная вместимость равна 0
var res = 0
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while i < j {
// Обновить максимальную вместимость
let cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
// Сдвигать внутрь более короткую сторону
if ht[i] < ht[j] {
i += 1
} else {
j -= 1
}
}
return res
}
```
=== "JS"
```javascript title="max_capacity.js"
/* Максимальная вместимость: жадный алгоритм */
function maxCapacity(ht) {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
let i = 0,
j = ht.length - 1;
// Начальная максимальная вместимость равна 0
let res = 0;
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while (i < j) {
// Обновить максимальную вместимость
const cap = Math.min(ht[i], ht[j]) * (j - i);
res = Math.max(res, cap);
// Сдвигать внутрь более короткую сторону
if (ht[i] < ht[j]) {
i += 1;
} else {
j -= 1;
}
}
return res;
}
```
=== "TS"
```typescript title="max_capacity.ts"
/* Максимальная вместимость: жадный алгоритм */
function maxCapacity(ht: number[]): number {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
let i = 0,
j = ht.length - 1;
// Начальная максимальная вместимость равна 0
let res = 0;
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while (i < j) {
// Обновить максимальную вместимость
const cap: number = Math.min(ht[i], ht[j]) * (j - i);
res = Math.max(res, cap);
// Сдвигать внутрь более короткую сторону
if (ht[i] < ht[j]) {
i += 1;
} else {
j -= 1;
}
}
return res;
}
```
=== "Dart"
```dart title="max_capacity.dart"
/* Максимальная вместимость: жадный алгоритм */
int maxCapacity(List<int> ht) {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
int i = 0, j = ht.length - 1;
// Начальная максимальная вместимость равна 0
int res = 0;
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while (i < j) {
// Обновить максимальную вместимость
int cap = min(ht[i], ht[j]) * (j - i);
res = max(res, cap);
// Сдвигать внутрь более короткую сторону
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
```
=== "Rust"
```rust title="max_capacity.rs"
/* Максимальная вместимость: жадный алгоритм */
fn max_capacity(ht: &[i32]) -> i32 {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
let mut i = 0;
let mut j = ht.len() - 1;
// Начальная максимальная вместимость равна 0
let mut res = 0;
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while i < j {
// Обновить максимальную вместимость
let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32;
res = std::cmp::max(res, cap);
// Сдвигать внутрь более короткую сторону
if ht[i] < ht[j] {
i += 1;
} else {
j -= 1;
}
}
res
}
```
=== "C"
```c title="max_capacity.c"
/* Максимальная вместимость: жадный алгоритм */
int maxCapacity(int ht[], int htLength) {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
int i = 0;
int j = htLength - 1;
// Начальная максимальная вместимость равна 0
int res = 0;
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while (i < j) {
// Обновить максимальную вместимость
int capacity = myMin(ht[i], ht[j]) * (j - i);
res = myMax(res, capacity);
// Сдвигать внутрь более короткую сторону
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
```
=== "Kotlin"
```kotlin title="max_capacity.kt"
/* Максимальная вместимость: жадный алгоритм */
fun maxCapacity(ht: IntArray): Int {
// Инициализировать i и j так, чтобы они располагались по двум концам массива
var i = 0
var j = ht.size - 1
// Начальная максимальная вместимость равна 0
var res = 0
// Выполнять жадный выбор в цикле, пока две доски не встретятся
while (i < j) {
// Обновить максимальную вместимость
val cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
// Сдвигать внутрь более короткую сторону
if (ht[i] < ht[j]) {
i++
} else {
j--
}
}
return res
}
```
=== "Ruby"
```ruby title="max_capacity.rb"
=begin
File: max_capacity.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Максимальная вместимость: жадный алгоритм ###
def max_capacity(ht)
# Инициализировать i и j так, чтобы они располагались по двум концам массива
i, j = 0, ht.length - 1
# Начальная максимальная вместимость равна 0
res = 0
# Выполнять жадный выбор в цикле, пока две доски не встретятся
while i < j
# Обновить максимальную вместимость
cap = [ht[i], ht[j]].min * (j - i)
res = [res, cap].max
# Сдвигать внутрь более короткую сторону
if ht[i] < ht[j]
i += 1
else
j -= 1
end
end
res
end
```
??? pythontutor "Визуализация кода"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20i%20%D0%B8%20j%20%D1%82%D0%B0%D0%BA%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BE%D0%BD%D0%B8%20%D1%80%D0%B0%D1%81%D0%BF%D0%BE%D0%BB%D0%B0%D0%B3%D0%B0%D0%BB%D0%B8%D1%81%D1%8C%20%D0%BF%D0%BE%20%D0%B4%D0%B2%D1%83%D0%BC%20%D0%BA%D0%BE%D0%BD%D1%86%D0%B0%D0%BC%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%2C%20%D0%BF%D0%BE%D0%BA%D0%B0%20%D0%B4%D0%B2%D0%B5%20%D0%B4%D0%BE%D1%81%D0%BA%D0%B8%20%D0%BD%D0%B5%20%D0%B2%D1%81%D1%82%D1%80%D0%B5%D1%82%D1%8F%D1%82%D1%81%D1%8F%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%B3%D0%B0%D1%82%D1%8C%20%D0%B2%D0%BD%D1%83%D1%82%D1%80%D1%8C%20%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D1%83%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%3D%20%7Bres%7D%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20i%20%D0%B8%20j%20%D1%82%D0%B0%D0%BA%2C%20%D1%87%D1%82%D0%BE%D0%B1%D1%8B%20%D0%BE%D0%BD%D0%B8%20%D1%80%D0%B0%D1%81%D0%BF%D0%BE%D0%BB%D0%B0%D0%B3%D0%B0%D0%BB%D0%B8%D1%81%D1%8C%20%D0%BF%D0%BE%20%D0%B4%D0%B2%D1%83%D0%BC%20%D0%BA%D0%BE%D0%BD%D1%86%D0%B0%D0%BC%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%D0%9D%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%D1%80%D0%B0%D0%B2%D0%BD%D0%B0%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D1%82%D1%8C%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%20%D0%B2%20%D1%86%D0%B8%D0%BA%D0%BB%D0%B5%2C%20%D0%BF%D0%BE%D0%BA%D0%B0%20%D0%B4%D0%B2%D0%B5%20%D0%B4%D0%BE%D1%81%D0%BA%D0%B8%20%D0%BD%D0%B5%20%D0%B2%D1%81%D1%82%D1%80%D0%B5%D1%82%D1%8F%D1%82%D1%81%D1%8F%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%83%D1%8E%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%D0%A1%D0%B4%D0%B2%D0%B8%D0%B3%D0%B0%D1%82%D1%8C%20%D0%B2%D0%BD%D1%83%D1%82%D1%80%D1%8C%20%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D1%83%D1%8E%20%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D1%83%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D1%8C%20%3D%20%7Bres%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div>
### 3. &nbsp; Доказательство корректности
Жадный алгоритм быстрее полного перебора именно потому, что каждый жадный шаг «пропускает» часть состояний.
Например, в состоянии $cap[i, j]$ перегородка $i$ является короткой, а $j$ - длинной. Если жадно сдвинуть короткую перегородку $i$ на одну позицию внутрь, то состояния, показанные на рисунке 15-12, будут «пропущены». **Это означает, что позже мы уже не сможем проверить вместимость этих состояний**.
$$
cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1]
$$
![Состояния, пропущенные из-за смещения короткой перегородки](max_capacity_problem.assets/max_capacity_skipped_states.png){ class="animation-figure" }
<p align="center"> Рисунок 15-12 &nbsp; Состояния, пропущенные из-за смещения короткой перегородки </p>
Нетрудно заметить, что **эти пропущенные состояния на самом деле и есть все состояния, в которых длинная перегородка $j$ сдвигается внутрь**. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно уменьшает вместимость. Иными словами, пропущенные состояния не могут быть оптимальным решением, **поэтому их пропуск не приводит к потере оптимума**.
Приведенный анализ показывает, что операция перемещения короткой перегородки является «безопасной», а жадная стратегия действительно эффективна.
@@ -0,0 +1,417 @@
---
comments: true
---
# 15.4 &nbsp; Задача о максимальном произведении разбиения
!!! question
Дан положительный целый $n$. Требуется разложить его в сумму как минимум двух положительных целых чисел и найти максимально возможное произведение всех полученных чисел, как показано на рисунке 15-13.
![Определение задачи о максимальном произведении разбиения](max_product_cutting_problem.assets/max_product_cutting_definition.png){ class="animation-figure" }
<p align="center"> Рисунок 15-13 &nbsp; Определение задачи о максимальном произведении разбиения </p>
Предположим, что мы разбили $n$ на $m$ целочисленных множителей, где $i$-й множитель обозначим через $n_i$, то есть
$$
n = \sum_{i=1}^{m}n_i
$$
Цель задачи - найти максимальное произведение всех целочисленных множителей, то есть
$$
\max(\prod_{i=1}^{m}n_i)
$$
Нужно понять: каким должно быть число частей $m$ и какими должны быть значения каждого $n_i$?
### 1. &nbsp; Определение жадной стратегии
Из опыта известно, что произведение двух целых чисел часто больше их суммы. Предположим, что мы выделяем из $n$ множитель $2$, тогда произведение равно $2(n-2)$. Сравним это выражение с $n$:
$$
\begin{aligned}
2(n-2) & \geq n \newline
2n - n - 4 & \geq 0 \newline
n & \geq 4
\end{aligned}
$$
Как показано на рисунке 15-14, когда $n \geq 4$, выделение множителя $2$ увеличивает произведение. **Это означает, что все целые числа, большие либо равные $4$, следует продолжать разбивать**.
**Жадная стратегия 1**: если в схеме разбиения присутствует множитель $\geq 4$, то его нужно дальше разбивать. В конечной схеме разбиения должны остаться только множители $1$, $2$, $3$.
![Разбиение увеличивает произведение](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png){ class="animation-figure" }
<p align="center"> Рисунок 15-14 &nbsp; Разбиение увеличивает произведение </p>
Теперь подумаем, какой множитель является наилучшим. Среди $1$, $2$, $3$ очевидно худшим является $1$, потому что всегда выполняется $1 \times (n-1) < n$, то есть выделение $1$ уменьшает произведение.
Как показано на рисунке 15-15, при $n = 6$ имеем $3 \times 3 > 2 \times 2 \times 2$. **Это означает, что выделять $3$ выгоднее, чем выделять $2$**.
**Жадная стратегия 2**: в схеме разбиения должно быть не более двух множителей $2$. Потому что три двойки всегда можно заменить двумя тройками и получить большее произведение.
![Оптимальные множители разбиения](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png){ class="animation-figure" }
<p align="center"> Рисунок 15-15 &nbsp; Оптимальные множители разбиения </p>
Итак, получаем следующую жадную стратегию.
1. Для заданного целого $n$ непрерывно выделять из него множитель $3$, пока остаток не станет равным $0$, $1$ или $2$.
2. Если остаток равен $0$, это означает, что $n$ кратно $3$, и больше ничего делать не нужно.
3. Если остаток равен $2$, дальнейшее разбиение не требуется, его нужно сохранить.
4. Если остаток равен $1$, то поскольку $2 \times 2 > 1 \times 3$, последний множитель $3$ следует заменить на $2$.
### 2. &nbsp; Код реализации
Как показано на рисунке 15-16, нам не нужен цикл, чтобы выполнять разбиение числа. Можно использовать целочисленное деление вниз, чтобы получить число троек $a$, и операцию взятия остатка, чтобы получить остаток $b$. Тогда имеем:
$$
n = 3 a + b
$$
Обратите внимание, что для граничного случая $n \leq 3$ необходимо выделить множитель $1$, и тогда произведение равно $1 \times (n - 1)$.
=== "Python"
```python title="max_product_cutting.py"
def max_product_cutting(n: int) -> int:
"""Максимальное произведение разрезания: жадный алгоритм"""
# Когда n <= 3, обязательно нужно выделить одну 1
if n <= 3:
return 1 * (n - 1)
# Жадно выделить множители 3, где a — число троек, а b — остаток
a, b = n // 3, n % 3
if b == 1:
# Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return int(math.pow(3, a - 1)) * 2 * 2
if b == 2:
# Если остаток равен 2, ничего не делать
return int(math.pow(3, a)) * 2
# Если остаток равен 0, ничего не делать
return int(math.pow(3, a))
```
=== "C++"
```cpp title="max_product_cutting.cpp"
/* Максимальное произведение разрезания: жадный алгоритм */
int maxProductCutting(int n) {
// Когда n <= 3, обязательно нужно выделить одну 1
if (n <= 3) {
return 1 * (n - 1);
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
int a = n / 3;
int b = n % 3;
if (b == 1) {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return (int)pow(3, a - 1) * 2 * 2;
}
if (b == 2) {
// Если остаток равен 2, ничего не делать
return (int)pow(3, a) * 2;
}
// Если остаток равен 0, ничего не делать
return (int)pow(3, a);
}
```
=== "Java"
```java title="max_product_cutting.java"
/* Максимальное произведение разрезания: жадный алгоритм */
int maxProductCutting(int n) {
// Когда n <= 3, обязательно нужно выделить одну 1
if (n <= 3) {
return 1 * (n - 1);
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
int a = n / 3;
int b = n % 3;
if (b == 1) {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return (int) Math.pow(3, a - 1) * 2 * 2;
}
if (b == 2) {
// Если остаток равен 2, ничего не делать
return (int) Math.pow(3, a) * 2;
}
// Если остаток равен 0, ничего не делать
return (int) Math.pow(3, a);
}
```
=== "C#"
```csharp title="max_product_cutting.cs"
/* Максимальное произведение разрезания: жадный алгоритм */
int MaxProductCutting(int n) {
// Когда n <= 3, обязательно нужно выделить одну 1
if (n <= 3) {
return 1 * (n - 1);
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
int a = n / 3;
int b = n % 3;
if (b == 1) {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return (int)Math.Pow(3, a - 1) * 2 * 2;
}
if (b == 2) {
// Если остаток равен 2, ничего не делать
return (int)Math.Pow(3, a) * 2;
}
// Если остаток равен 0, ничего не делать
return (int)Math.Pow(3, a);
}
```
=== "Go"
```go title="max_product_cutting.go"
/* Максимальное произведение разрезания: жадный алгоритм */
func maxProductCutting(n int) int {
// Когда n <= 3, обязательно нужно выделить одну 1
if n <= 3 {
return 1 * (n - 1)
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
a := n / 3
b := n % 3
if b == 1 {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return int(math.Pow(3, float64(a-1))) * 2 * 2
}
if b == 2 {
// Если остаток равен 2, ничего не делать
return int(math.Pow(3, float64(a))) * 2
}
// Если остаток равен 0, ничего не делать
return int(math.Pow(3, float64(a)))
}
```
=== "Swift"
```swift title="max_product_cutting.swift"
/* Максимальное произведение разрезания: жадный алгоритм */
func maxProductCutting(n: Int) -> Int {
// Когда n <= 3, обязательно нужно выделить одну 1
if n <= 3 {
return 1 * (n - 1)
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
let a = n / 3
let b = n % 3
if b == 1 {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return pow(3, a - 1) * 2 * 2
}
if b == 2 {
// Если остаток равен 2, ничего не делать
return pow(3, a) * 2
}
// Если остаток равен 0, ничего не делать
return pow(3, a)
}
```
=== "JS"
```javascript title="max_product_cutting.js"
/* Максимальное произведение разрезания: жадный алгоритм */
function maxProductCutting(n) {
// Когда n <= 3, обязательно нужно выделить одну 1
if (n <= 3) {
return 1 * (n - 1);
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
let a = Math.floor(n / 3);
let b = n % 3;
if (b === 1) {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return Math.pow(3, a - 1) * 2 * 2;
}
if (b === 2) {
// Если остаток равен 2, ничего не делать
return Math.pow(3, a) * 2;
}
// Если остаток равен 0, ничего не делать
return Math.pow(3, a);
}
```
=== "TS"
```typescript title="max_product_cutting.ts"
/* Максимальное произведение разрезания: жадный алгоритм */
function maxProductCutting(n: number): number {
// Когда n <= 3, обязательно нужно выделить одну 1
if (n <= 3) {
return 1 * (n - 1);
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
let a: number = Math.floor(n / 3);
let b: number = n % 3;
if (b === 1) {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return Math.pow(3, a - 1) * 2 * 2;
}
if (b === 2) {
// Если остаток равен 2, ничего не делать
return Math.pow(3, a) * 2;
}
// Если остаток равен 0, ничего не делать
return Math.pow(3, a);
}
```
=== "Dart"
```dart title="max_product_cutting.dart"
/* Максимальное произведение разрезания: жадный алгоритм */
int maxProductCutting(int n) {
// Когда n <= 3, обязательно нужно выделить одну 1
if (n <= 3) {
return 1 * (n - 1);
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
int a = n ~/ 3;
int b = n % 3;
if (b == 1) {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return (pow(3, a - 1) * 2 * 2).toInt();
}
if (b == 2) {
// Если остаток равен 2, ничего не делать
return (pow(3, a) * 2).toInt();
}
// Если остаток равен 0, ничего не делать
return pow(3, a).toInt();
}
```
=== "Rust"
```rust title="max_product_cutting.rs"
/* Максимальное произведение разрезания: жадный алгоритм */
fn max_product_cutting(n: i32) -> i32 {
// Когда n <= 3, обязательно нужно выделить одну 1
if n <= 3 {
return 1 * (n - 1);
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
let a = n / 3;
let b = n % 3;
if b == 1 {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
3_i32.pow(a as u32 - 1) * 2 * 2
} else if b == 2 {
// Если остаток равен 2, ничего не делать
3_i32.pow(a as u32) * 2
} else {
// Если остаток равен 0, ничего не делать
3_i32.pow(a as u32)
}
}
```
=== "C"
```c title="max_product_cutting.c"
/* Максимальное произведение разрезания: жадный алгоритм */
int maxProductCutting(int n) {
// Когда n <= 3, обязательно нужно выделить одну 1
if (n <= 3) {
return 1 * (n - 1);
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
int a = n / 3;
int b = n % 3;
if (b == 1) {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return pow(3, a - 1) * 2 * 2;
}
if (b == 2) {
// Если остаток равен 2, ничего не делать
return pow(3, a) * 2;
}
// Если остаток равен 0, ничего не делать
return pow(3, a);
}
```
=== "Kotlin"
```kotlin title="max_product_cutting.kt"
/* Максимальное произведение разрезания: жадный алгоритм */
fun maxProductCutting(n: Int): Int {
// Когда n <= 3, обязательно нужно выделить одну 1
if (n <= 3) {
return 1 * (n - 1)
}
// Жадно выделить множители 3, где a — число троек, а b — остаток
val a = n / 3
val b = n % 3
if (b == 1) {
// Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return 3.0.pow((a - 1)).toInt() * 2 * 2
}
if (b == 2) {
// Если остаток равен 2, ничего не делать
return 3.0.pow(a).toInt() * 2 * 2
}
// Если остаток равен 0, ничего не делать
return 3.0.pow(a).toInt()
}
```
=== "Ruby"
```ruby title="max_product_cutting.rb"
=begin
File: max_product_cutting.rb
Created Time: 2024-05-07
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
# ## Максимальное произведение разрезания: жадный алгоритм ###
def max_product_cutting(n)
# Когда n <= 3, обязательно нужно выделить одну 1
return 1 * (n - 1) if n <= 3
# Жадно выделить множители 3, где a — число троек, а b — остаток
a, b = n / 3, n % 3
# Если остаток равен 1, преобразовать одну пару 1 * 3 в 2 * 2
return (3.pow(a - 1) * 2 * 2).to_i if b == 1
# Если остаток равен 2, ничего не делать
return (3.pow(a) * 2).to_i if b == 2
# Если остаток равен 0, ничего не делать
3.pow(a).to_i
end
```
??? pythontutor "Визуализация кода"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D1%8F%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20n%20%3C%3D%203%2C%20%D0%BE%D0%B1%D1%8F%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BE%D0%B4%D0%BD%D1%83%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20%2A%20%28n%20-%201%29%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%BD%D0%BE%D0%B6%D0%B8%D1%82%D0%B5%D0%BB%D0%B8%203%2C%20%D0%B3%D0%B4%D0%B5%20a%20%E2%80%94%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%82%D1%80%D0%BE%D0%B5%D0%BA%2C%20%D0%B0%20b%20%E2%80%94%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%0A%20%20%20%20a%2C%20b%20%3D%20n%20%2F%2F%203%2C%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%201%2C%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%201%20%2A%203%20%D0%B2%202%20%2A%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%20-%201%29%29%20%2A%202%20%2A%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%202%2C%20%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BD%D0%B5%20%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%20%2A%202%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%200%2C%20%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BD%D0%B5%20%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%0A%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%7Bres%7D%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D1%8F%3A%20%D0%B6%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%22%22%22%0A%20%20%20%20%23%20%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0%20n%20%3C%3D%203%2C%20%D0%BE%D0%B1%D1%8F%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%20%D0%BD%D1%83%D0%B6%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BE%D0%B4%D0%BD%D1%83%201%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20%2A%20%28n%20-%201%29%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D0%BE%20%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D0%BC%D0%BD%D0%BE%D0%B6%D0%B8%D1%82%D0%B5%D0%BB%D0%B8%203%2C%20%D0%B3%D0%B4%D0%B5%20a%20%E2%80%94%20%D1%87%D0%B8%D1%81%D0%BB%D0%BE%20%D1%82%D1%80%D0%BE%D0%B5%D0%BA%2C%20%D0%B0%20b%20%E2%80%94%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%0A%20%20%20%20a%2C%20b%20%3D%20n%20%2F%2F%203%2C%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%201%2C%20%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%BE%D0%B4%D0%BD%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%201%20%2A%203%20%D0%B2%202%20%2A%202%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%20-%201%29%29%20%2A%202%20%2A%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%202%2C%20%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BD%D0%B5%20%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%20%2A%202%0A%20%20%20%20%23%20%D0%95%D1%81%D0%BB%D0%B8%20%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BE%D0%BA%20%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%200%2C%20%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BD%D0%B5%20%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%0A%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%D0%96%D0%B0%D0%B4%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5%20%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%20%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D1%8F%20%3D%20%7Bres%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=5&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div>
![Метод вычисления максимального произведения разбиения](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png){ class="animation-figure" }
<p align="center"> Рисунок 15-16 &nbsp; Метод вычисления максимального произведения разбиения </p>
**Временная сложность зависит от того, как в языке программирования реализовано возведение в степень**. Если взять Python, то обычно используются три распространенные функции для вычисления степени.
- Оператор `**` и функция `pow()` имеют временную сложность $O(\log a)$.
- Функция `math.pow()` внутри вызывает функцию `pow()` из библиотеки C, выполняющую возведение в степень с плавающей точкой, и ее временная сложность равна $O(1)$.
Переменные $a$ и $b$ занимают дополнительную память постоянного размера, **поэтому пространственная сложность равна $O(1)$**.
### 3. &nbsp; Доказательство корректности
Используем доказательство от противного и рассмотрим только случай $n \geq 4$.
1. **Все множители $\leq 3$**: предположим, что в оптимальной схеме разбиения существует множитель $x \geq 4$. Тогда его можно дальше разложить в $2(x-2)$ и получить большее или равное произведение. Это противоречит предположению.
2. **Схема разбиения не содержит $1$**: предположим, что в оптимальной схеме присутствует множитель $1$. Тогда его можно объединить с другим множителем и получить большее произведение. Это противоречит предположению.
3. **Схема разбиения содержит не более двух $2$**: предположим, что в оптимальной схеме присутствуют три двойки. Тогда их можно заменить двумя тройками и получить большее произведение. Это противоречит предположению.
+18
View File
@@ -0,0 +1,18 @@
---
comments: true
---
# 15.5 &nbsp; Резюме
### 1. &nbsp; Ключевые моменты
- Жадный алгоритм обычно используется для решения задач оптимизации. Его принцип состоит в том, чтобы на каждом этапе принятия решения делать локально оптимальный выбор в надежде получить глобально оптимальный ответ.
- Жадный алгоритм итеративно делает один жадный выбор за другим, на каждом шаге превращая задачу в подзадачу меньшего размера, пока задача не будет полностью решена.
- Жадный алгоритм не только прост в реализации, но и часто обладает высокой эффективностью. По сравнению с динамическим программированием его временная сложность обычно ниже.
- В задаче о размене монет для некоторых наборов монет жадный алгоритм способен гарантировать оптимальный ответ, а для других наборов - нет: он может дать очень плохое решение.
- Задачи, подходящие для жадного алгоритма, обладают двумя ключевыми свойствами: свойством жадного выбора и оптимальной подструктурой. Свойство жадного выбора отражает корректность жадной стратегии.
- Для некоторых сложных задач доказать свойство жадного выбора непросто. Относительно легче найти контрпример и опровергнуть его, как это видно на примере задачи о размене монет.
- Решение жадной задачи обычно состоит из трех шагов: анализ задачи, определение жадной стратегии и доказательство корректности. Из них ключевым является выбор жадной стратегии, а доказательство корректности часто оказывается самым трудным.
- В задаче о дробном рюкзаке, в отличие от задачи о рюкзаке 0-1, разрешено брать часть предмета, поэтому ее можно решать жадным алгоритмом. Корректность жадной стратегии доказывается методом от противного.
- Задачу о максимальной вместимости можно решать полным перебором со временной сложностью $O(n^2)$. Разработав жадную стратегию со сдвигом короткой перегородки внутрь на каждом шаге, временную сложность можно оптимизировать до $O(n)$.
- В задаче о максимальном произведении разбиения мы последовательно выводим две жадные стратегии: все целые числа $\geq 4$ следует дальше разбивать, а оптимальным множителем разбиения является $3$. В коде присутствуют операции возведения в степень, поэтому временная сложность зависит от способа их реализации и обычно равна $O(1)$ или $O(\log n)$.