В учебной, научной и инженерной деятельности достаточно часто возникает необходимость в решении т.н. трансцендентных уравнений. Такими уравнениями называются уравнения, не являющееся алгебраическим. Обычно это уравнения, содержащие показательные, логарифмические, тригонометрические, обратные тригонометрические функции. Обычно при их решении поступают следующим образом. Вначале любым способом выделяют зоны корней уравнения, т.е. диапазоны значений аргумента на границах которого функция меняет знак. Затем, на втором этапе, проводится уточнение корней. Рассмотрим наиболее простые методы уточнения корней. Следует отметить, что они применимы к любым уравнениям, включая и алгебраические. В настоящее время известно много методов уточнения корня, мы рассмотрим некоторые из них:
Метод сканирования
Метод половинного деления
Метод хорд
Метод золотого сечения
Метод сканирования
Сущность метода сканирования состоит в следующем. Значение решаемого уравнения вычисляется при различных значениях аргумента от левого(A) до правого(B) с некоторым небольшим (dx) шагом.
Как только обнаружится изменение знака функции, зона интереса сжимается, т.е. за левую границу (A) принимается левая граница обнаруженной последней зоны изменения знака, а за правая (B) соответствующая правая. Вычисляется новое (меньшее) значение dx.
И процесс уточнения корня повторяется. И так до тех пор, пока не будут выполнены условия нахождения корня, упомянутые выше.
Сущность метода сканирования состоит в следующем. Значение решаемого уравнения вычисляется при различных значениях аргумента от левого(A) до правого(B) с некоторым небольшим (dx) шагом.
Как только обнаружится изменение знака функции, зона интереса сжимается, т.е. за левую границу (A) принимается левая граница обнаруженной последней зоны изменения знака, а за правая (B) соответствующая правая. Вычисляется новое (меньшее) значение dx.
И процесс уточнения корня повторяется. И так до тех пор, пока не будут выполнены условия нахождения корня, упомянутые выше.
Далее приводится вариант программы на языке С (компилятор BORLAND C++ 3.1)
//************************************************** // Программа нахождения выделенного корня // методом СКАНИРОВАНИЯ. // Программа сама выбирает шаг движения. //************************************************** #include<stdio.h> #include<conio.h> #include<math.h> float f(float y) { return sin(y); } void main(void) { //0 float b,e,x,dx,A,B; int k; puts("Введи левую, правую границу, абс. точность и начальное число шагов "); scanf("%g%g%g%i",&x,&b,&e,&k); // x=2; b=4; e=1e-7; k=10; A=f(x); // вычисление значения функции на ЛЕВОЙ границе зоны СМЕНЫ ЗНАК dx=(b-x)/k; // вычисление начального шага движения // До тех пор, пока ШАГ движения больше ЗАДАННОЙ АБСОЛЮТНОЙ ТОЧНОСТИ // и модуль значения функции больше ЗАДАННОЙ АБСОЛЮТНОЙ ТОЧНОСТИ while ((fabs(B=f(x+=dx))>e) && (dx>e)) // До тех пока, не изменился знак функции при очередном значении аргумента X dx=(A*B<0)?(((b=x)-(x=x-dx))/k):(dx); printf("n%g",x); getch(); } //-0
Далее приводится текст программы на языке PASCAL. Проверено на компиляторах
PASCALABC 3.1 и PASCALABC.NET 2.2.
{************************************************** Программа нахождения выделенного корня методом СКАНИРОВАНИЯ. Программа сама выбирает шаг движения. **************************************************} uses crt; var b,e,x,dx,A,D:real; k:integer; function f(y:real):real; begin f:= sin(y); end; begin {0} writeln('Введи левую, правую границу, абс. точность и начальное число шагов '); readln(x,b,e,k); { x=2; b=4; e=1e-7; k=10;} A:=f(x); { вычисление значения функции на ЛЕВОЙ границе зоны СМЕНЫ ЗНАКА } D:=A; dx:=(b-x)/k; { вычисление начального шага движения } { До тех пор, пока ШАГ движения больше ЗАДАННОЙ АБСОЛЮТНОЙ ТОЧНОСТИ и модуль значения функции больше ЗАДАННОЙ АБСОЛЮТНОЙ ТОЧНОСТИ} while ((abs(D)>e) and (dx>e)) do begin x:=x+dx; D:=f(x); { До тех пока, не изменился знак функции при очередном значении аргумента X} if (A*D<0) then begin b:=x; x:=x-dx; dx:=(b-x)/k; end; end; writeln(x:12:8); end. {-0}
Далее приводится текс программы на языке QBASIC в компиляторе
MS-DOS QBASIC 1.0.
'********************************************** 'ZERO009.bas 'Программа нахождения выделенного корня 'методом СКАНИРОВАНИЯ. 'Программа сама выбирает шаг движения. '********************************************** DECLARE FUNCTION F! (x AS SINGLE) 'Объявляем глобальные переменные. DIM b AS SINGLE, e AS SINGLE, k AS INTEGER, x AS SINGLE, dx AS SINGLE, A AS SINGLE 'Тело программы CLS 'Очиcтка экрана INPUT "Введите границы, точность и число делений: ", x, b, e, k 'Ввод исходных данных A = F(x)' вычисление значения функции на ЛЕВОЙ границе зоны СМЕНЫ ЗНАК D = A dx = (b - x) / k' вычисление начального шага движения ' До тех пор, пока ШАГ движения больше ЗАДАННОЙ АБСОЛЮТНОЙ ТОЧНОСТИ ' и модуль значения функции больше ЗАДАННОЙ АБСОЛЮТНОЙ ТОЧНОСТИ. WHILE ((ABS(D) > e) AND (dx > e)) x = x + dx D = F(x) ' До тех пока, не изменился знак функции при очередном значении аргумента X IF (A * D < 0) THEN b = x x = x - dx dx = (b - x) / k END IF WEND PRINT x END ' Функция собственно вычисления функции FUNCTION F! (x AS SINGLE) F = SIN(x) END FUNCTION
Ниже приводится текст этой же программы составленный для интерпретатора Python 3.4.3.
#********************************************** # Программа нахождения выделенного корня # методом СКАНИРОВАНИЯ. # Программа сама выбирает шаг движения. #********************************************** import math # Функция, корень которой ищется def F(x): return math.sin(x) x,b,e,k=input('Введите границы, точность и число делений: ').split() x=float(x) b=float(b) e=float(e) k=int(k) D=A=F(x) # вычисление значения функции на ЛЕВОЙ границе зоны СМЕНЫ ЗНАК dx=(b-x)/k # вычисление начального шага движения # До тех пор, пока ШАГ движения больше ЗАДАННОЙ АБСОЛЮТНОЙ ТОЧНОСТИ # и модуль значения функции больше ЗАДАННОЙ АБСОЛЮТНОЙ ТОЧНОСТИ. while ((abs(D)>e) and (dx>e)): x+=dx D=F(x) # До тех пока, не изменился знак функции при очередном значении аргумента X if (A*D<0): b=x x-=dx dx=(b-x)/k print(x) input() # команда ДЕРЖИТ экран
Метод половинного деления
Далее приводится вариант программы на языке С (компилятор BORLAND C++ 3.1)
//****************************************************************** // Получения корня методом ПОЛОВИННОГО ДЕЛЕНИЯ. // Ее достоинством является только ОДНО вычисление функции на // каждом шаге заужения интервала поиска корня. //****************************************************************** #include<stdio.h> #include<math.h> #include<conio.h> int k; // Исследуемая функция double f(double x) { return (1.5-0.5*log(2*x+3)-x); } void main() { double y1,y2,a,b,c,d,eps; clrscr(); puts("nВведите границы (левую и правую) и точность "); scanf("%lf%lf%lf",&a,&b,&eps); y1=f(a); while(b-a>eps) (y1*(y2=f(c=(a+b)/2))<0)?(b=c):(y1=y2,a=c); printf("n%12.10lf %12.10lf",c,y2); getch(); }
Далее приводится текст программы на языке PASCAL. Проверено на компиляторах
PASCALABC 3.1 и PASCALABC.NET 2.2.
{Программа поиска выделенного корня методом половинного деления.} var a,b,eps,c:real; {Функция, корень которой ищется.} function f(x:real):real; begin f:=1.5-0.5*ln(2*x+3)-x; end; {Процедура поиска выделенного корня методом половинного деления. Тут параметры: А/В - левая и правая границы, EPS - абс. точность. С - найденный корень.} procedure zer(a,b,eps:real;var c:real); var fa,fc:real; {0}begin fa:=f(a); while (b-a>eps) do begin c:=(b+a)/2; fc:=f(c); if (fa*fc<0) then b:=c else begin a:=c; fa:=fc; end; end; {-0}end; {3}begin writeln('Введи границы диапазона (левую и правую) и точность '); readln(a,b,eps); zer(a,b,eps,c); writeln('Найденный корень=', c:10:8); writeln('Значение функции в точке корня=',f(c):15:8); readln; {-3}end.
Далее приводится текс программы на языке QBASIC в компиляторе
MS-DOS QBASIC 1.0.
' Получение корня функции методом половинного деления. DECLARE FUNCTION FUN! (x AS SINGLE) DIM A AS SINGLE, B AS SINGLE, C AS SINGLE, E AS SINGLE DIM FA AS SINGLE, FC AS SINGLE 'Тело программы CLS 'Очиcтка экрана INPUT "Введите границы и точность: ", A, B, E 'Ввод исходных данных FA = FUN(A) WHILE (B - A > E) C = (A + B) / 2 FC = FUN(C) IF FC * FA < 0 THEN B = C ELSE FA = FC: A = C WEND PRINT C; FC END ' Функция собственно вычисления функции FUNCTION FUN! (x AS SINGLE) FUN = 1.5 - .5 * LOG(2 * x + 3) - x END FUNCTION
Метод хорд
Рассмотрим третий часто используемый метод уточнения выделенного корня. Он называется метод хорд.
Программа является универсальной и работает вне зависимости от знака функции на краях интервала и изгиба функции между границами.
Определимся с принятыми обозначениями:
eps - абсолютная точность определения корня
A и B - левая и правая граница зоны выделенного корня. Знак функции в этих точках разный
FA - значение функции в точке A
FAM - модуль FA
FB - значение функции в точке B
FBM - модуль FB
C - значение, получаемое на оси аргумента при пересечении его прямой, связывающей значения функции FA и FB.
FC - значение функции в точке С
FCM - модуль FC
Идея алгоритма состоит в следующем:
Уже имеются начальные значения локализации корня (аргумента X при котором знак функции F(X) меняется) А и В.
из подобия треугольников, образованных A-FA-C и B-FB-C следует FA/(C-A)=FB/(B-C)
отсюда в общем случае координата С получается как С=A+FAM*(B-A)/(FBM+FAM), где FAM и FBM - модули значений функции в соответствующих точках.
затем определяется значение и модуль значения
функции в найденной точке С (FC и FCM).
если FCM>eps, то в зависимости от совпадения или не совпадения знака FC и FA найденная точка С вместе с FCM передается или в A или в B, а далее зациклить.
если FCM<eps, то считается что С - корень равнения
Далее приводится вариант программы на языке С (компилятор BORLAND C++ 3.1)
//*************************************************** // Поиск выделенного корня МЕТОДОМ ХОРД. // Программа является универсальной и работает вне зависимости // от знака и изгиба. Ее идея состоит в следующем: // - из подобия треугольников, образованных A-FA-C и // B-FB-C следует FA/(C-A)=FB/(B-C) // - отсюда в общем случае координата С получается как // С=A+FAM*(B-A)/(FBM+FAM), где FAM и FBM - модули // значений функции в соответствующих точках. // - затем определяется значение и модуль значения // функции в найденной точке С (FC и FCM). // - если FCM>eps, то в зависимости от совпадения // или не совпадения знака FC и FA найденная точка // С вместе с FCM передается или в A или в B. // - зациклить. //*************************************************** #include <stdio.h> #include <math.h> #include <conio.h> int k,i; double f(double x) { k++; return 1.5-0.5*log(2*x+3)-x; } void main() { double a=0, b=2, eps=1e-7, x, fx,c,fa,fc,fam,fbm,fcm; // clrscr(); fam=fabs(fa=f(a)); fbm=fabs(f(b)); while ((fcm=(fabs(fc=f(c=a+fam*(b-a)/(fbm+fam)))))>eps) // 12 4 3 5 6 6 7 75342 1 (fa*fc>0)?(a=c, fam=fcm):(b=c, fbm=fcm); printf("nНайден корень=%12.10lf Значение функции там=%12.10lf Вызовов функ. %3d",c,fc,k); }
Далее приводится текст программы на языке PASCAL. Проверено на компиляторах
PASCALABC 3.1 и PASCALABC.NET 2.2.
{ *************************************************** Поиск выделенного корня МЕТОДОМ ХОРД. Программа является универсальной и работает вне зависимости от знака и изгиба. Ее идея состоит в следующем: - из подобия треугольников, образованных A-FA-C и B-FB-C следует FA/(C-A)=FB/(B-C) - отсюда в общем случае координата С получается как С=A+FAM*(B-A)/(FBM+FAM), где FAM и FBM - модули значений функции в соответствующих точках. - затем определяется значение и модуль значения функции в найденной точке С (FC и FCM). - если FCM>eps, то в зависимости от совпадения или не совпадения знака FC и FA найденная точка С вместе с FCM передается или в A или в B. - зациклить. *************************************************** } uses crt; var k,i:integer; a, b, eps, x, c,fa,fc,fam,fbm,fcm:double; function f(x:double):double; begin inc(k); f:=1.5-0.5*ln(2*x+3)-x; end; begin eps:=1e-7; a:=0.1; b:=2; fa:=f(a); fam:=abs(fa); fbm:=abs(f(b)); while (true) do begin c:=a+fam*(b-a)/(fbm+fam); fc:=f(c); fcm:=abs(fc); if (fcm<=eps) then break; if (fa*fc>0) then begin a:=c; fam:=fcm; end else begin b:=c; fbm:=fcm; end; end; writeln('Найден корень=',c:12:10,' Значение функции там=',fc:12:10,' Вызовов функ. ',k:4); end.
Далее приводится текс программы на языке QBASIC в компиляторе
MS-DOS QBASIC 1.0.
DECLARE SUB CHORD (a AS SINGLE, b AS SINGLE, E AS SINGLE) DECLARE SUB POLDEL (a AS SINGLE, b AS SINGLE, E AS SINGLE) DECLARE FUNCTION FUN! (x AS SINGLE) ' Получение корня функции методом ХОРД ' c использованием функций и процедур. DIM a AS SINGLE, b AS SINGLE, E AS SINGLE 'Объявляем глобальные переменные. DIM SHARED K AS INTEGER, c AS SINGLE, fc AS SINGLE 'Тело программы CLS 'Очиcтка экрана INPUT "Введите границы и точность: ", a, b, E 'Ввод исходных данных CHORD a, b, E PRINT c; fc; K END ' Процедура собственно метода ХОРД деления SUB CHORD (a AS SINGLE, b AS SINGLE, E AS SINGLE) DIM fa AS SINGLE, fbm AS SINGLE fa = FUN(a) fam = ABS(fa) fbm = ABS(FUN(b)) WHILE (1 > 0) c = a + fam * (b - a) / (FBm + fam) fc = FUN(c) fcm = ABS(fc) IF (fcm < E) THEN EXIT SUB IF (fa * fc > 0) THEN a = c: fam = fcm ELSE b = c: fbm = fcm END IF WEND END SUB ' Функция собственно вычисления функции FUNCTION FUN! (x AS SINGLE) K = K + 1 FUN = 1.5 - .5 * LOG(2 * x + 3) - x END FUNCTION
Ниже приводится текст этой же программы составленный для компилятора Compaq Visual Fortran 2000.
!*************************************************** ! Программа получения корня уравнения методом хорд. !*************************************************** !************************************ ! Основная программа. !************************************ ! Первая строка - необязательная. program ZERO_HORDA ! Данная строка определяет к какому типу данных ! следует относить идентификаторы, начинающиеся ! с перечисленных букв. В пртивном случае по ! умолчанию или конкретным указанием типов ! конкретных идентификаторов. implicit real*8 (a-f) ! Следующая строка задает конкретные типы ! конкретных идентификаторов. ! Но и без следующей и предыдущих строк ! работать будет точно также по умолчанию. !! real a, b, fa,fb,fc,c ! Задаю левую и правую границы интервала, ! абсолютную точность определения корня и ! начальное значение функции в точке С. a=0.1; b=2.0; eps=0.00001; fc=1.0 ! Вычисляю значение функции на границах интервала. fa=f(a); fb=f(b) ! Циклить до тех пор, пока модуль значения функции ! в промежуточной точке больше точности. do 1 while (abs(fc)>eps) ! Вычисляю новую точку пересечения хорды и оси Х. c=a-fa*(b-a)/(fb-fa) ! Вычисляю там значение функции. fc=f(c) ! Выясняю какую часть отрезка надо отбросить. if ((fa*fc)>0) then ! Переношу левую границу a=c; fa=fc else ! Переношу правую границу b=c; fb=fc end if 1 continue ! Вывожу результат. print *,c ! Оператор конца программы. end program ZERO_HORDA !************************ ! Описание функции. !************************ ! Определяю тип возвращаемого значения. real*8 function f(x) ! Определяю тип формального параметра real*8 x ! Присваиваю возвращаемое значение ! имени функции. f=1.5-0.5*alog(2*x+3)-x ! Оператор конца функции. end function f
Метод золотого сечения
Рассмотрим четвертый часто используемый метод уточнения выделенного корня. Он называется метод золотого сечения. Обычно данный метод используется для одномерной оптимизации.
Алгоритм метода состоит в следующем. Уже имеются начальные значения локализации корня (аргумента X при котором знак функции F(X) меняется) А и В.
Программа является универсальной и работает вне зависимости
от знака функции на краях интервала и изгиба функции между границами.
Определимся с принятыми обозначениями:
EPS - абсолютная точность определения корня
A и B - левая и правая граница зоны выделенного корня.
Знак функции в этих точках разный
D и C - точки (левая и правая симметричные относительно середины интервала A-B) внутри интервала A-B, положение которых определяется
по правилу золотого сечения. Весь интервал так относится к большей части, как большая часть относится к меньшей части. В наших обозначениях это выражается следующей пропорцией: AB/AC=AC/CB и AB/BD=BD/AD
Y1 - значение функции в точке C
Y2 - значение функции в точке D
Идея алгоритма состоит в следующем:
- до тех пор, пока модуль Y2 больше EPS повторяется следующее:
- если знаки Y1 и Y2 одинаковые, то из анализа исключается
область CB, а если разные, то AD.
Соответствующим образом переназначаются A,B,C,D,Y1,Y2.
Достоинством метода является то, что на каждом шаге значение
функции вычисляется всего один раз.
Ниже приведены тексты реализованного алгоритма ЗОЛОТОГО СЕЧЕНИЯ на разных языках программирования.
Далее приводится вариант программы на языке С (компилятор BORLAND C++ 3.1)
//****************************************************************** // Программа поиска корня функции методом ЗОЛОТОГО СЕЧЕНИЯ. // Достоинством этой реализации является то, что на каждом шаге // значение функции вычисляется только ОДИН РАЗ. // При вводе 0.1 2.0 1e-7 получаю 0.7483322894 32 //****************************************************************** #include<stdio.h> #include<math.h> #include<conio.h> int k; // Исследуемая функция double f(double x) { k++; return (1.5-0.5*log(2*x+3)-x); // return sin(x); } void main() { double y1,y2,a,b,c,d,eps; clrscr(); puts("nВведите границы (левую и правую) и относительную точность "); scanf("%lf%lf%lf",&a,&b,&eps); y1=f(c=a+(b-a)*(sqrt(5.)-1)/2); y2=f(d=a+b-c); while(fabs(y2)>eps) (y1*y2>0)?(y1=y2,y2=f(d=a+(b=c)-(c=d))):(y2=y1,y1=f(c=b+(a=d)-(d=c))); // 1 1 2 3 4 4 5 532 6 7 8 8 9 976 printf("n%12.10lf %d",c,k); getch(); }
Далее приводится текст программы на языке PASCAL. Проверено на компиляторах
PASCALABC 3.1 и PASCALABC.NET 2.2.
//****************************************************************** // ZERO07.PAS // Программа поиска корня функции методом ЗОЛОТОГО СЕЧЕНИЯ. // Достоинством этой реализации является то, что на каждом шаге // значение функции вычисляется только ОДИН РАЗ. // При вводе 0.1 2.0 1e-7 получаю 0.7483322895 32 //****************************************************************** uses crt; // Исследуемая функция var y1,y2,a,b,c,d,eps:real; k:integer; function f(x:real):real; begin inc(k); f:=1.5-0.5*ln(2*x+3)-x; end; begin write('Введите границы (левую и правую) и относительную точность '); readln(a,b,eps); c:=a+(b-a)*(sqrt(5.0)-1)/2; d:=a+b-c; y1:=f(c); y2:=f(d); while(abs(y2)>eps) do begin if (y1*y2>0) then begin y1:=y2; b:=c; c:=d; d:=a+b-c; y2:=f(d); end else begin y2:=y1; a:=d; d:=c; c:=b+a-d; y1:=f(c); end; end; write(c:12:10,k:6); end.