План:
Метод на динамичното оптимиране
Задача за монети - минимален брой
Задача за монети - брой разбивания
Разбиване на естествено число
Домино подредица
Разстояние на Левенщайн
Задачи за домашно 1 и 2
unsigned long fib(unsigned n)
{
if (n < 2) return n;
else return fib(n-1) + fib(n-2);
}
Едно решение за избягване на повторно пресмятане на
вече пресметната стойност е да се въведе таблица на всички вече
пресметнати стойности. Всеки път, когато трябва да пресметнем fib(n)за някоя конкретна стойност на
n, първо ще проверяваме дали вече не сме я записали в таблицата и
едва тогава, в случай че задачата все още не е била решавана, ще
извършваме съответните пресмятания.
Програмистка техника, при която се извършва запълване на таблица
с резултатите от решенията на вече решени подзадачи с цел
избягване на повторни пресмятания се нарича динамично оптимиране.
Оптимизационни задачи (Задача за раницата):
-- множество допустими кандидати за решения;
-- целева функция.
На всеки кандидат за решение се съпоставя някакво число - стойност
на целевата функция.
Целта е да се намери решение (оптимално решение), за което
функцията приема своята екстремална (максимална или минимална)
стойност.
Възможно е задачата да има повече от едно оптимални решения (в
общия случай прилагането на динамично оптимиране ще ни даде само
едно от тях).
** Задача за монети - минимален брой [Coin problem, AL p. 65]
Дадени са монети със стойности c1, c2,
..., ck и целева сума n. Задачата е да се направи тази
сума с минимален брой монети.
Пример: Дадени са три вида монети със стойности 1, 3 и 4.
solve(0) = 0
solve(1) = 1
solve(2) = 2
solve(3) = 1
solve(4) = 1
solve(5) = 2
solve(6) = 2
solve(7) = 2
solve(8) = 2
solve(9) = 3
solve(10) = 3
solve: целева сума -> минимален брой монети
Рекурсивна формула за примера:
solve(x) = min{solve(x−1) + 1, solve(x−3) + 1, solve(x−4) + 1} =
min{solve(x−1), solve(x−3) , solve(x−4) } + 1
solve(10) = min{solve(10 - 1), solve(10 - 3), solve(10 - 4)}
+ 1 = min{solve(9), solve(7), solve(6)} = min{3, 2, 2} + 1 = 2 + 1
= 3
solve(10) = solve(7) + 1 = solve(4) + 2 = solve(0) + 3 = 3.
Обща рекурсивна формула за монети със стойности c1, c2,
..., ck :
solve(0) = 0, solve(x) = min {solve(x - ci) + 1, i = 1,
2,..., k}
int solve(int x)
{
if (x == 0) return 0;
int best = INF;
for (int i = 0; i < k; i++)
if (x - c[i] >= 0)
best = min(best, solve(x - c[i]) + 1);
return best;
}
Рекурсия със запомняне на пресметнатите стойности (memorization)
в масива value:
int solve(int x)
{
if (x == 0) return 0;
if (value[x] >= 0) return
value[x];
int best = INF;
for (int i = 0; i < k; i++)
if (x - c[i]
>= 0) best = min(best, solve(x - c[i]) + 1);
value[x] = best;
return best;
}
Итеративен вариант със запомняне на пресметнатите стойности
(memorization) в масива value::
value[0] = 0;
for (int x = 1; x <= n; x++)
{
value[x] = INF;
for (int i = 0; i < k; i++)
if (x - c[i]
>= 0) value[x] = min(value[x], value[x - c[i]] + 1);
}
Така получаваме с колко монети може да се направи сумата. С кои
монети е решението може да се намери със следната модификация:
int first[N];
value[0] = 0;
for (int x = 1; x <= n; x++)
{
value[x] = INF;
for (int i = 0; i < k; i++)
if (x-c >= 0
&& value[x-c]+1 < value[x])
{
value[x] =
value[x-c[i]]+1;
first[x] = c[i];
}
}
while (n > 0)
{
cout << first[n] <<
"\n";
n -= first[n];
}
s/m |
1 |
2 |
3 |
4 |
5 |
6 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
2 |
1 |
2 |
2 |
2 |
2 |
2 |
3 |
1 |
2 |
3 |
3 |
3 |
3 |
4 |
1 |
3 |
4 |
|||
5 |
1 |
|||||
6 |
1 |
L - 1 |
O - 2 |
V- 3 |
E - 4 |
||
0 |
1 |
2 |
3 |
4 |
|
M - 1 |
1 |
min{2, 2, 1} = 1 |
2 |
3 |
4 |
O -2 |
2 |
2 |
1 |
2 |
3 |
V - 3 |
3 |
||||
I - 4 |
4 |
||||
E - 5 |
5 |