Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
2012 Вычислительная математика практика 01-08.pdf
Скачиваний:
17
Добавлен:
05.06.2015
Размер:
550.13 Кб
Скачать

-если значение признака успешного завершения алгоритма достигло максимально возможного, то функция возвращает текущее или нулевое значение 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