
- •Соглашения
- •Общие замечания
- •Порядок выполнения первого задания
- •Общий порядок выполнения очередного (не первого) задания
- •Признак успешности выполнения алгоритма
- •Практическое задание №1. Метод половинного деления
- •Методические указания
- •Порядок выполнения задания
- •Варианты решаемых функций
- •Практическое задание №2. Метод простых итераций
- •Методические указания
- •Порядок выполнения задания
- •Варианты решаемых функций
- •Практическое задание №3. Метод Ньютона
- •Методические указания
- •Порядок выполнения задания
- •Варианты решаемых функций
- •Практическое задание №4. Метод секущих
- •Методические указания
- •Порядок выполнения задания
- •Варианты решаемых функций
- •Практическое задание №5. Метод простых итераций (СЛАУ)
- •Методические указания
- •Порядок выполнения задания
- •Варианты СЛАУ
- •Практическое задание №6. Метод Зейделя (СЛАУ)
- •Методические указания
- •Порядок выполнения задания
- •Варианты СЛАУ
- •Практическое задание №7. Метод Гаусса (СЛАУ)
- •Методические указания
- •Порядок выполнения задания
- •Варианты СЛАУ
- •Практическое задание №8. Интерполяционный многочлен Лагранжа
- •Методические указания
- •Порядок выполнения задания
-если значение признака успешного завершения алгоритма достигло максимально возможного, то функция возвращает текущее или нулевое значение X (счетчик итераций при этом должен инкрементироваться);
-алгоритм вычисляет значение g0 так же, как и в методе 1;
-алгоритм вычисляет значение g1 так же, как и в методе 1;
-алгоритм вычисляет новое приближение решения X так же, как и в методе 1;
-если достигнута заданная точность eps, функция возвращает X, иначе функция возвращает результат вызова самой себя, при этом в качестве начального приближения X0 используется X1, а в качестве начального приближения X1 используется X.
После описания данного алгоритма его следует тщательно оттрассировать (исполнить пошагово). Следует анализировать выполнение алгоритма
вкаждой его точке с тем, чтобы понять, как он работает.
Варианты решаемых функций
В качестве решаемых функций можно использовать любую из задания №1. Чтобы правильно выбрать начальные приближения, нужно убедиться, что значения решаемой функции в точках начальных приближений имеют одинаковые знаки.
Практическое задание №5. Метод простых итераций (СЛАУ)
Краткое описание задания: задана система линейных алгебраических уравнений (СЛАУ) в виде A·X = B, здесь A — квадратная матрица коэффициентов при переменных, B — матрица (вектор-столбец) свободных элементов, X — матрица (вектор-строка) переменных. Методом простых итераций найти ее решение с заданной точностью ε = 0,001.
Методические указания
1.Программа ничего не принимает с клавиатуры. Все исходные данные задаются в коде программы, как правило, в основном модуле, во время вызова функции метода (внутри функции main).
2.Программа решает задачу тремя способами:
а) с помощью итераций и заданием матрицы коэффициентов; б) с помощью итераций и заданием решаемой функции; в) с помощью рекурсии и заданием решаемой функции.
3.Название модуля (файла типа .h) для функции метода — msiters.
4.Название функции метода — siters.
5.Сигнатура функции метода для способа 1 имеет следующий вид:
26
void siters1(int N, double X[], double X1[], double A[], double eps, int* success);
Здесь:
-N — число переменных (размерность СЛАУ);
-X — начальное приближение;
-X1 — новое приближение (результат);
-A — массив коэффициентов и свободных элементов СЛАУ;
-eps — заданная точность решения;
-success — возвращаемый признак успешного выполнения алгоритма.
Сигнатура функции метода для способов 2 и 3 имеет вид:
void sitersM(int N, double X[], double X1[], slauc ef, double eps, int* success);
Здесь:
-M — номер способа (2 или 3);
-N — число переменных (размерность СЛАУ);
-X — начальное приближение;
-X1 — новое приближение (результат);
-slauc — тип функции, вычисляющей очередное приближение;
-ef — функция, вычисляющая очередное приближение;
-eps — заданная точность решения;
-success — возвращаемый признак успешного выполнения алгоритма.
7. Функция метода вызывается в основной функции.
После вызова выводятся строчки результата, число зависит от количества переменных, например:
SUCCESS=11
X1=0.360
X2=0.138
X3=-0.384
Здесь:
SUCCESS — успешность выполнения алгоритма (значение success); Xi — значение переменной xi, вывод должен соответствовать заданной
точности.
Порядок выполнения задания
1.Сначала выполните действие 1, описанное выше в разделе «Общий порядок выполнения очередного (не первого) задания».
2.Добавьте в решение новый модуль типа .h (заголовочный) с названием msiters. Процесс добавления нового модуля описан в практическом задании №1.
27
3. Добавьте в новый модуль файловый комментарий, например:
/* Файл msiters.h |
*/ |
/* ОТИ НИЯУ МИФИ |
*/ |
/* 1ПО-XXД |
*/ |
/* Пономарев Владимир Вадимович |
*/ |
/* Вычислительная математика |
*/ |
/* Программа VM1 |
*/ |
/* |
Метод простых итераций (СЛАУ) */ |
|
/* |
25.03.2013 |
*/ |
4. Для отладки будем решать следующую систему уравнений:
4x1 |
+ 3x2 |
– 3x3 |
= 3 |
|||
x1 |
– |
2x2 |
– |
5x3 |
= |
2 |
5x1 |
– |
3x2 |
+ |
x3 |
= |
1 |
Решение этой системы с точностью 0,001 равно:
x1 = 0,360 x2 = 0,138 x3 = –0,384
Сначала систему нужно преобразовать таким образом, чтобы коэффициенты Aii были бы по абсолютной величине больше суммы абсолютных значений других коэффициентов уравнения номер i. Это необходимо для обеспечения сходимости и достигается перестановкой и линейными преобразованиями. Например, сначала можно переставить третье уравнение на первое место. При этом коэффициент при переменной x1 станет больше суммы коэффициентов при переменных x2 и x3:
5x1 |
– 3x2 |
+ |
x3 |
= |
1 |
|
4x1 |
+ |
3x2 |
– 3x3 |
= |
3 |
|
x1 |
– |
2x2 |
– |
5x3 |
= |
2 |
Затем можно вычесть из первого уравнения второе:
5x1 |
– 3x2 |
+ |
x3 |
= |
1 |
|
x1 |
– |
6x2 |
– 4x3 |
= –2 |
||
x1 |
– |
2x2 |
– |
5x3 |
= |
2 |
В результате достигается необходимое условие для второго уравнения. Для третьего уравнения условие также выполняется.
Теперь можно привести систему к виду Xi = A(Xi – 1) + B. Здесь A — матрица коэффициентов при переменных, B — матрица свободных элементов, Xi – 1 — матрица предыдущего приближенного решения, Xi — матрица нового приближенного решения:
x1 |
= 0x1 |
+ 3/5x2 |
– 1/5x3 |
+ 1/5 |
|||
x2 |
= |
1/6x1 |
+ |
0x2 |
+ 4/6x3 |
+ |
2/6 |
x3 |
= |
1/5x1 |
– |
2/5x2 |
+ 0x3 |
– |
2/5 |
28
Непосредственно из этой системы нужно сформировать массив коэффициентов и функцию для вычисления нового приближения.
Массив коэффициентов нужен для решения способом 1. Массив содержит коэффициенты при переменных и свободные элементы в следующем порядке:
a11, a12, a13, b1,
0 1 2 3
a21, a22, a23, b2,
4 5 6 7
a31, a32, a33, b3
8 9 10 11
Под элементами подписаны индексы элементов в массиве. Модуль функции модуля msiters.h.
Описываем константу, определяющую количество уравнений:
// Число неизвестных СЛАУ
#define NOX 3
Ниже описываем массив коэффициентов, например, так:
// Описание СЛАУ для метода простых итераций
double matr[NOX * NOX + NOX] = { |
1.0/5.0, |
||
0.0, |
3.0/5.0, |
-1.0/5.0, |
|
1.0/6.0, |
0.0, |
4.0/6.0, |
2.0/6.0, |
1.0/5.0, -2.0/5.0, |
0.0, |
-2.0/5.0 |
|
}; |
|
|
|
Для решения вторым способом потребуется функция, которая вычисляет новое приближение. Описываем ее ниже, например, так:
// Функция ищет новое приближение конкретной СЛАУ void slau(double X[], double X1[]) {
X1[0] = (3.0 / 5.0) * X[1] - (1.0 / 5.0) * X[2] + (1.0 / 5.0); X1[1] = ...;
X1[2] = ...;
}
Заметим, что аргумент X — это массив переменных предыдущего приближения, а аргумент X1 — массив переменных нового приближения. Функция вычисляет новое приближение конкретной СЛАУ, и для ее описания использована система вида Xi = A(Xi – 1) + B.
Коэффициенты заданы так, как они получились в результате преобразований исходной СЛАУ, то есть в виде дробей. Чтобы не потерять точность, числитель и знаменатель записаны вещественными константами, а не целочисленными.
Ниже описываем тип этой функции:
29
typedef void (*slauc)(double*, double*);
Ниже описываем функцию, которая определяет момент завершения итераций, назовем ее finish. Функция принимает количество переменных, массив переменных предыдущего приближения, массив переменных нового приближения и заданную точность:
// Определяет момент завершения итераций
int finish(int N, double X[], double X1[], double eps) { return 1;
}
Пока функция возвращает признак завершения 1. Наконец, описываем функцию метода для способа 1:
/* Метод простых итераций (СЛАУ), способ 1 */
void siters1(int N, double X[], double X1[], double A[], double eps, int* success) {
*success = 0; while ( 1 ) {
if (++(*success) > MAX_CYCLE_COUNT) break;
}
}
5. Переходим в основной модуль VM1.cpp.
Сначала включим новый модуль в основной и опишем константу, задающую точность вычислений для системы уравнений:
/* Метод секущих */ #include "msecant.h"
/* Метод простых итераций (СЛАУ) */ #include "msiters.h"
/* Заданная точность вычислений */ #define EPS 0.000001
#define SEPS 0.001
Ниже функции printr опишем функцию prints, предназначенную для вывода результатов решения СЛАУ, например, так:
/* вывод массива переменных */
void prints(int success, int N, double* X) { printf("SUCCESS=%d\n", success);
for (int i = 0; i < N; i++) { printf("X%d=%0.3f\n", (i + 1), X[i]);
}
printf("\n");
}
В основной функции описываем массив X с начальным приближением, равным значениям свободных элементов СЛАУ, массив Y для результата (последнего приближения), затем описываем вызов функции siters1 и вывод результатов:
30
/* Основная функция |
*/ |
|
void _tmain() { |
0; |
|
int success = |
0.0; |
|
double result |
= |
|
double X[NOX] |
= |
{ 0.2, 0.333, -0.4 }; |
double Y[NOX] |
= |
{ 0 }; |
siters1(NOX, X, |
Y, matr, SEPS, &success); |
|
prints(success, |
NOX, Y); |
|
} |
|
|
Запускаем проект на выполнение и убеждаемся, что выводятся какието значения результатов (равные нулю).
Площадка для решения способом 1 готова.
6. Переходим в модуль функции метода msiters.h. Описываем функцию метода для решения способом 2:
/* Метод простых итераций (СЛАУ), способ 2 */
void siters2(int N, double* X, double* X1, slauc ef, double eps, int* success) {
*success = 0; while ( 1 ) {
if (++(*success) > MAX_CYCLE_COUNT) break;
}
}
Ниже описываем функцию для решения способом 3:
/* Метод простых итераций (СЛАУ), способ 3 */
void siters3(int N, double* X, double* X1, slauc ef, double eps, int* success) {
if (++(*success) > MAX_CYCLE_COUNT) return;
}
Возвращаемся в основной модуль и в основной функции описываем вызовы и выводы результатов решения способами 2 и 3:
/* Основная функция |
*/ |
|
void _tmain() { |
|
|
int success = 0; |
0.0; |
|
double result = |
||
double X[NOX] = |
{ 0.2, 0.333, -0.4 }; |
|
double X1[NOX] = { |
0 }; |
|
siters1(NOX, X, |
Y, |
matr, SEPS, &success); |
prints(success, |
NOX, X1); |
|
X[0] = 0.2; X[1] = |
0.333; X[2] = -0.4; |
|
siters2(NOX, X, |
Y, |
slau, SEPS, &success); |
prints(success, |
NOX, Y); |
|
success = 0; |
|
0.333; X[2] = -0.4; |
X[0] = 0.2; X[1] = |
||
siters3(NOX, X, |
Y, |
slau, SEPS, &success); |
prints(success, |
NOX, Y); |
|
} |
|
|
31
Заметим, что поскольку способ 3 будет использовать рекурсию, переменная success перед вызовом функции метода 3 должна быть обнулена.
Запускаем проект на выполнение, и убеждаемся, что вывод результатов происходит во всех трех случаях. При необходимости устраняем ошибки.
7. Переходим в модуль msiters.h и начинаем разрабатывать функцию метода для решения способом 1.
Прежде всего нам понадобятся целочисленные переменные:
/* Метод простых итераций (СЛАУ), способ 1 */
void siters1(int N, double* X, double* X1, double* A, double eps, int* success) {
*success = 0; int i, j, k, m; while ( 1 ) {
if (++(*success) > MAX_CYCLE_COUNT) break;
}
}
Их назначение следующее:
i — номер строки в массиве коэффициентов; j — номер столбца в массиве коэффициентов;
k — номер текущего элемента в массиве коэффициентов;
m — номер первого элемента строки в массиве коэффициентов.
Далее необходимо продумать, как вычислять новое приближение. Прежде всего нужно понять, что вычисление нового приближения ре-
шения СЛАУ предполагает вычисление не одного, а нескольких значений. Количество значений равно количеству переменных, которое задано аргументом N функции метода. Следовательно, внутри бесконечного цикла потребуется еще один цикл с N итерациями, причем цикл типа for. Переменная этого цикла i обозначает номер уравнения от нуля или номер воображаемой строки в линейном массиве коэффициентов, обозначенном в программе переменной-аргументом A. Обозначим этот цикл for-i. Его уже
можно вписывать после проверки на зацикливание:
if (++(*success) > MAX_CYCLE_COUNT) break; for (i ...) {
}
Далее думаем, как вычисляется одно значение, например, X1[0], соответствующее x1. Согласно рекуррентной формуле имеем
x1 = A11·x1 + A12·x2 + A13·x3 + … + A1n·xN + B1 ,
причем коэффициент A11 равен нулю.
Поскольку количество переменных N заранее неизвестно, то вычислить эту сумму можно только с помощью еще одного цикла типа for с пе
32
ременной цикла j (назовем этот цикл for-j). Этот цикл располагается внутри цикла for-i:
if (++(*success) > MAX_CYCLE_COUNT) break; for (i ...) {
for (j ...) {
}
}
Заметим, что с помощью цикла for-j можно вычислить сумму только первых N элементов рекуррентной формулы, потому что эти первые элементы вычисляются в виде произведения Aij·xj, а последний элемент, соответствующий свободному члену, прибавляется как есть. Поэтому свободный член прибавляется сначала, перед циклом for-j, точнее, начальное значение вычисляемого значения X1[i] приравнивается значению свободного элемента уравнения номер i, например, так: X1[i] = A[k].
Этот код, в принципе, уже можно вписать перед циклом for-j.
Однако перед этим нужно вычислить значение k, соответствующее индексу свободного члена i-го уравнения в линейном массиве A, а перед этим
— значение m, соответствующее индексу элемента, который начинает воображаемую строку в массиве A. Сейчас не помешает воспроизвести нумерацию элементов массива A:
a11, a12, a13, b1, // строка 0, соответствует уравнению 1 0 1 2 3
a21, a22, a23, b2, // строка 1, соответствует уравнению 2 4 5 6 7
a31, a32, a33, b3 // строка 2, соответствует уравнению 3 8 9 10 11
Как видим, m должно принимать значения 0, 4, 8, … Так как длина воображаемой строки равна 4 (а в общем случае N+1), значение m можно вычислить по формуле m = i * (N + 1):
i m = i * (3 + 1)
00
14
28
…
Номера свободных элементов k образуют ряд 3, 7, 11, … Чтобы вычислить значение k, нужно всего лишь к m прибавить N:
i |
m |
k = m + 3 |
0 |
0 |
3 |
1 |
4 |
7 |
2 |
8 |
11 |
… |
|
|
33
Теперь можно вписать в функцию соответствующие вычисления:
for (i ...) { m = ...; k = ...;
X1[i] = A[k]; for (j ...) {
}
}
Теперь можно перейти к циклу for-j. В нем к значению X1[i] прибавляется произведение A[k]·X[j]. Причем нужно понимать, что значение k здесь имеет другой смысл и вычисляется заново. Так, для вычисления первого прибавляемого слагаемого A[k]·X[0] (при индексе j, равном 0) k = 0 для вычисления X1[0], k=4 при вычислении X1[1], и k=8 при вычислении X1[2].
Иначе говоря, индекс первого слагаемого равен индексу начала соответствующей строки m, который уже подсчитан. Индексы следующих слагаемых вычисляются с помощью индекса j:
m |
j |
k=m+j |
индекс Aij в массиве A |
индекс Xj в массиве X |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
0 |
2 |
2 |
2 |
2 |
4 |
0 |
4 |
4 |
0 |
4 |
1 |
5 |
5 |
1 |
4 |
2 |
6 |
6 |
2 |
8 |
0 |
8 |
8 |
0 |
8 |
1 |
9 |
9 |
1 |
8 |
2 |
10 |
10 |
2 |
Таким образом, в цикле for-j сначала нужно вычислить k, а затем прибавить к значению X1[i] произведение A[k]·X[j].
После оформления этой части кода нужно убедиться в правильности вычислений. Запускаем проект и пошагово доходим до той части кода, в которой вычисление X1[0] будет завершено (то есть после завершения выполнения цикла for-j в итерации i = 0). Убеждаемся, что вычисленное значение равно 0.479 (три цифры после запятой). Для проверки:
X1[0] = 1/5 + 3/5×1/3 + 1/5×2/5 = 0.48 точно.
Если результат правильный, переходим к вычислению условия завершения. У нас есть функция finish, которая должна возвращать 0 в случае, если хотя бы одно абсолютное значение разности X1[i] – X[i] меньше значения заданной точности, и единицу в противоположном случае (i — номер уравнения от нуля). Эту функцию нужно вызвать после завершения циклов for, и если функция возвращает единицу, выйти из цикла (при по-
мощи break).
34