Применение ряда Фибоначчи
Пример 2. Определение минимума функции с помощью ряда Фибоначчи.
Ряд Фибоначчи много интересных применений в математике. О некоторых из них мы поговорим позже, а на этом занятии разберем использование этого ряда для поиска минимума функции на заданном промежутке (смотри также В.Ф. Очков, Ю.В. Пухначев, "128 советов начинающему программисту", Москва, 1991 г.).
Для примера рассмотрим функцию
на промежутке (0, 2). График этой функции, а точнее, его часть на промежутка (0, 2) показан на рисунке 23.Рис. 23
Итак, стоит задача найти минимум функции на этом промежутке, т.е. значение x, при котором получается минимум и значение функции y в этой точке. Первая мысль, которая возникает - это делить отрезок (0, 2) пополам, затем выяснять, на каком из получившихся частей может находится минимум и делить эту часть пополам и так далее. Такой процесс возможен, но имеет два существенных недостатка.
Во-первых, "приближение" к точке минимума будет очень медленным, придется испробовать много вариантов, чтобы "подобраться" к искомой точке.
Во-вторых, при даже очень большом количестве делений точность приближения будет невелика.
Поэтому, возникает необходимость как-то иначе делить отрезок (если мы избрали метод деления отрезка). Но как? Отделять от одного конца 4-ю или 3-ю части и постепенно сужать отрезок к лучшему результату не приведет, пожалуй еще и к худшему.
Улучшить процесс деления отрезка помогает ряд Фибоначчи. Может даже возникнуть впечатление, что он как будто специально создан для этой цели. Хотя ряд возник совершенно из других более естественных соображений, он показывает число появления кроликов во 2-й, 3-й, 4-й и т. д. годы, если первоначально есть только одна самка и один самец. Но многие закономерности природы прекрасно описываются математическими средствами, вот и в нашем примере ряд Фибоначчи дает очень хорошие результаты.
Для деления отрезка можно задать число делений - n, а затем в зависимости от n определить коэффициент деления, как отношение (n - 1)-го и n-го членов ряда.
Если a - левый конец промежутка, b - правый, тогда разделить отрезок можно так: x2 := a + (b - a)*fib(n - 1)/fib(n); y2 := f(x2);
Для n = 10 отношение fib(9)/fib(10) = 34/55 = 0.6181818... . Тогда, правая граница станет равна: x2 := 1.236364... . Для дальнейшего процесса поиска, надо "приблизить" левый конец промежутка на такое же расстояние к правому концу (), затем найти соответствующее значения функции (y1 := f(x1) и определить, который из промежутков брать для дальнейшего рассмотрения.
В дальнейшем могут возникнуть несколько случаев.
Если x2 > x1 и y2 > y1, тогда в качестве правого конца промежутка принять x2 и зафиксировать его (b := x2), а x2 заменить на x1 (x2 := x1), y2 присвоить значение y1 (y2:=y1) и повторить процесс приближения левого конца к правому:
x1 := a + b - x2.
Если x2 <= x1 и y2 > y1, тогда a := x2; x2 := x1; y2 := y1 и повторить:
x1 := a + b - x2.
Если x2 > x1 и y2 < y1, тогда a := x1 и выполнить: x1 := a + b - x2.
Если x2 <= x1 и y2 <= y1, тогда b := x1 и выполнить: x1 := a + b - x2.
Если ни одно из этих условий не выполняется, тогда выполнятся оператор:
x1 := a + b - x2
и весь процесс повторяется.
Для составления программы потребуется функция, вычисляющая члены ряда Фибоначчи, уже знакомая нам:
{ Функция вычисления членов ряда Фибоначчи }
Function fib(n : integer) : real;
var
f, f1, f2 : real;
i : integer;
begin
f1 := 1; f := 0;
for i := 1 to n do
begin
f2 := f1; f1 := f;
f := f1 + f2
end;
fib := f
end;
Нужно будет вычислять значения заданной функции и для этого составим следующую функцию:
{ Заданная исследуемая функция }
Function
func(x : real) : real;
begin
func := x*x*x*x - 14*x*x*x + 60*x*x - 70*x
end;
Полностью программа
приводится ниже:
Program Minimumfib;
uses WinCrt;
label 1;
var
a, aa, bb, x, b, x1, x2, y1, y2 : real; i, n : integer;
{----------------------------------------------------------------------------------------}
{ Заданная исследуемая функция }
Function func(x : real) : real;
begin
func := x*x*x*x - 14*x*x*x + 60*x*x - 70*x
end;
{----------------------------------------------------------------------------------------}
{ Функция вычисления членов ряда Фибоначчи }
Function fib(n : integer) : real;
var
f, f1, f2 : real; i: integer;
begin
f1 := 1; f := 0;
for i := 1 to n do
begin
f2 := f1;
f1 := f;
f := f1 + f2
end;
fib := f
end;
{----------------------------------------------------------------------------------------}
{ Основная программа }
begin
write('Введите нижнюю границу промежутка '); readln(a);
aa := a;
write('Введите правую границу промежутка '); readln(b);
bb := b;
write('Введите число приближений к минимуму '); readln(n);
x2 := a + (b - a)*fib(n-1)/fib(n); y2 := func(x2);
for i := 1 to n do
begin
x1 := a + b - x2; y1 := func(x1);
if (x2 > x1) and (y2 > y1)
then
begin
b := x2; x2 := x1; y2 := y1; goto 1
end;
if (x2 <= x1) and (y2 > y1)
then
begin
a := x2; x2 := x1; y2 := y1; goto 1
end;
if (x2 > x1) and (y2 < y1)
then
begin
a := x1; goto 1
end;
if (x2 <= x1) and (y2 <= y1)
then
begin
b := x1; goto 1
end;
1: end;
x := (a + b)/2;
write('Мин.
значение функции на (');
writeln(aa:1:0, ',', bb:2:0, ')');
writeln('Значение функции равно ', func(x):6:12);
writeln('При значении аргумента ', x:6:12)
end.
В этой программе число приближений устанавливается пользователем. Программу можно изменить, указав в условии цикла точность приближения значений аргумента x. Но тогда придется каждый раз при повторении цикла изменять коэффициент деления, так неизвестно конечное число повторений цикла. Для этого можно добавить в программу процедуру вычисления границ промежутка:
{ Процедура вычисления знач. аргумента и функции }
{ approach - приближение }
Procedure approach(a, b : real; n : integer; var x2, y2 : real);
begin
x2 := a + (b - a)*fib(n - 1)/fib(n);
y2 := func(x2)
end;
И тогда, программа станет следующей:
{Поиск минимума функции методом Фибоначчи}
Program Minimumfib;
uses WinCrt;
label 1;
var
a, aa, bb, x, b, x1, x2, y1, y2, e : real;
n : integer;
{----------------------------------------------------------------------------------------}
{ Заданная исследуемая функция }
Function func(x : real) : real;
begin
func := x*x*x*x - 14*x*x*x + 60*x*x - 70*x
end;
{----------------------------------------------------------------------------------------}
{ Функция вычисления членов ряда Фибоначчи }
Function fib(n : integer) : real;
var
f, f1, f2 : real;
i : integer;
begin
f1 := 1; f := 0;
for i := 1 to n do
begin
f2 := f1;
f1 := f;
f := f1+f2
end;
fib := f
end;
{----------------------------------------------------------------------------------------}
{ Процедура вычисления знач.
аргумента и функции }
{ approach - приближение }
Procedure approach(a, b : real; n : integer;
var x2, y2 : real);
begin
x2 := a + (b - a)*fib(n-1)/fib(n);
y2 := func(x2)
end;
{----------------------------------------------------------------------------------------}
{ Основная программа }
begin
write(' Введите нижнюю границу промежутка '); readln(a);
aa := a;
write('Введите правую границу промежутка '); readln(b);
bb := b;
write('Введите точность приближения '); readln(e); n := 3;
approach(a, b, n, x2, y2);
while abs(b - a) > e do
begin
x1 := a + b - x2; y1 := func(x1);
if (x2 > x1) and (y2 > y1)
then
begin
n := n + 1;
approach(a, b, n, x2, y2);
b := x2; x2 := x1; y2 := y1; goto 1
end;
if (x2 <= x1) and (y2 > y1)
then
begin
n := n + 1;
approach(a, b, n, x2, y2);
a := x2; x2 := x1; y2 := y1; goto 1
end;
if (x2 > x1) and (y2 < y1)
then
begin
n := n + 1;
approach(a, b, n, x2, y2);
a := x1; goto 1
end;
if (x2 <= x1) and (y2 <= y1)
then
begin
n := n + 1;
approach(a, b, n, x2, y2);
b := x1; goto 1
end;
n := n + 1;
approach(a, b, n, x2, y2);
1: end;
x := (a + b)/2;
write('Мин. значение функции на промежутке (');
writeln(aa:1:0,',',bb:2:0,')');
writeln('Значение функции равно ', func(x):6:12);
writeln('При значении аргумента ', x:6:12)
end.
Очень часто, при поиске минимума функции для деления отрезка используют так называемое "Золотое сечение".
"Золотое сечение" или "золотое сечение" - деление отрезка на две части так, что большая из них есть средняя пропорциональная между меньшей частью и всем отрезком. Если a - весь отрезок, x - большая из двух частей, тогда:
a : x = x : (a - x)
Решая это уравнение, получаем: получим:
(с точностью до 0.001).
Зная это, можно составить программу поиска минимума функции, используя "золотое сечение", тогда:
x1 := 0.618*a + 0.382*b, x2 := 0.382*a + 0.618*b.
Для вычисления этих значений можно составить две процедуры, которые затем использовать в программе.
Procedure gold1(a, b : real; var
x1, y1 : real);
begin
x1 := 0.618*a + 0.382*b; y1 := fx(x1)
end;
{------------------------------------------------------}
Procedure gold2(a, b : real; var
x2, y2 : real);
begin
x2 := 0.382*a + 0.618*b; y2 := fx(x2)
end;