Матрица — это двумерный массив, каждый элемент которого имеет два индекса: номер строки — $$i$$, номер столбца —$$j$$.
Статический двумерный массив (матрицу) можно объявить так:
тип имя_переменной [n][m];
где тип определяет тип элементов массива, имя_переменной — имя матрицы, $$n$$ — количество строк, $$m$$ — количество столбцов в матрице. Строки нумеруются от 0 до $$n-1$$, столбцы — от 0 до $$m-1$$.
Например,
double x[20][35];
Описана матрица вещественных чисел $$x$$, состоящая из 20 строк и 35 столбцов (строки нумеруются от 0 до 19, столбцы от 0 до 34).
Как и любой другой переменной, матрице можно присвоить начальное значение, например int A[2][3]={{1,2,3}, {4,5,6}};
Для обращения к элементу матрицы необходимо указать её имя, и в квадратных скобках номер строки, а затем в квадратных скобках — номер столбца. Например, x[2][4] — элемент матрицы x, находящийся в третьей строке и пятом
Для работы с элементами матрицы необходимо использовать два цикла. Для построчной обработки матрицы значениями параметра первого (внешнего) цикла будут номера строк матрицы, значениями параметра второго (внутреннего) цикла — номера столбцов (см.рис. 6.1). При построчной обработке матрицы вначале поочерёдно рассматриваются элементы первой строки (столбца), затем второй и т.д. до последней. Если необходимо обрабатывать матрицу по столбцам, то необходимо организовать внешний цикл по столбцам, а внутренний по строкам (см. рис. 6.2).
В предыдущем параграфе мы рассмотрели описание статических матриц, в этом случае память для хранения матрицы выделяется в момент описания. Однако в С++ существует возможность создавать динамические матрицы. Динамические матрицы можно создавать с использованием обычных указателей и с помощью двойных указателей. Рассмотрим оба способа работы с динамическими матрицами последовательно.
(рис 6.1) Блок-схема построчной обработки матрицы
(рис 6.2) Блок-схема обработки матрицы по столбцам
При работе с динамическими матрицами можно использовать обычные указатели. После описания указателя необходимо будет выделить память для хранения $$N\times M$$ элементов ($$N$$ — число строк, $$M$$ — число столбцов). Рассмотрим в качестве примера выделение памяти для хранения целочисленной матрицы размером $$N\times M$$.
int *A, N, M; A=( int *) calloc (N*M, sizeof ( int ) );
Для выделения памяти можно использовать также и функцию malloc
A=( int *) malloc (N*M*sizeof ( int ) );
или операцию new
A=new int [N*M];
Память мы выделили, осталось найти способ обратиться к элементу матрицы. Все элементы матрицы хранятся в одномерном массиве размером $$N\times M$$ элементов. Сначала в этом массиве расположена 0-я строка матрицы, затем 1-я и т.д. Поэтому для обращения к элементу A[i][j] необходимо по номеру строки $$i$$ и номеру столбца $$j$$ вычислить номер этого элемента $$k$$ в динамическом массиве. Учитывая, что в массиве элементы нумеруются с нуля, $$k = iM + j$$. Обращение к элементу A[i][j] будет таким: *(A+i*M+j) или A[i*M+j].
Основной способ работы с динамическими матрицами базируется на использовании двойных указателей. Рассмотрим следующий фрагмент программы.
int main ( )
{
int N,M;
float **a;
a=new float *[N ];
}
С помощью оператора new создан массив из $$n$$ floata[i].
for ( i =0; i<N; i++) a [ i ]=new float [M];
После этого определён массив $$N$$ указателей, каждый из которых адресует массив из $$M$$ чисел (в данном случае вещественных типа float). Фактически создана динамическая матрица размера $$N\times M$$. Обращение к элементу динамической матрицы идёт так же, как и к элементу статической матрицы. Для того, чтобы обратиться к элементу $$a_{i,j}$$ в программе на C++, необходимо указать его имя, и в квадратных скобках номер строки и столбца (a[i][j]).
Рассмотрим основные операции, выполняемые над матрицами (статическими и динамическими) при решении задач.
Матрицы, как и массивы, нужно вводить (выводить) поэлементно. Блок- схема ввода элементов матрицы x[n][m] изображена на рис. 6.3.
(рис 6.3) Ввод элементов матрицы
(рис 6.4) Блок-схема построчного вывода матрицы
При выводе матрицы элементы располагаются построчно, например:
$$\begin{matrix}6-9713\\5838\\378833\\55778837\end{matrix}$$Алгоритм построчного вывода элементов матрицы приведён на рис. 6.4.
Ниже приведён текст программы на C++ ввода-вывода статической матрицы.
#include <iostream>
using namespace std;
int main ( )
{
int i, j,N,M, a [ 20 ] [ 20 ];
cout<<" N = ";
cin>>N; //Ввод количества строк
cout<<" M = ";
cin>>M; //Ввод количества столбцов
cout<<"Ввод матрицы A "<<endl;
for ( i =0; i<N; i++) //Цикл по переменной i, перебираем строки матрицы
for ( j =0; j<M; j++) //Цикл по переменной j, перебираем элементы внутри строки
cin>>a [ i ] [ j ]; //Ввод очередного элемента матрицы
cout<<"Вывод матрицы A "<<endl;
for ( i =0; i<N; i++) //Цикл по переменной i, перебираем строки матрицы
{
for ( j =0; j<M; j++) //Цикл по переменной j, перебираем строки матрицы
cout<<a [ i ] [ j ]<<" \t "; //Вывод очередного элемента матрицы
cout<<endl; //По окончанию вывода всех элементов строки — переход на новую строку.
}
}
Цикл для построчного вывода матрицы можно записать и так.
for ( i =0; i<N; cout<<end l, i++) for ( j =0; j<M; j++) cout<<a [ i ] [ j ]<<" \t ";
При вводе матрицы элементы каждой строки можно разделять пробелами, символами табуляции или Enter. Ниже представлены результаты работы программы.
N=4 M=5 Ввод матрицы A 1 2 3 5 4 3 6 7 8 9 1 2 3 4 5 6 7 8 9 0 Вывод матрицы A 1 2 3 5 4 3 6 7 8 9 1 2 3 4 5 6 7 8 9 0
Далее на примерах решения практических задач будут рассмотрены основные алгоритмы обработки матриц и их реализация в C++. Перед этим давайте вспомним некоторые свойства матриц (рис. 6.5):
(рис 6.5) Свойства элементов матрицы
Задача 6.1. Найти сумму элементов матрицы, лежащих выше главной диагонали.
Алгоритм решения данной задачи (рис. 6.6) построен следующим образом: обнуляем ячейку для накапливания суммы (переменная $$s$$), затем с помощью двух циклов (первый по строкам, второй по столбцам) просматриваем каждый элемент матрицы, но суммируем только в том случае, если этот элемент находится выше главной диагонали (при выполнении условия $$i < j$$).
(рис 6.6) Блок-схема задачи 6.1 (алгоритм 1).
(рис 6.7) Блок-схема задачи 6.1 (алгоритм 2).
Текст программы:
#include <iostream>
using namespace std;
int main ( )
{
int s, i, j, n,m, a [ 20 ] [ 20 ];
cout<<" N = ";
cin>>n;
cout<<" M = ";
cin>>m;
cout<<"Ввод матрицы A "<<endl;
for ( i =0; i<n; i++)
for ( j =0; j<m; j++)
cin>>a [ i ] [ j ];
for ( s= i =0; i<n; i++)
for ( j =0; j<m; j++)
//Если элемент лежит выше главной диагонали, то наращиваем сумму.
if ( j> i ) s+=a [ i ] [ j ];
cout<<" S = "<<s<<endl;
}
На рис. 6.7 изображён ещё один алгоритм решения данной задачи. В нём проверка условия $$i < j$$ не выполняется, но, тем не менее, в нём так же суммируются элементы матрицы, находящиеся выше главной диагонали. В нулевой строке заданной матрицы необходимо сложить все элементы, начиная с первого. В первой — все, начиная со второго, в $$i$$–й строке процесс начнётся с ($$i + 1$$)–го элемента и так далее. Таким образом, внешний цикл работает от 0 до $$N - 1$$, а второй от $$i+1$$ до $$M$$. Авторы надеются, что читатель самостоятельно составит программу, соответствующую описанному алгоритму.
Задача 6.2. Вычислить количество положительных элементов квадратной матрицы, расположенных по её периметру и на диагоналях. Напомним, что в квадратной матрице число строк равно числу столбцов.
Прежде чем приступить к решению задачи, рассмотрим рис. 6.8, на котором изображена схема квадратных матриц различной размерности.
Из условия задачи понятно, что не нужно рассматривать все элементы заданной матрицы. Достаточно просмотреть первую и последнюю строки, первый и последний столбцы, а так же диагонали. Все эти элементы отмечены на схеме, причём чёрным цветом выделены элементы, обращение к которым может произойти дважды. Например, элемент с номером (0, 0) принадлежит как к нулевой строке, так и к нулевому столбцу, а элемент с номером ($$N - 1,N - 1$$) находится в последней строке и последнем столбце одновременно. Кроме того, если $$N$$ — число нечётное (на рис. 6.8 эта матрица расположена слева), то существует элемент с номером ($$N/2,N/2$$), который находится на пересечении главной и побочной диагоналей. При чётном значении $$N$$ (матрица справа на рис. 6.8) диагонали не пересекаются.
(рис 6.8) Рисунок к задаче 6.2.
Итак, разобрав подробно постановку задачи, рассмотрим алгоритм её решения. Для обращения к элементам главной диагонали вспомним, что номера строк этих элементов всегда равны номерам столбцов. Поэтому, если параметр $$i$$ изменяется циклически от 0 до $$N - 1$$, то A[i][i] — элемент главной диагонали. Воспользовавшись свойством, характерным для элементов побочной диагонали, получим: $$i+j+1=N \longrightarrow j=N-i-1,$$ следовательно, для $$i = 0, 1,...,N - 1$$ элемент А[i][N-i-1] — элемент побочной диагонали. Элементы, находящиеся по периметру матрицы записываются следующим образом: А[0][i] — нулевая строка, А[N-1][i] — последняя строка и соответственно А[i][0] — нулевой столбец, А[i][N-1] — последний столбец.
Текст программы решения задачи с пояснениями приведён далее.
#include <iostream>
using namespace std;
int main ( )
{
int k, i, j,N, a [ 20 ] [ 20 ];
cout<<" N = ";
cin>>N;
cout<<"Ввод матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
//k — количество положительных элементов матрицы,
//расположенных по её периметру и на диагоналях.
for ( i=k=0; i<N; i++)
{
if ( a [ i ] [ i ] >0) k++; //Элемент лежит на главной диагонали.
if ( a [ i ] [ N-i -1]>0)k++; //Элемент лежит на побочной диагонали.
}
for ( i =1; i<N-1; i++)
{
if ( a [ 0 ] [ i ] >0) k++; //Элемент находится в нулевой строке.
if ( a [N-1 ] [ i ] >0) k++; //Элемент находится в последней строке.
if ( a [ i ] [ 0 ] > 0 ) k++; //Элемент находится в нулевом столбце.
if ( a [ i ] [ N-1]>0)k++; //Элемент находится в последнем столбце.
}
//Элемент, находящийся на пересечении диагоналей, подсчитан дважды,
//надо уменьшить вычисленное значение k на один.
if ( (N%2!=0)(a [N/ 2 ] [N/2 ] >0) ) k--;
cout<<" k = "<<k<<endl;
}
Задача 6.3. Проверить, является ли заданная квадратная матрица единичной.
Единичной называют матрицу, у которой элементы главной диагонали — единицы, а все остальные — нули. Например,
$$\left(\begin{matrix}10000\\01000\\00100\\00010\\00001\end{matrix}\right)$$Решать задачу будем так. Предположим, что матрица единичная, и попытаемся доказать обратное. Если окажется, что хотя бы один диагональный элемент не равен единице, или любой из элементов вне диагонали не равен нулю, то матрица единичной не является. Воспользовавшись логическими операциями языка С(С++), все эти условия можно соединить в одно и составить программу, текст которой приведён ниже.
#include <iostream>
using namespace std;
int main ( )
{
int pr, i, j,N, **a;
cout<<" N = "; //Ввод размерности матрицы
cin>>N;
a=new int * [N ]; //Создаём квадратную динамическую матрицу
for ( i =0; i<N; i++)
a [ i ]=new int [N ];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
//Предположим, что матрица единичная, и присвоим переменной pr значение 1 (истина).
//Если значение этой переменной при выходе из цикла не изменится, это будет означать,
//что матрица действительно единичная.
for ( pr =1, i =0; i<N; i++)
for ( j =0; j<N; j++)
if ( ( ( i==j )(a [ i ] [ j ] ! = 1 ) ) | | ( ( i != j )(a [ i ] [ j ] ! = 0 ) ) )
//Если элемент лежит на главной диагонали и не равен единице или элемент лежит вне
//главной диагонали и не равен нулю, то
{
pr =0; //Переменной pr присвоить значение 0 (ложь), это будет означать,
break; //что матрица единичной не является, и выйти из цикла.
}
//Проверка значения переменной pr и вывод соответствующего сообщения.
if ( pr ) cout<<"Единичная матрица\n ";
else cout<<"Матрица не является единичной\n ";
}
Задача 6.4.Преобразовать исходную матрицу так, чтобы нулевой элемент каждой строки был заменён средним арифметическим элементов этой строки.
Для решения данной задачи необходимо найти в каждой строке сумму элементов, которую разделить на их количество. Полученный результат записать в нулевой элемент соответствующей строки. Текст программы приведён далее.
#include <iostream>
using namespace std;
int main ( )
{
int i, j,N,M;
double S, **a;
cout<<" N = "; //Ввод размерности матрицы.
cin>>N;
cout<<" M = ";
cin>>M;
a=new double * [N ]; //Создаём динамическую матрицу
for ( i =0; i<N; i++)
a [ i ]=new double [M];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
cin>>a [ i ] [ j ];
//Цикл по i завершается записью среднего значения в нулевой элемент строки и наращиванием i.
for ( i =0; i<N; a [ i ] [ 0 ] = S/M, i++)
for ( S=j =0; j<M; j++) //Вычисление суммы элементов строки.
S+=a [ i ] [ j ];
cout<<"Преобразованная матрица A "<<endl;
for ( i =0; i<N; cout<<end l, i++)
for ( j =0; j<M; j++)
cout<<a [ i ] [ j ]<<" \t ";
}
Задача 6.5. Задана матрица A(n,m). Поменять местами её максимальный и минимальный элементы.
Алгоритм решения этой задачи следующий: находим максимальный элемент матрицы (max) и его индексы (imax, jmax), а также минимальный (min) и его индексы (imin, jmin). После чего элементы A[imax][jmax] и A[imin][jmin] поменяем местами. Для поиска максимального элемента и его индексов в переменную max запишем A[0][0], в переменные imax, jmax (номер строки и столбца, где находятся максимальный элемент) запишем 0. Затем в двойном цикле (цикл по переменной $$i$$ — по строкам, цикл по переменной $$j$$ — по столбцам) перебираем все элементы, и каждый из них сравниваем с максимальным (со значением переменной max). Если текущий элемент массива оказывается больше максимального, то его переписываем в переменную max, а в переменную imax — текущее значение индекса $$i$$, в переменную jmax — текущее значение $$j$$. Поиск минимального элемента матрицы аналогичен и отличается только знаком операции сравнения. Далее представлен текст программы решения задачи 6.5.
#include <iostream>
using namespace std;
int main ( )
{
int i, j, imax, jmax, imin, jmin,N,M;
double min, max, b, **a;
cout<<" N = "; //Ввод размеров матрицы.
cin>>N;
cout<<" M = ";
cin>>M;
a=new double * [N ]; //Создаём динамическую матрицу
for ( i =0; i<N; i++)
a [ i ]=new double [M];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
cin>>a [ i ] [ j ];
//Двойной цикл для поиска максимального, минимального элементов и их индексов.
for (max=min=a [ 0 ] [ 0 ], imax=jmax=imin=jmin= i =0; i<N; i++)
for ( j =0; j<M; j++)
{
if ( a [ i ] [ j ]>max) {max=a [ i ] [ j ]; imax= i; jmax=j; }
if ( a [ i ] [ j ]<min ) {min=a [ i ] [ j ]; imin= i; jmin=j; }
}
//Обмен двух элементов матрицы.
b=a [ imax ] [ jmax ];
a [ imax ] [ jmax ]=a [ imin ] [ jmin ];
a [ imin ] [ jmin ]=b;
//Вывод преобразованной матрицы.
cout<<"Преобразованная матрица A "<<endl;
for ( i =0; i<N; cout<<end l, i++)
for ( j =0; j<M; j++)
cout<<a [ i ] [ j ]<<" \t ";
}
Задача 6.6. Преобразовать матрицу A(m,n) так, чтобы строки с нечётными индексами были упорядочены по убыванию, с чётными — по возрастанию.
В связи с нумерацией строк в C++ с нуля необходимо помнить, что нулевая, вторая, четвёртая строки упорядочиваются по убыванию, а первая, третья, пятая и т.д. — по возрастанию. Алгоритм решения этой задачи сводится к тому, что уже известный нам по предыдущей главе алгоритм упорядочивания элементов в массиве выполняется для каждой строки матрицы. Блок-схема приведена на рис. 6.9. Текст программы с комментариями приведён ниже.
#include <iostream>
using namespace std;
int main ( )
{
int i, j, k,N,M;
double b, **a;
cout<<" M = "; //Ввод размеров матрицы.
cin>>M;
cout<<" N = ";
cin>>N;
a=new double * [N ]; //Создаём динамическую матрицу
for ( i =0; i<N; i++)
a [ i ]=new double [M];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<M; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
for ( i =0; i<M; i++) //Цикл по i — для перебора строк матрицы.
if ( i%2==0) //Если строка чётна, то
{ //упорядочиваем элементы строки по возрастанию,
for ( k=1;k<N; k++)
for ( j =0; j<N-k; j++)
if ( a [ i ] [ j ]>a [ i ] [ j +1 ])
{
b=a [ i ] [ j ];
a [ i ] [ j ]=a [ i ] [ j + 1 ];
a [ i ] [ j +1]=b;
}
}
else //иначе нечётные строки упорядочиваем по убыванию.
for ( k=1;k<N; k++)
for ( j =0; j<N-k; j++)
if ( a [ i ] [ j ]<a [ i ] [ j +1 ])
{
b=a [ i ] [ j ];
a [ i ] [ j ]=a [ i ] [ j + 1 ];
a [ i ] [ j +1]=b;
}
cout<<"Преобразованная матрица A "<<endl; //Вывод преобразованной матрицы.
for ( i =0; i<M; cout<<endl, i++)
for ( j =0; j<N; j++)
cout<<a [ i ] [ j ]<<" \t ";
}
(рис 6.9) Блок-схема алгоритма задачи 6.6.
Задача 6.7. Поменять местами элементы главной и побочной диагонали матрицы $$A(k,k)$$.
Алгоритм решения задачи следующий: перебираем все строки матрицы (цикл по переменной $$i$$ от 0 до $$k-1$$ в тексте программы), и в каждой строке меняем местами элементы, расположенные на главной и побочной диагоналях (в $$i$$-й строке надо поменять местами элементы A[i][i] и А[i][k-i-1]). Текст программы с комментариями приведён далее.
#include <iostream>
using namespace std;
int main ( )
{
int i, j, k;
double b, ** a;
cout<<" k = "; //Ввод размера матрицы.
cin>>k;
a=new double * [ k ]; //Создаём динамическую матрицу
for ( i =0; i<k; i++)
a [ i ]=new double [ k ];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<k; i++)
for ( j =0; j<k; j++)
cin>>a [ i ] [ j ];
for ( i =0; i<k; i++) //Цикл по строкам.
{//В каждой строке обмен между элементами, лежащими на главной и побочной диагоналях.
b=a [ i ] [ i ];
a [ i ] [ i ]=a [ i ] [ k-1-i ];
a [ i ] [ k-1-i ]=b;
}
cout<<"Преобразованная матрица A "<<endl; //Вывод преобразованной матрицы.
for ( i =0; i<k; cout<<end l, i++)
for ( j =0; j<k; j++)
cout<<a [ i ] [ j ]<<" \t ";
}
Задача 6.8. Заполнить матрицу $$A(6, 6)$$ числами от 1 до 36 следующим образом:
$$\left(\begin{matrix} 123456\\ 121110987\\ 131415161718\\ 242322212019\\ 252627282930\\ 363534333231 \end{matrix}\right)$$Последовательно построчно заполняем матрицу возрастающей арифметической последовательностью 1, 2, 3,..., 36. Чётные строки заполняем от нулевого элемента к последнему, а нечётные — от последнего к нулевому. Текст программы приведён далее.
#include <iostream>
using namespace std;
int main ( int arg c, char **argv )
{
int **a, n=6,k=0, i, j;
a=new int * [ n ]; //Выделяем память для хранения матрицы
for ( i =0; i<n; i++)
a [ i ]=new int [ n ];
for ( i =0; i<n; i++) //Перебираем все строки матрицы.
if ( i%2==0) //Строки с чётными номерами заполняем возрастающей последовательностью
for ( j =0; j<n; j++) //чисел слева направо
a [ i ] [ j ]=++k;
else //Строки с нечётными номерами заполняем возрастающей последовательностью чисел
for ( j=n-1; j >=0; j --) //справа налево
a [ i ] [ j ]=++k;
cout<<"Вывод матрицы A "<<endl;
for ( i =0; i<n; cout<<end l, i++)
for ( j =0; j<n; j++)
cout<<a [ i ] [ j ]<<" \t ";
return 0;
}
В этом параграфе рассмотрим использование матриц при решении таких задач линейной алгебры, как сложение, вычитание и умножение матриц, решение систем линейных алгебраических уравнений, вычисление определителя и обратной матрицы.
Задача 6.9.Заданы четыре матрицы вещественных чисел$$ A(N,M), B(N,M), C(M,N), D(M,N)$$. Вычислить матрицу $$C=((A+B)(C-D))^2$$.
Суммой (разностью) матриц одинаковой размерности $$A$$ и $$B$$ называется матрица $$C$$, элементы которой получаются сложением $$C_{i,j}=A_{i,j}+B_{i,j}$$ (вычитанием $$C_{i,j}=A_{i,j}-B_{i,j}$$) соответствующих элементов исходных матриц.
Напомним алгоритм умножения матриц на примере
$$\small $\left(\begin{matrix}a_{0,0}a_{0,1}a_{0,2}\\a_{1,0}a_{1,1}a_{1,2}\\a_{2,0}a_{2,1}a_{2,2}\end{matrix}\right)\cdot \left(\begin{matrix}b_{0,0}b_{0,1}\\b_{1,0}b_{1,1}\\b_{2,0}b_{2,1}\end{matrix}\right)$$Воспользовавшись правилом "строка на столбец", получим матрицу:
$$\left(\begin{matrix}c_{0,0}c_{0,1}\\c_{1,0}c_{1,1}\\c_{2,0}c_{2,1}\end{matrix}\right)= \left(\begin{matrix}a_{0,0}\cdot b_{0,0}+a_{0,1}\cdot b_{1,0}+a_{0,2}\cdot b_{2,0}a_{0,0}\cdot b_{0,1}+a_{0,1}\cdot b_{1,1}+a_{0,2}\cdot b_{2,1}\\a_{1,0}\cdot b_{0,0}+a_{1,1}\cdot b_{1,2}+a_{1,2}\cdot b_{2,1}a_{1,0}\cdot b_{0,1}+a_{1,1}\cdot b_{1,1}+a_{1,2}\cdot b_{2,1}\\a_{2,0}\cdot b_{0,0}+a_{2,1}\cdot b_{1,0}+a_{2,2}\cdot b_{2,0}a_{2,0}\cdot b_{0,1}+a_{2,1}\cdot b_{1,1}+a_{2,2}\cdot b_{2,1}\end{matrix}\right)$$Произведением матриц $$A(N,M)$$ и $$B(M,L)$$ является матрица$$ C(N,L)$$, каждый элемент которой $$C_{i,j}$$ вычисляется по формуле: $$C_{i,j}=\sum\limits_{k=0}^{M-1}A_{i,k}B_{k,j}$$ где $$i = 0,N - 1$$ и $$j = 0,L - 1$$.
Операция умножения имеет смысл только в том случае, если количество строк левой матрицы совпадает с количеством столбцов правой. Кроме того, $$A\cdot B\neq B\cdot A$$.
При решении задачи будем использовать динамические матрицы и двойные указатели. Напишем следующие функции.
float **sum_m(float **A, float **B, int N, int M) — функция формирует матрицу, которая является суммой двух матриц. Здесь A, B — указатели на исходные матрицы, N, M — количество строк и столбцов матриц, функция возвращает указатель на сформированную матрицу, которая является суммой двух матриц $$A$$ и $$B$$.float **minus_m(float **A, float **B, int N, int M) — функция формирует матрицу, которая является разностью двух матриц. Здесь A, B — указатели на исходные матрицы, N, M — количество строк и столбцов матриц, функция возвращает указатель на сформированную матрицу, которая является разностью двух матриц $$A$$ и $$B$$.float **product_m(float **A, float **B, int N, int M, int L) — функция формирует матрицу, которая является произведением двух матриц. Здесь A, B — указатели на исходные матрицы. Матрица $$A$$ имеет $$N$$ строк и $$M$$ столбцов, матрица $$B$$ имеет $$M$$ строка и $$L$$ столбцов, функция возвращает указатель на сформированную матрицу, которая является произведением двух матриц $$A$$ и $$B$$.float **create_m(int N, int M) — функция создаёт матрицу, в которой будет $$N$$ строк и $$M$$ столбцов, осуществляет ввод элементов матрицы, функция возвращает указатель на сформированную матрицу.void output_m(float **A, int N, int M) — функция построчного вывода на экран матрицы $$A$$, которая имеет $$N$$ строк и $$M$$ столбцов.Далее приведён текст программы с комментариями.
#include <iostream>
using namespace std;
//функция вычисления суммы двух матриц.
float **sum_m( float **A, float **B, int N, int M)
{
int i, j;
float **temp; //указатель для хранения результирующей матрицы
temp=new float * [N ]; //выделение памяти для хранения результирующей матрицы
for ( i =0; i<N; i++)
temp [ i ]=new float [M];
for ( i =0; i<N; i++) //Вычисляем сумму двух матриц
for ( j =0; j<M; j++)
temp [ i ] [ j ]=A [ i ] [ j ]+B [ i ] [ j ];
return temp; //Возвращаем матрицу как двойной указатель
}
//функция вычисления разности двух матриц.
float **minus_m ( float **A, float **B, int N, int M)
{ int i, j;
float **temp; //указатель для хранения результирующей матрицы
temp=new float * [N ]; //выделение памяти для хранения результирующей матрицы
for ( i =0; i<N; i++)
temp [ i ]=new float [M];
for ( i =0; i<N; i++) //Вычисляем разность двух матриц
for ( j =0; j<M; j++)
temp [ i ] [ j ]=A [ i ] [ j ]-B [ i ] [ j ];
return temp; //Возвращаем матрицу как двойной указатель
}
//функция вычисления произведения двух матриц.
float **product_m ( float **A, float **B, int N, int M, int L)
{
int i, j, k;
float **temp; //указатель для хранения результирующей матрицы
temp=new float * [N ]; //выделение памяти для хранения результирующей матрицы
for ( i =0; i<N; i++)
temp [ i ]=new float [ L ];
//Вычисляем произведение двух матриц, последовательно формируя все элементы матрицы
for ( i =0; i<N; i++)
for ( j =0; j<L; j++)
//Элемент с индексами i, j — скалярное произведение i-й строки матрицы A
for ( temp [ i ] [ j ]=k=0;k<M; k++) //и j-го столбца матрицы B
temp [ i ] [ j ]+=A [ i ] [ k ] *B [ k ] [ j ];
return temp; //Возвращаем матрицу как двойной указатель
}
//функция создаёт динамическую матрицу вещественных чисел размерности N на M,
//в этой же функции осуществляется и ввод элементов матрицы
float ** create_m ( int N, int M)
{
int i, j;
float **temp;
temp=new float * [N ];
for ( i =0; i<N; i++)
temp [ i ]=new float [M];
cout<<"Ввод матрицы\n ";
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
cin>>temp [ i ] [ j ];
return temp;
}
//функция осуществляет построчный вывод матрицы A(N,M)
void output_m ( float **A, int N, int M)
{
int i, j;
//Цикл по строкам. По окончанию вывода всех элементов строки — переход на новую строку.
for ( i =0; i<N; cout<<endl, i++)
for ( j =0; j<M; j++) //Цикл по переменной j, в котором перебираем строки матрицы
cout<<A [ i ] [ j ]<<" \t "; //Вывод очередного элемента матрицы и символа табуляции.
}
int main ( int arg c, char ** argv )
{
float **A, **B, **C, **D, ** result; //указатели для хранения исходных и
результирующей матриц
int N,M;
cout<<" N = "; cin>>N; //Ввод размерностей матрицы
cout<<" M = "; cin>>M;
//Выделение памяти и ввод матриц A, B, C, D, обращением к функции create_m.
A=create_m (N,M);
B=create_m (N,M);
C=create_m (M,N);
D=create_m (M,N);
//Вычисление результирующей матрицы.
result=product_m ( product_m (sum_m(A, B,N,M),minus_m (C,D,M,N),N,M,N), product_m
(sum_m(A, B,N,M),minus_m (C,D,M,N),N,M,N),N,N,N);
output_m ( result,N,N); //Вывод результирующей матрицы.
return 0;
}
Далее без комментариев приведена программа решения задачи 6.9 с помощью динамических матриц и обычных
#include <iostream>
using namespace std;
float *sum_m( float *A, float *B, int N, int M)
{
int i, j;
float *temp;
temp=new float [N*M];
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
temp [ i *M+j ]=A [ i *M+j ]+B [ i *M+j ];
return temp;
}
float *minus_m ( float *A, float *B, int N, int M)
{ int i, j;
float *temp;
temp=new float [N*M];
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
temp [ i *M+j ]=A [ i *M+j ]-B [ i *M+j ];
return temp;
}
float *product_m ( float *A, float *B, int N, int M, int L)
{
int i, j, k;
float *temp;
temp=new float [N*L ];
for ( i =0; i<N; i++)
for ( j =0; j<L; j++)
for ( temp [ i *L+j ]=k=0;k<M; k++)
temp [ i *L+j ]+=A [ i *M+k ] *B [ k*L+j ];
return temp;
}
float *create_m ( int N, int M)
{
int i, j;
float *temp;
temp=new float [N*M];
cout<<"Ввод матрицы\n ";
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
cin>>temp [ i *M+j ];
return temp;
}
void output_m ( float *A, int N, int M)
{
int i, j;
for ( i =0; i<N; cout<<endl, i++)
for ( j =0; j<M; j++)
cout<<A [ i *M+j ]<<" \t ";
}
int main ( int arg c, char ** argv )
{
float *A, *B, *C, *D, * result;
int N,M;
cout<<" N = "; cin>>N;
cout<<" M = "; cin>>M;
A=create_m (N,M);
B=create_m (N,M);
C=create_m (M,N);
D=create_m (M,N);
result=product_m ( product_m (sum_m(A, B,N,M), minus_m (C,D,M,N),N,M,N),
product_m (sum_m(A, B,N,M), minus_m (C,D,M,N),N,M,N),N,N,N);
output_m ( result,N,N);
return 0;
}
Задача 6.10. Решить систему линейных алгебраических уравнений.
При решении этой задачи напишем универсальную функцию решения системы линейных алгебраических уравнений методом Гаусса, а в функции main() просто вызовем эту функцию. Вспомним метод Гаусса.
Пусть дана система линейных алгебраических уравнений (СЛАУ) с $$n$$ неизвестными
$$\left\{\begin{array}{ll} a_{00}x_0+a_{01}x_1+...+a_{0n-1}x_{n-1}=b_0,\\ a_{10}x_0+a_{11}x_1+...+a_{1n-1}x_{n-1}=b_1,\\ \hdotsfor{2}\\ a_{n-10}x_0+a_{n-11}x_1+...+a_{n-1n-1}x_{n-1}=b_{n-1} \end{array}\right.$$Обозначим через $$A=\left(\begin{matrix} a_{00}a_{01}...a_{0n-1}\\ a_{10}a_{11}...a_{1n-1}\\ ............\\ a_{n-10}a_{n-11}...a_{n-1n-1} \end{matrix}\right)$$ матрицу коэффициентов системы (6.1), через $$b=\left(\begin{matrix}b_0\\b_1\\...\\b_{n-1}\end{matrix}\right)$$- столбец её свободных членов, и через $$x=\left(\begin{matrix}x_0\\x_1\\...\\x_{n-1}\end{matrix}\right)$$- столбец из неизвестных (искомый вектор). Тогда система (6.1) может быть записана в виде матричного уравнения $$Ax = b$$.
Наиболее распространённым приёмом решения систем линейных уравнений является алгоритм последовательного исключения неизвестных — метод Гаусса.
При решении систем линейных алгебраических уравнений этим методом всевозможные преобразования производят не над уравнениями системы (6.1), а над так называемой расширенной матрицей системы, которая получается путём добавления к основной матрице $$A$$ столбца свободных членов $$b$$.
Первый этап решения системы уравнений, называемый прямым ходом метода Гаусса, заключается в приведении расширенной матрицы (6.2) к треугольному виду. Это означает, что все элементы матрицы (6.2) ниже главной диагонали должны быть равны нулю.
$$A'=\left(\begin{matrix} a_{00}a_{01}...a_{0n-1}b_0\\ a_{10}a_{11}...a_{1n-1}b_1\\ ...............\\ a_{n-10}a_{n-11}...a_{n-1n-1}b_{n-1} \end{matrix}\right)$$На первом этапе необходимо обнулить элементы 0-го столбца расширенной матрицы
$$A^{'}=\left(\begin{matrix} a_{00}a_{01}a_{02}...a_{0n-1}b_0\\ 0a_{11}a_{12}...a_{1n-1}b_1\\ 00a_{22}...a_{2n-1}b_2\\ 000...a_{3n-1}b_3\\ ..................\\ 000...a_{n-1n-1}b_{n-1} \end{matrix}\right)$$Для этого необходимо из каждой строки (начиная с первой) вычесть нулевую, умноженную на некоторое число $$M$$. В общем виде этот процесс можно записать так:
1-я строка = 1-я строка – $$M\times 0$$-я строка
2-я строка = 2-я строка – $$M\times 0$$-я строка
...
$$i$$-я строка =$$ i$$-я строка – $$M\times 0$$-я строка
...
$$n - 1$$-я строка = $$n - 1$$-я строка – M\times 0-я строка
Понятно, что преобразование элементов первой строки будет происходить по формулам:
$$a_{10}=a_{10}-Ma_{00}\\ a_{11}=a_{11}-Ma_{01}\\ … \\ a_{1i}=a_{1i}-Ma_{0i}\\ … \\ a_{1n-1}=a_{1n-1}-Ma_{0n-1}\\ b_1=b_1-Mb_0$$Так как целью данных преобразований является обнуление первого элемента строки, то $$M$$ выбираем из условия: $$a_{10}=a_{10}-Ma_{00}=0$$. Следовательно, $$M=\frac{a_{10}}{a_{00}}$$.
Элементы второй строки и коэффициент $$M$$ можно рассчитать аналогично:
$$a_{20}=a_{20}-Ma_{00}\\ a_{21}=a_{21}-Ma_{01}\\ …\\ a_{2i}=a_{2i}-Ma_{0i}\\ … \\ a_{2n-1}=a_{2n-1}-Ma_{0n-1}\\ b_2=b_2-Mb_0$\\ a_{20}=a_{20}-Ma_{00}=0 \Rightarrow M=\frac{a_{20}}{a_{00}$.$$Таким образом, преобразование элементов $$i$$–й строки будет происходить следующим образом:
$$a_{i0}=a_{i0}-Ma_{00}\\ a_{i1}=a_{i1}-Ma_{01}\\ …\\ a_{ii}=a_{ii}-Ma_{0i}\\ …\\ a_{in-1}=a_{in-1}-Ma_{0n-1}\\ b_i=b_i-Mb_0.$$Коэффициент $$M$$ для $$i$$–й строки выбирается из условия $$a_{i0}=a_{i0}-Ma_{00}=0$$ и равен $$M=\frac{a_{i0}}{a_{00}$$.
После проведения подобных преобразований для всех строк матрица (6.2) примет вид
$$A'=\left(\begin{matrix}a_{00}a_{01}...a_{0n-1}b_0\\0a_{11}...a_{1n-1}b_1\\0a_{21}...a_{2n-1}b_2\\...............\\0a_{n-11}...a_{n-1n-1}b_{n-1}\end{matrix}\right)$$Блок-схема обнуления первого столбца матрицы приведена на рис. 6.10.
Очевидно, что если повторить описанный выше алгоритм для следующих столбцов матрицы (6.2), то в результате будет получена матрица (6.3). Алгоритм этого процесса изображён на рис. 6.11.
(рис 6.10) Блок-схема обнуления первого столбца матрицы
(рис 6.11) Блок-схема алгоритма преобразования расширенной матрицы к треугольному виду
Заметим, что если в матрице (6.2) на главной диагонали встретится элемент $$a_{k,k}$$, равный нулю, то расчёт коэффициента $$M=\frac{a_{ik}}{a_{kk}}$$ для k$$-$$й строки будет невозможен. Избежать деления на ноль можно, избавившись от нулевых элементов на главной диагонали. Для этого перед обнулением элементов в $$k$$–м столбце необходимо найти в нём максимальный по модулю элемент (среди расположенных ниже $$a_{k,k}$$), запомнить номер строки, в которой он находится, и поменять её местами с $$k$$-й. Алгоритм, отображающий эти преобразования, приведён на рис. 6.12.
В результате выполнения прямого хода метода Гаусса матрица (6.2) преобразуется в матрицу (6.3), а система уравнений (6.1) будет иметь следующий вид:
$$\left\{\begin{array}{rl} a_{00}x_0+a_{01}x_1+a_{20}x_2+...+a_{0n-1}x_{n-1}=b_0,\\ a_{11}x_1+a_{21}x_2+...+a_{1n-1}x_{n-1}=b_1,\\ a_{22}x_2+...+a_{2n-1}x_{n-1}=b_2,\\ ...\\ a_{n-1n-1}x_{n-1}=b_{n-1} \end{array}\right.$$Решение системы (6.4) называют обратным ходом метода Гаусса.
Последнее $$(n - 1)$$-е уравнение системы (6.4) имеет вид: $$a_{n-1n-1}x_{n-1}=b_{n-1}$$. Тогда, если $$a_{n-1n-1}\neq 0$$, то $$x_{n-1}=\frac{b_{n-1}}{a_{n-1n-1}}$$. В случае, если a$$a_{n-1n-1}=0,$$, и $$b_{n-1}=0$$, то система (6.4), а следовательно, и система (6.1) имеют бесконечное множество решений.
При $$a_{n-1n-1}=0$$ и $$b_{n-1}\neq 0$$ система (6.4), а значит и система (6.1), решения не имеет. Предпоследнее $$(n-2)$$-е уравнение системы (6.4) имеет вид $$a_{n-2n-2}x_{n-2}+a_{n-2n-1}x_{n-1}=b_{n-2}$$.
(рис 6.12) Блок-схема алгоритма перестановки строк расширенной матрицы
(рис 6.13) Блок-схема алгоритма обратного хода метода Гаусса
Значит, $$x_{n-2}=\frac{b_{n-2}-a_{n-2n-1}x_{n-1}}{a_{n-2n-2}}$$.
Следующее $$(n - 3)$$-е уравнение системы (6.4) будет выглядеть так:
$$a_{n-3n-3}x_{n-3}+a_{n-3n-2}x_{n-2}+a_{n-3n-1}x_{n-1}=b_{n-3}.$$Отсюда имеем
$$x_{n-3}=\frac{b_{n-3}-a_{n-3n-2}x_{n-2}-a_{n-3n-1}x_{n-1}}{a_{n-3n-3}},x_{n-3}= \frac{b_{n-3}-\sum\limits_{j=n-2}^{n-1}{a_{n-3j}x_j}}{a_{n-3n-3}}.$$Таким образом, формула для вычисления $$i$$-го значения $$x$$ будет иметь вид:$$x_i=\frac{b_i-\sum\limits_{j=i+1}^{n-1}{a_{ij}x_j}}{a_{ii}}$$.
Алгоритм, реализующий обратный ход метода Гаусса, представлен в виде блок-схемы на рис. 6.13.
Объединив блок-схемы, изображённые на рис. 6.11,рис. 6.12 и рис. 6.13, получим общую блок-схему метода Гаусса (рис. 6.14). Блоки 2-6 содержат последовательный ввод данных, где $$n$$ — это размерность системы линейных алгебраических уравнений, а сама система задаётся в виде матрицы коэффициентов при неизвестных $$A$$ и вектора свободных коэффициентов $$b$$. Блоки 7-18 предусматривают прямой ход метода Гаусса, а блоки 23-27 — обратный. Для вывода результатов предусмотрено несколько блоков вывода. Если результат проверки условий 19 и 20 положительный, то выдаётся сообщение о том, что система имеет бесконечное множество решений (блок 21). Если условие 19 выполняется, а 20 — нет, то появляется сообщение о том, что система не имеет решений (блок 22). Сами же решения системы уравнений, представленные вектором $$x$$, вычисляются (блоки 23–26) и выводятся экран/печать (блок 27) только в случае невыполнения условия.
Теперь алгоритм решения СЛАУ, представленный на рис. 6.14, разобьём на
главную функцию main() и функцию решения СЛАУ методом Гаусса. В функции main() будет находиться ввод исходных данных, обращение к функции SLAU
и вывод вектора решения. Функция SLAU предназначена для решения системы
линейных алгебраических уравнений методом Гаусса.
При написании функции следует учитывать следующее: в методе Гаусса изменяются матрица коэффициентов и вектор правых частей. Поэтому, для того чтобы их не испортить, в функции SLAU матрицу коэффициентов и вектор правых частей необходимо скопировать во внутренние переменные, и в функции обрабатывать внутренние переменные-копии.
Функция SLAU возвращает значение 0, если решение найдено, -1 — если система имеет бесконечное множество решений, -2 — если система не имеет решений.
Ниже приведено решение задачи 6.10 с подробными комментариями.
#include <iostream>
#include <math.h>
using namespace std;
int SLAU( double ** matrica_a, int n, double *massiv_b, double *x )
//Функция SLAU возвращает значение типа int: 0, если решение найдено, _1 — если система имеет
//бесконечное множество решений, _2 — если система не имеет решений.
//Формальные параметры функции: n — размерность системы,
//matrica_a — матрица коэффициентов СЛАУ,
//massiv_b — вектор правых частей, x — решение СЛАУ, передаются как указатели.
{
int i, j, k, r;
double c,M, max, s;
//Матрица a — копия матрицы коэффициентов, массив b — копия вектора правых частей.
double **a, *b;
a=new double * [ n ]; //Выделение памяти для a и b.
for ( i =0; i<n; i++)
a [ i ]=new double [ n ];
b=new double [ n ];
//В a записываем копию матрицы коэффициентов, в b копию вектора правых частей.
for ( i =0; i<n; i++)
for ( j =0; j<n; j++)
a [ i ] [ j ]=matrica_a [ i ] [ j ];
for ( i =0; i<n; i++)
b [ i ]=massiv_b [ i ];
//Прямой ход метода Гаусса: приводим матрицу a (копию матрицы коэффициентов СЛАУ)
//к диагональному виду.
for ( k=0;k<n; k++)
{ //Поиск максимального по модулю элемента в k-м столбце.
max=fabs ( a [ k ] [ k ] );
r=k;
for ( i=k+1; i<n; i++)
if ( fabs ( a [ i ] [ k ] )>max)
{
max=fabs ( a [ i ] [ k ] );
r= i;
}
for ( j =0; j<n; j++) //Меняем местами k-ю и r-ю (строку, где находится
{ //максимальный по модулю элемент) строки.
c=a [ k ] [ j ];
a [ k ] [ j ]=a [ r ] [ j ];
a [ r ] [ j ]= c;
}
c=b [ k ];
b [ k ]=b [ r ];
b [ r ]= c;
for ( i=k+1; i<n; i++) //Приведение матрицы к диагональному виду.
{
for (M=a [ i ] [ k ] / a [ k ] [ k ], j=k; j<n; j++)
a [ i ] [ j ]-=M*a [ k ] [ j ];
b [ i ]-=M*b [ k ];
}
}
//Обратный ход метода Гаусса.
if ( a [ n-1 ] [ n-1]==0) //Если последний диагональный элемент равен 0 и
if ( b [ n-1]==0) //последний коэффициент вектора свободных членов равен 0,
return -1; //то система имеет бесконечное множество решений
else return -2; //последний коэффициент вектора свободных членов не равен 0,
//система решений не имеет.
else //Последний диагональный элемент не равен 0, начинается обратный ход метода Гаусса.
{
for ( i=n-1; i >=0; i --)
{
for ( s =0, j= i +1; j<n; j++)
s+=a [ i ] [ j ] * x [ j ];
x [ i ]=( b [ i ]- s ) / a [ i ] [ i ];
}
return 0;
}
}
int main ( )
{
int result, i, j,N;
double **a, *b, *x;
cout<<" N = "; //Ввод размерности системы.
cin>>N;
a=new double * [N ]; //Выделение памяти для матрицы правых частей и вектора свободных
членов.
for ( i =0; i<N; i++)
a [ i ]=new double [N ];
b=new double [N ];
x=new double [N ];
cout<<"Ввод матрицы A "<<endl; //Ввод матрицы правых частей
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
cout<<"Ввод вектора B "<<endl; //и вектора свободных членов.
for ( i =0; i<N; i++)
cin>>b [ i ];
//Вызов функции решения СЛАУ методом Гаусса. По значению result можно судить, сколько
//корней имеет система. Если result=0, то система имеет единственное решение, result= -1 -
//система имеет бесконечное множество решений, result=-2 — система не имеет решений.
result=SLAU( a,N, b, x );
if ( result ==0)
{ //Вывод массива решения.
cout<<" MassivX "<<endl;
for ( i =0; i<N; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
}
else if ( result ==-1)
cout<<"Бесконечное множество решений\n ";
else if ( result ==-2)
cout<<"Нет решений\n ";
}
(рис 6.14) Блок-схема алгоритма решения СЛАУ методом Гаусса
Задача 6.11. Найти обратную матрицу к квадратной матрице $$A(N,N)$$.
Один из методов вычисления обратной матрицы основан на решении систем линейных алгебраических уравнений. Пусть задана некоторая матрица $$A$$:
$$A=\left(\begin{matrix}a_{00}a_{01}a_{02}...a_{0n-1}\\a_{10}a_{11}a_{12}...a_{1n-1}\\...............\\a_{n-10}a_{n-11}a_{n-12}...a_{n-1n-1}\end{matrix}\right)$$Необходимо найти матрицу $$A^{-1}$$, которая является обратной к матрице $$A$$:
$$Y=A^{-1}=\left(\begin{matrix}y_{00}y_{01}y_{02}...y_{0n-1}\\y_{10}y_{11}y_{12}...y_{1n-1}\\...............\\y_{n-10}y_{n-11}y_{n-12}...y_{n-1n-1}\end{matrix}\right)$$Матрица (6.6) будет обратной к матрице (6.5), если выполняется соотношение $$A\cdot A^{-1}=E$$, где $$E$$ — это единичная матрица, или более подробно:
$$\left(\begin{matrix}a_{00}a_{01}...a_{0n-1}\\a_{10}a_{11}...a_{1n-1}\\............\\a_{n-10}a_{n-11}...a_{n-1n-1}\end{matrix}\right)\left(\begin{matrix}y_{00}y_{01}...y_{0n-1}\\y_{10}y_{11}...y_{1n-1}\\............\\y_{n-10}y_{n-11}...y_{n-1n-1}\end{matrix}\right)=E$$Результат перемножения матриц из соотношения (6.7) можно представить поэлементно в виде $$n$$ систем линейных уравнений. Умножение матрицы (6.5) на нулевой столбец матрицы (6.6) даст нулевой столбец единичной матрицы:
$$\left\{\begin{matrix} a_{00}y_{00}+a_{01}y_{10}+...+a_{0n-1}y_{n-10}=1,\\ a_{10}y_{00}+a_{11}y_{10}+...+a_{1n-1}y_{n-10}=0,\\ \hdotsfor{2}\\ a_{i0}y_{00}+a_{i1}y_{10}+...+a_{in-1}y_{n-10}=0,\\ \hdotsfor{2}\\ a_{n-10}y_{00}+a_{n-11}y_{10}+...+a_{n-1n-1}y_{n-10}=0 \end{matrix}\right.$$При умножении матрицы $$A$$ на первый столбец обратной матрицы получается следующая система линейных алгебраических уравнений.
$$\left\{\begin{matrix} a_{00}y_{01}+a_{01}y_{11}+...+a_{0n-1}y_{n-11}=0,\\ a_{10}y_{01}+a_{11}y_{11}+...+a_{1n-1}y_{n-11}=1,\\ \hdotsfor{2}\\ a_{i0}y_{01}+a_{i1}y_{11}+...+a_{in-1}y_{n-11}=0,\\ \hdotsfor{2}\\ a_{n-10}y_{01}+a_{n-11}y_{11}+...+a_{n-1n-1}y_{n-11}=0 \end{matrix}\right.$$Система, полученная в результате умножения матрицы (6.5) на $$i$$-й столбец матрицы (6.6), будет выглядеть следующим образом:
$$\left\{\begin{matrix} a_{00}y_{0i}+a_{01}y_{1i}+...+a_{0n-1}y_{n-1i}=0,\\ a_{10}y_{0i}+a_{11}y_{1i}+...+a_{1n-1}y_{n-1i}=0,\\ \hdotsfor{2}\\ a_{i0}y_{0i}+a_{i1}y_{1i}+...+a_{in-1}y_{n-1i}=1,\\ \hdotsfor{2}\\ a_{n-10}y_{0i}+a_{n-11}y_{1i}+...+a_{n-1n-1}y_{n-1i}=0 \end{matrix}\right.$$Понятно, что $$n$$-я система будет иметь вид:
$$\left\{\begin{matrix} a_{00}y_{0n-1}+a_{01}y_{1n-1}+...+a_{0n-1}y_{n-1n-1}=0,\\ a_{10}y_{0n-1}+a_{11}y_{1n-1}+...+a_{1n-1}y_{n-1n-1}=0,\\ \hdotsfor{2}\\ a_{i0}y_{0n-1}+a_{i1}y_{1n-1}+...+a_{in-1}y_{n-1n-1}=0,\\ \hdotsfor{2}\\ a_{n-10}y_{0n-1}+a_{n-11}y_{1n-1}+...+a_{n-1n-1}y_{n-1n-1}=1 \end{matrix}\right.$$Решением каждой из приведённых выше систем будет $$i$$-й столбец обратной матрицы. Количество систем равно размерности обратной матрицы. Для отыскания решений систем линейных алгебраических уравнений можно воспользоваться методом Гаусса.
Описанный алгоритм представлен в виде блок-схемы на рис. 6.15. Блоки 2–5 отражают формирование вектора правых частей системы линейных алгебраических уравнений. Если условие в блоке 3 выполняется и элемент находится на главной диагонали, то он равен единице, все остальные элементы нулевые. В блоке 6 происходит вызов подпрограммы для решения системы уравнений методом Гаусса. В качестве параметров в эту подпрограмму передаётся исходная матрица $$A$$, сформированный в блоках 2–5 вектор свободных коэффициентов $$B$$, размерность системы $$n$$. Вектор $$X$$ будет решением $$i$$-й системы уравнений и, следовательно, $$i$$-м столбцом искомой матрицы $$Y$$.
Как видно из блок-схемы, приведённой на рис. 6.15, при нахождении обратной матрицы понадобится функция SLAU, рассмотренная при решении задачи 6.10. Ниже приведён текст программы с подробными комментариями решения задачи 6.11. В функции main() будет находиться ввод исходной матрицы, обращение к функции INVERSE для вычисления обратной матрицы. Из функции INVERSE будет осуществляться вызов функции SLAU для решения системы линейных алгебраических уравнений.
#include <iostream>
#include <math.h>
using namespace std;
//Функция решения системы линейных алгебраических уравнений методом Гаусса.
int SLAU( double ** matrica_a, int n, double *massiv_b, double *x )
{
int i, j, k, r;
double c,M, max, s;
double **a, *b;
a=new double * [ n ];
for ( i =0; i<n; i++)
a [ i ]=new double [ n ];
b=new double [ n ];
for ( i =0; i<n; i++)
for ( j =0; j<n; j++)
a [ i ] [ j ]=matrica_a [ i ] [ j ];
for ( i =0; i<n; i++)
b [ i ]=massiv_b [ i ];
for ( k=0;k<n; k++)
{
max=fabs ( a [ k ] [ k ] );
r=k;
for ( i=k+1; i<n; i++)
if ( fabs ( a [ i ] [ k ] )>max)
{
max=fabs ( a [ i ] [ k ] );
r= i;
}
for ( j =0; j<n; j++)
{
c=a [ k ] [ j ];
a [ k ] [ j ]=a [ r ] [ j ];
a [ r ] [ j ]= c;
}
c=b [ k ];
b [ k ]=b [ r ];
b [ r ]= c;
for ( i=k+1; i<n; i++)
{
for (M=a [ i ] [ k ] / a [ k ] [ k ], j=k; j<n; j++)
a [ i ] [ j ]*=M_a [ k ] [ j ];
b [ i ]_=M_b [ k ];
}
}
if ( a [ n -1 ] [ n-1]==0)
if ( b [ n-1]==0)
return -1;
else return -2;
else
{
for ( i=n-1; i >=0; i --)
{
for ( s =0, j= i +1; j<n; j++)
s+=a [ i ] [ j ] * x [ j ];
x [ i ]=( b [ i ]- s ) / a [ i ] [ i ];
}
return 0;
}
for ( i =0; i<n; i++)
delete [ ] a [ i ];
delete [ ] a;
delete [ ] b;
}
//Функция вычисления обратной матрицы
int INVERSE( double **a, int n, double **y )
//Формальные параметры: a — исходная матрица, n — размерность матрицы, y — обратная
матрица.
//Функция будет возвращать 0, если обратная матрица существует, -1 — в противном случае.
{
int i, j, res;
double *b, *x;
//Выделение памяти для промежуточных массивов b и x.
b=new double [ n ];
x=new double [ n ];
for ( i =0; i<n; i++)
{
//Формирование вектора правых частей для нахождения i-го столбца матрицы.
for ( j =0; j<n; j++)
if ( j==i )
b [ j ]= 1;
else b [ j ]= 0;
//Нахождение i-го столбца матрицы путём решения СЛАУ Ax = b методом Гаусса.
res=SLAU( a, n, b, x );
//Если решение СЛАУ не найдено, то невозможно вычислить обратную матрицу.
if ( res !=0)
break;
else
//Формирование i-го столбца обратной матрицы.
for ( j =0; j<n; j++)
y [ j ] [ i ]=x [ j ];
}
//Проверка существования обратной матрицы, если решение одного из уравнений Ax=b не
//существует, то невозможно найти обратную матрицу, и функция INVERSE вернёт значение -1.
if ( res !=0)
return -1;
//Если обратная матрица найдена, то функция INVERSE вернёт значение 0,
//а обратная матрица будет возвращаться через указатель double **y.
else
return 0;
}
int main ( )
{
int result, i, j,N;
double **a, **b; //Двойные указатели для хранения исходной a и обратной b матрицы.
cout<<" N = "; //Ввод размера матрицы.
cin>>N;
a=new double * [N ]; //Выделение памяти для матриц a и b.
for ( i =0; i<N; i++)
a [ i ]=new double [N ];
b=new double * [N ];
for ( i =0; i<N; i++)
b [ i ]=new double [N ];
cout<<"Ввод матрицы A "<<endl; //Ввод исходной матрицы.
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
result=INVERSE( a,N, b ); //Вычисление обратной матрицы.
if ( result ==0) //Если обратная матрица существует, то вывести её на экран.
{
cout<<"Обратная матрица"<<endl;
for ( i =0; i<N; cout<<endl, i++)
for ( j =0; j<N; j++)
cout<<b [ i ] [ j ]<<" \t ";
}
else
//Если обратная матрица не существует, то вывести соответствующее сообщение.
cout<<"Нет обратной матрицы"<<endl;
}
Задача 6.12. Найти определитель квадратной матрицы $$A(N,N)$$.
(рис 6.15) Блок-схема алгоритма вычисления обратной матрицы
Пусть задана матрица (6.2), необходимо вычислить её определитель. Для этого матрицу необходимо преобразовать к треугольному виду (6.3), а затем воспользоваться свойством, известным из курса линейной алгебры, которое гласит, что определитель треугольной матрицы равен произведению её диагональных элементов: $$\det A=\prod\limits_{i=0}^{n-1}a_{ii}$$.
Преобразование матрицы (6.2) к виду (6.3) можно осуществить с помощью прямого хода метода Гаусса. Алгоритм вычисления определителя матрицы, изображённый в виде блок-схемы на рис. 6.16, представляет собой алгоритм прямого хода метода Гаусса, в процессе выполнения которого проводится перестановка строк матрицы. Эта операция приводит к смене знака определителя. В блок- схеме момент смены знака отражён в блоках 8–9. В блоке 8 определяется, будут ли строки меняться местами, и если ответ утвердительный, то в блоке 9 происходит смена знака определителя. В блоках 15–16 выполняется непосредственное вычисление определителя путём перемножения диагональных элементов преобразованной матрицы.
На листинге приведён текст программы решения задачи 6.12 с комментариями.
#include <iostream>
#include <math.h>
using namespace std;
//Функция вычисления определителя.
double determinant ( double ** matrica_a, int n )
//Формальные параметры: matrica_a — исходная матрица, n — размер матрицы,
//функция возвращает значение определителя (тип double.)
{
int i, j, k, r;
double c,M, max, s, det =1;
//a — копия исходной матрицы.
double **a;
//Выделение памяти для матрицы a .
a=new double * [ n ];
for ( i =0; i<n; i++)
a [ i ]=new double [ n ];
//В a записываем копию исходной матрицы.
for ( i =0; i<n; i++)
for ( j =0; j<n; j++)
a [ i ] [ j ]=matrica_a [ i ] [ j ];
//Прямой ход метода Гаусса.
for ( k=0;k<n; k++)
{
max=fabs ( a [ k ] [ k ] );
r=k;
for ( i=k+1; i<n; i++)
if ( fabs ( a [ i ] [ k ] )>max)
{
max=fabs ( a [ i ] [ k ] );
r= i;
}
//Если строки менялись местами, то смена знака определителя.
if ( r !=k ) det=-det;
for ( j =0; j<n; j++)
{
c=a [ k ] [ j ];
a [ k ] [ j ]=a [ r ] [ j ];
a [ r ] [ j ]= c;
}
for ( i=k+1; i<n; i++)
for (M=a [ i ] [ k ] / a [ k ] [ k ], j=k; j<n; j++)
a [ i ] [ j ]-=M*a [ k ] [ j ];
}
//Вычисление определителя.
for ( i =0; i<n; i++)
det*=a [ i ] [ i ];
//Возврат определителя в качестве результата функции
for ( i =0; i<n; i++)
delete [ ] a [ i ];
delete [ ] a;
return det;
}
int main ( )
{
int result, i, j,N;
double **a, b;
cout<<" N = ";
cin>>N;
a=new double _ [N ];
for ( i =0; i<N; i++)
a [ i ]=new double [N ];
//Ввод значений исходной матрицы.
cout<<"Ввод матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
//Обращение к функции вычисления определителя.
cout<<"определитель= "<<determinant ( a,N)<<endl;
}
(рис 6.16) Блок-схема алгоритма вычисления определителя
В этой главе читатель познакомился с обработкой статических и динамических матриц в C++, а также с использованием функций для решения задач обработки динамических матриц.
Разработать программу на языке С++ для решения следующей задачи.
(рис 6.17)
(рис 6.18)
(рис 6.19)
(рис 6.20)
(рис 6.21)
(рис 6.22)
(рис 6.23)
(рис 6.24)
(рис 6.25)
(рис 6.26)
Разработать программу на языке С++ для решения следующей задачи.
Разработать программу на языке С++ для решения следующей задачи.
Решить матричное уравнение $$X(A + E) = 3B - E$$, где $$E$$ — единичная матрица.
Решить матричное уравнение $$(2A - E)X = B + E$$, где $$E$$ — единичная матрица.
Если образуют, то найти координаты вектора $$x=[1\ -1\ 3\ -1]^T$$ в этом базисе. Для решения задачи необходимо показать, что определитель матрицы $$F$$ со столбцами $$f_1, f_2, f_3, f_4$$ отличен от нуля, а затем вычислить координаты вектора $$x$$ в новом базисе по формуле $$y=F^{-1}\cdot x$$
.Если образуют, то найти координаты вектора $$x = [1\ 1\ 1\ 1]^T$$ в этом базисе. Для решения задачи необходимо показать, что определитель матрицы F со столбцами $$f_1, f_2, f_3, f_4$$ отличен от нуля, а затем вычислить координаты вектора $$x$$ в новом базисе, решив СЛАУ $$F\cdot y=x$$.
Решить матричное уравнение $$G+E)\cdot X=5B^T-E$$, где $$E$$ — единичная матрица.
Матрица — это двумерный массив, каждый элемент которого имеет два индекса: номер строки — $$i$$, номер столбца —$$j$$.
Статический двумерный массив (матрицу) можно объявить так:
тип имя_переменной [n][m];
где тип определяет тип элементов массива, имя_переменной — имя матрицы, $$n$$ — количество строк, $$m$$ — количество столбцов в матрице. Строки нумеруются от 0 до $$n-1$$, столбцы — от 0 до $$m-1$$.
Например,
double x[20][35];
Описана матрица вещественных чисел $$x$$, состоящая из 20 строк и 35 столбцов (строки нумеруются от 0 до 19, столбцы от 0 до 34).
Как и любой другой переменной, матрице можно присвоить начальное значение, например int A[2][3]={{1,2,3}, {4,5,6}};
Для обращения к элементу матрицы необходимо указать её имя, и в квадратных скобках номер строки, а затем в квадратных скобках — номер столбца. Например, x[2][4] — элемент матрицы x, находящийся в третьей строке и пятом
Для работы с элементами матрицы необходимо использовать два цикла. Для построчной обработки матрицы значениями параметра первого (внешнего) цикла будут номера строк матрицы, значениями параметра второго (внутреннего) цикла — номера столбцов (см.рис. 6.1). При построчной обработке матрицы вначале поочерёдно рассматриваются элементы первой строки (столбца), затем второй и т.д. до последней. Если необходимо обрабатывать матрицу по столбцам, то необходимо организовать внешний цикл по столбцам, а внутренний по строкам (см. рис. 6.2).
В предыдущем параграфе мы рассмотрели описание статических матриц, в этом случае память для хранения матрицы выделяется в момент описания. Однако в С++ существует возможность создавать динамические матрицы. Динамические матрицы можно создавать с использованием обычных указателей и с помощью двойных указателей. Рассмотрим оба способа работы с динамическими матрицами последовательно.
(рис 6.1) Блок-схема построчной обработки матрицы
(рис 6.2) Блок-схема обработки матрицы по столбцам
При работе с динамическими матрицами можно использовать обычные указатели. После описания указателя необходимо будет выделить память для хранения $$N\times M$$ элементов ($$N$$ — число строк, $$M$$ — число столбцов). Рассмотрим в качестве примера выделение памяти для хранения целочисленной матрицы размером $$N\times M$$.
int *A, N, M; A=( int *) calloc (N*M, sizeof ( int ) );
Для выделения памяти можно использовать также и функцию malloc
A=( int *) malloc (N*M*sizeof ( int ) );
или операцию new
A=new int [N*M];
Память мы выделили, осталось найти способ обратиться к элементу матрицы. Все элементы матрицы хранятся в одномерном массиве размером $$N\times M$$ элементов. Сначала в этом массиве расположена 0-я строка матрицы, затем 1-я и т.д. Поэтому для обращения к элементу A[i][j] необходимо по номеру строки $$i$$ и номеру столбца $$j$$ вычислить номер этого элемента $$k$$ в динамическом массиве. Учитывая, что в массиве элементы нумеруются с нуля, $$k = iM + j$$. Обращение к элементу A[i][j] будет таким: *(A+i*M+j) или A[i*M+j].
Основной способ работы с динамическими матрицами базируется на использовании двойных указателей. Рассмотрим следующий фрагмент программы.
int main ( )
{
int N,M;
float **a;
a=new float *[N ];
}
С помощью оператора new создан массив из $$n$$ floata[i].
for ( i =0; i<N; i++) a [ i ]=new float [M];
После этого определён массив $$N$$ указателей, каждый из которых адресует массив из $$M$$ чисел (в данном случае вещественных типа float). Фактически создана динамическая матрица размера $$N\times M$$. Обращение к элементу динамической матрицы идёт так же, как и к элементу статической матрицы. Для того, чтобы обратиться к элементу $$a_{i,j}$$ в программе на C++, необходимо указать его имя, и в квадратных скобках номер строки и столбца (a[i][j]).
Рассмотрим основные операции, выполняемые над матрицами (статическими и динамическими) при решении задач.
Матрицы, как и массивы, нужно вводить (выводить) поэлементно. Блок- схема ввода элементов матрицы x[n][m] изображена на рис. 6.3.
(рис 6.3) Ввод элементов матрицы
(рис 6.4) Блок-схема построчного вывода матрицы
При выводе матрицы элементы располагаются построчно, например:
$$\begin{matrix}6-9713\\5838\\378833\\55778837\end{matrix}$$Алгоритм построчного вывода элементов матрицы приведён на рис. 6.4.
Ниже приведён текст программы на C++ ввода-вывода статической матрицы.
#include <iostream>
using namespace std;
int main ( )
{
int i, j,N,M, a [ 20 ] [ 20 ];
cout<<" N = ";
cin>>N; //Ввод количества строк
cout<<" M = ";
cin>>M; //Ввод количества столбцов
cout<<"Ввод матрицы A "<<endl;
for ( i =0; i<N; i++) //Цикл по переменной i, перебираем строки матрицы
for ( j =0; j<M; j++) //Цикл по переменной j, перебираем элементы внутри строки
cin>>a [ i ] [ j ]; //Ввод очередного элемента матрицы
cout<<"Вывод матрицы A "<<endl;
for ( i =0; i<N; i++) //Цикл по переменной i, перебираем строки матрицы
{
for ( j =0; j<M; j++) //Цикл по переменной j, перебираем строки матрицы
cout<<a [ i ] [ j ]<<" \t "; //Вывод очередного элемента матрицы
cout<<endl; //По окончанию вывода всех элементов строки — переход на новую строку.
}
}
Цикл для построчного вывода матрицы можно записать и так.
for ( i =0; i<N; cout<<end l, i++) for ( j =0; j<M; j++) cout<<a [ i ] [ j ]<<" \t ";
При вводе матрицы элементы каждой строки можно разделять пробелами, символами табуляции или Enter. Ниже представлены результаты работы программы.
N=4 M=5 Ввод матрицы A 1 2 3 5 4 3 6 7 8 9 1 2 3 4 5 6 7 8 9 0 Вывод матрицы A 1 2 3 5 4 3 6 7 8 9 1 2 3 4 5 6 7 8 9 0
Далее на примерах решения практических задач будут рассмотрены основные алгоритмы обработки матриц и их реализация в C++. Перед этим давайте вспомним некоторые свойства матриц (рис. 6.5):
(рис 6.5) Свойства элементов матрицы
Задача 6.1. Найти сумму элементов матрицы, лежащих выше главной диагонали.
Алгоритм решения данной задачи (рис. 6.6) построен следующим образом: обнуляем ячейку для накапливания суммы (переменная $$s$$), затем с помощью двух циклов (первый по строкам, второй по столбцам) просматриваем каждый элемент матрицы, но суммируем только в том случае, если этот элемент находится выше главной диагонали (при выполнении условия $$i < j$$).
(рис 6.6) Блок-схема задачи 6.1 (алгоритм 1).
(рис 6.7) Блок-схема задачи 6.1 (алгоритм 2).
Текст программы:
#include <iostream>
using namespace std;
int main ( )
{
int s, i, j, n,m, a [ 20 ] [ 20 ];
cout<<" N = ";
cin>>n;
cout<<" M = ";
cin>>m;
cout<<"Ввод матрицы A "<<endl;
for ( i =0; i<n; i++)
for ( j =0; j<m; j++)
cin>>a [ i ] [ j ];
for ( s= i =0; i<n; i++)
for ( j =0; j<m; j++)
//Если элемент лежит выше главной диагонали, то наращиваем сумму.
if ( j> i ) s+=a [ i ] [ j ];
cout<<" S = "<<s<<endl;
}
На рис. 6.7 изображён ещё один алгоритм решения данной задачи. В нём проверка условия $$i < j$$ не выполняется, но, тем не менее, в нём так же суммируются элементы матрицы, находящиеся выше главной диагонали. В нулевой строке заданной матрицы необходимо сложить все элементы, начиная с первого. В первой — все, начиная со второго, в $$i$$–й строке процесс начнётся с ($$i + 1$$)–го элемента и так далее. Таким образом, внешний цикл работает от 0 до $$N - 1$$, а второй от $$i+1$$ до $$M$$. Авторы надеются, что читатель самостоятельно составит программу, соответствующую описанному алгоритму.
Задача 6.2. Вычислить количество положительных элементов квадратной матрицы, расположенных по её периметру и на диагоналях. Напомним, что в квадратной матрице число строк равно числу столбцов.
Прежде чем приступить к решению задачи, рассмотрим рис. 6.8, на котором изображена схема квадратных матриц различной размерности.
Из условия задачи понятно, что не нужно рассматривать все элементы заданной матрицы. Достаточно просмотреть первую и последнюю строки, первый и последний столбцы, а так же диагонали. Все эти элементы отмечены на схеме, причём чёрным цветом выделены элементы, обращение к которым может произойти дважды. Например, элемент с номером (0, 0) принадлежит как к нулевой строке, так и к нулевому столбцу, а элемент с номером ($$N - 1,N - 1$$) находится в последней строке и последнем столбце одновременно. Кроме того, если $$N$$ — число нечётное (на рис. 6.8 эта матрица расположена слева), то существует элемент с номером ($$N/2,N/2$$), который находится на пересечении главной и побочной диагоналей. При чётном значении $$N$$ (матрица справа на рис. 6.8) диагонали не пересекаются.
(рис 6.8) Рисунок к задаче 6.2.
Итак, разобрав подробно постановку задачи, рассмотрим алгоритм её решения. Для обращения к элементам главной диагонали вспомним, что номера строк этих элементов всегда равны номерам столбцов. Поэтому, если параметр $$i$$ изменяется циклически от 0 до $$N - 1$$, то A[i][i] — элемент главной диагонали. Воспользовавшись свойством, характерным для элементов побочной диагонали, получим: $$i+j+1=N \longrightarrow j=N-i-1,$$ следовательно, для $$i = 0, 1,...,N - 1$$ элемент А[i][N-i-1] — элемент побочной диагонали. Элементы, находящиеся по периметру матрицы записываются следующим образом: А[0][i] — нулевая строка, А[N-1][i] — последняя строка и соответственно А[i][0] — нулевой столбец, А[i][N-1] — последний столбец.
Текст программы решения задачи с пояснениями приведён далее.
#include <iostream>
using namespace std;
int main ( )
{
int k, i, j,N, a [ 20 ] [ 20 ];
cout<<" N = ";
cin>>N;
cout<<"Ввод матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
//k — количество положительных элементов матрицы,
//расположенных по её периметру и на диагоналях.
for ( i=k=0; i<N; i++)
{
if ( a [ i ] [ i ] >0) k++; //Элемент лежит на главной диагонали.
if ( a [ i ] [ N-i -1]>0)k++; //Элемент лежит на побочной диагонали.
}
for ( i =1; i<N-1; i++)
{
if ( a [ 0 ] [ i ] >0) k++; //Элемент находится в нулевой строке.
if ( a [N-1 ] [ i ] >0) k++; //Элемент находится в последней строке.
if ( a [ i ] [ 0 ] > 0 ) k++; //Элемент находится в нулевом столбце.
if ( a [ i ] [ N-1]>0)k++; //Элемент находится в последнем столбце.
}
//Элемент, находящийся на пересечении диагоналей, подсчитан дважды,
//надо уменьшить вычисленное значение k на один.
if ( (N%2!=0)(a [N/ 2 ] [N/2 ] >0) ) k--;
cout<<" k = "<<k<<endl;
}
Задача 6.3. Проверить, является ли заданная квадратная матрица единичной.
Единичной называют матрицу, у которой элементы главной диагонали — единицы, а все остальные — нули. Например,
$$\left(\begin{matrix}10000\\01000\\00100\\00010\\00001\end{matrix}\right)$$Решать задачу будем так. Предположим, что матрица единичная, и попытаемся доказать обратное. Если окажется, что хотя бы один диагональный элемент не равен единице, или любой из элементов вне диагонали не равен нулю, то матрица единичной не является. Воспользовавшись логическими операциями языка С(С++), все эти условия можно соединить в одно и составить программу, текст которой приведён ниже.
#include <iostream>
using namespace std;
int main ( )
{
int pr, i, j,N, **a;
cout<<" N = "; //Ввод размерности матрицы
cin>>N;
a=new int * [N ]; //Создаём квадратную динамическую матрицу
for ( i =0; i<N; i++)
a [ i ]=new int [N ];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
//Предположим, что матрица единичная, и присвоим переменной pr значение 1 (истина).
//Если значение этой переменной при выходе из цикла не изменится, это будет означать,
//что матрица действительно единичная.
for ( pr =1, i =0; i<N; i++)
for ( j =0; j<N; j++)
if ( ( ( i==j )(a [ i ] [ j ] ! = 1 ) ) | | ( ( i != j )(a [ i ] [ j ] ! = 0 ) ) )
//Если элемент лежит на главной диагонали и не равен единице или элемент лежит вне
//главной диагонали и не равен нулю, то
{
pr =0; //Переменной pr присвоить значение 0 (ложь), это будет означать,
break; //что матрица единичной не является, и выйти из цикла.
}
//Проверка значения переменной pr и вывод соответствующего сообщения.
if ( pr ) cout<<"Единичная матрица\n ";
else cout<<"Матрица не является единичной\n ";
}
Задача 6.4.Преобразовать исходную матрицу так, чтобы нулевой элемент каждой строки был заменён средним арифметическим элементов этой строки.
Для решения данной задачи необходимо найти в каждой строке сумму элементов, которую разделить на их количество. Полученный результат записать в нулевой элемент соответствующей строки. Текст программы приведён далее.
#include <iostream>
using namespace std;
int main ( )
{
int i, j,N,M;
double S, **a;
cout<<" N = "; //Ввод размерности матрицы.
cin>>N;
cout<<" M = ";
cin>>M;
a=new double * [N ]; //Создаём динамическую матрицу
for ( i =0; i<N; i++)
a [ i ]=new double [M];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
cin>>a [ i ] [ j ];
//Цикл по i завершается записью среднего значения в нулевой элемент строки и наращиванием i.
for ( i =0; i<N; a [ i ] [ 0 ] = S/M, i++)
for ( S=j =0; j<M; j++) //Вычисление суммы элементов строки.
S+=a [ i ] [ j ];
cout<<"Преобразованная матрица A "<<endl;
for ( i =0; i<N; cout<<end l, i++)
for ( j =0; j<M; j++)
cout<<a [ i ] [ j ]<<" \t ";
}
Задача 6.5. Задана матрица A(n,m). Поменять местами её максимальный и минимальный элементы.
Алгоритм решения этой задачи следующий: находим максимальный элемент матрицы (max) и его индексы (imax, jmax), а также минимальный (min) и его индексы (imin, jmin). После чего элементы A[imax][jmax] и A[imin][jmin] поменяем местами. Для поиска максимального элемента и его индексов в переменную max запишем A[0][0], в переменные imax, jmax (номер строки и столбца, где находятся максимальный элемент) запишем 0. Затем в двойном цикле (цикл по переменной $$i$$ — по строкам, цикл по переменной $$j$$ — по столбцам) перебираем все элементы, и каждый из них сравниваем с максимальным (со значением переменной max). Если текущий элемент массива оказывается больше максимального, то его переписываем в переменную max, а в переменную imax — текущее значение индекса $$i$$, в переменную jmax — текущее значение $$j$$. Поиск минимального элемента матрицы аналогичен и отличается только знаком операции сравнения. Далее представлен текст программы решения задачи 6.5.
#include <iostream>
using namespace std;
int main ( )
{
int i, j, imax, jmax, imin, jmin,N,M;
double min, max, b, **a;
cout<<" N = "; //Ввод размеров матрицы.
cin>>N;
cout<<" M = ";
cin>>M;
a=new double * [N ]; //Создаём динамическую матрицу
for ( i =0; i<N; i++)
a [ i ]=new double [M];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
cin>>a [ i ] [ j ];
//Двойной цикл для поиска максимального, минимального элементов и их индексов.
for (max=min=a [ 0 ] [ 0 ], imax=jmax=imin=jmin= i =0; i<N; i++)
for ( j =0; j<M; j++)
{
if ( a [ i ] [ j ]>max) {max=a [ i ] [ j ]; imax= i; jmax=j; }
if ( a [ i ] [ j ]<min ) {min=a [ i ] [ j ]; imin= i; jmin=j; }
}
//Обмен двух элементов матрицы.
b=a [ imax ] [ jmax ];
a [ imax ] [ jmax ]=a [ imin ] [ jmin ];
a [ imin ] [ jmin ]=b;
//Вывод преобразованной матрицы.
cout<<"Преобразованная матрица A "<<endl;
for ( i =0; i<N; cout<<end l, i++)
for ( j =0; j<M; j++)
cout<<a [ i ] [ j ]<<" \t ";
}
Задача 6.6. Преобразовать матрицу A(m,n) так, чтобы строки с нечётными индексами были упорядочены по убыванию, с чётными — по возрастанию.
В связи с нумерацией строк в C++ с нуля необходимо помнить, что нулевая, вторая, четвёртая строки упорядочиваются по убыванию, а первая, третья, пятая и т.д. — по возрастанию. Алгоритм решения этой задачи сводится к тому, что уже известный нам по предыдущей главе алгоритм упорядочивания элементов в массиве выполняется для каждой строки матрицы. Блок-схема приведена на рис. 6.9. Текст программы с комментариями приведён ниже.
#include <iostream>
using namespace std;
int main ( )
{
int i, j, k,N,M;
double b, **a;
cout<<" M = "; //Ввод размеров матрицы.
cin>>M;
cout<<" N = ";
cin>>N;
a=new double * [N ]; //Создаём динамическую матрицу
for ( i =0; i<N; i++)
a [ i ]=new double [M];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<M; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
for ( i =0; i<M; i++) //Цикл по i — для перебора строк матрицы.
if ( i%2==0) //Если строка чётна, то
{ //упорядочиваем элементы строки по возрастанию,
for ( k=1;k<N; k++)
for ( j =0; j<N-k; j++)
if ( a [ i ] [ j ]>a [ i ] [ j +1 ])
{
b=a [ i ] [ j ];
a [ i ] [ j ]=a [ i ] [ j + 1 ];
a [ i ] [ j +1]=b;
}
}
else //иначе нечётные строки упорядочиваем по убыванию.
for ( k=1;k<N; k++)
for ( j =0; j<N-k; j++)
if ( a [ i ] [ j ]<a [ i ] [ j +1 ])
{
b=a [ i ] [ j ];
a [ i ] [ j ]=a [ i ] [ j + 1 ];
a [ i ] [ j +1]=b;
}
cout<<"Преобразованная матрица A "<<endl; //Вывод преобразованной матрицы.
for ( i =0; i<M; cout<<endl, i++)
for ( j =0; j<N; j++)
cout<<a [ i ] [ j ]<<" \t ";
}
(рис 6.9) Блок-схема алгоритма задачи 6.6.
Задача 6.7. Поменять местами элементы главной и побочной диагонали матрицы $$A(k,k)$$.
Алгоритм решения задачи следующий: перебираем все строки матрицы (цикл по переменной $$i$$ от 0 до $$k-1$$ в тексте программы), и в каждой строке меняем местами элементы, расположенные на главной и побочной диагоналях (в $$i$$-й строке надо поменять местами элементы A[i][i] и А[i][k-i-1]). Текст программы с комментариями приведён далее.
#include <iostream>
using namespace std;
int main ( )
{
int i, j, k;
double b, ** a;
cout<<" k = "; //Ввод размера матрицы.
cin>>k;
a=new double * [ k ]; //Создаём динамическую матрицу
for ( i =0; i<k; i++)
a [ i ]=new double [ k ];
cout<<"Ввод элементов матрицы A "<<endl;
for ( i =0; i<k; i++)
for ( j =0; j<k; j++)
cin>>a [ i ] [ j ];
for ( i =0; i<k; i++) //Цикл по строкам.
{//В каждой строке обмен между элементами, лежащими на главной и побочной диагоналях.
b=a [ i ] [ i ];
a [ i ] [ i ]=a [ i ] [ k-1-i ];
a [ i ] [ k-1-i ]=b;
}
cout<<"Преобразованная матрица A "<<endl; //Вывод преобразованной матрицы.
for ( i =0; i<k; cout<<end l, i++)
for ( j =0; j<k; j++)
cout<<a [ i ] [ j ]<<" \t ";
}
Задача 6.8. Заполнить матрицу $$A(6, 6)$$ числами от 1 до 36 следующим образом:
$$\left(\begin{matrix} 123456\\ 121110987\\ 131415161718\\ 242322212019\\ 252627282930\\ 363534333231 \end{matrix}\right)$$Последовательно построчно заполняем матрицу возрастающей арифметической последовательностью 1, 2, 3,..., 36. Чётные строки заполняем от нулевого элемента к последнему, а нечётные — от последнего к нулевому. Текст программы приведён далее.
#include <iostream>
using namespace std;
int main ( int arg c, char **argv )
{
int **a, n=6,k=0, i, j;
a=new int * [ n ]; //Выделяем память для хранения матрицы
for ( i =0; i<n; i++)
a [ i ]=new int [ n ];
for ( i =0; i<n; i++) //Перебираем все строки матрицы.
if ( i%2==0) //Строки с чётными номерами заполняем возрастающей последовательностью
for ( j =0; j<n; j++) //чисел слева направо
a [ i ] [ j ]=++k;
else //Строки с нечётными номерами заполняем возрастающей последовательностью чисел
for ( j=n-1; j >=0; j --) //справа налево
a [ i ] [ j ]=++k;
cout<<"Вывод матрицы A "<<endl;
for ( i =0; i<n; cout<<end l, i++)
for ( j =0; j<n; j++)
cout<<a [ i ] [ j ]<<" \t ";
return 0;
}
В этом параграфе рассмотрим использование матриц при решении таких задач линейной алгебры, как сложение, вычитание и умножение матриц, решение систем линейных алгебраических уравнений, вычисление определителя и обратной матрицы.
Задача 6.9.Заданы четыре матрицы вещественных чисел$$ A(N,M), B(N,M), C(M,N), D(M,N)$$. Вычислить матрицу $$C=((A+B)(C-D))^2$$.
Суммой (разностью) матриц одинаковой размерности $$A$$ и $$B$$ называется матрица $$C$$, элементы которой получаются сложением $$C_{i,j}=A_{i,j}+B_{i,j}$$ (вычитанием $$C_{i,j}=A_{i,j}-B_{i,j}$$) соответствующих элементов исходных матриц.
Напомним алгоритм умножения матриц на примере
$$\small $\left(\begin{matrix}a_{0,0}a_{0,1}a_{0,2}\\a_{1,0}a_{1,1}a_{1,2}\\a_{2,0}a_{2,1}a_{2,2}\end{matrix}\right)\cdot \left(\begin{matrix}b_{0,0}b_{0,1}\\b_{1,0}b_{1,1}\\b_{2,0}b_{2,1}\end{matrix}\right)$$Воспользовавшись правилом "строка на столбец", получим матрицу:
$$\left(\begin{matrix}c_{0,0}c_{0,1}\\c_{1,0}c_{1,1}\\c_{2,0}c_{2,1}\end{matrix}\right)= \left(\begin{matrix}a_{0,0}\cdot b_{0,0}+a_{0,1}\cdot b_{1,0}+a_{0,2}\cdot b_{2,0}a_{0,0}\cdot b_{0,1}+a_{0,1}\cdot b_{1,1}+a_{0,2}\cdot b_{2,1}\\a_{1,0}\cdot b_{0,0}+a_{1,1}\cdot b_{1,2}+a_{1,2}\cdot b_{2,1}a_{1,0}\cdot b_{0,1}+a_{1,1}\cdot b_{1,1}+a_{1,2}\cdot b_{2,1}\\a_{2,0}\cdot b_{0,0}+a_{2,1}\cdot b_{1,0}+a_{2,2}\cdot b_{2,0}a_{2,0}\cdot b_{0,1}+a_{2,1}\cdot b_{1,1}+a_{2,2}\cdot b_{2,1}\end{matrix}\right)$$Произведением матриц $$A(N,M)$$ и $$B(M,L)$$ является матрица$$ C(N,L)$$, каждый элемент которой $$C_{i,j}$$ вычисляется по формуле: $$C_{i,j}=\sum\limits_{k=0}^{M-1}A_{i,k}B_{k,j}$$ где $$i = 0,N - 1$$ и $$j = 0,L - 1$$.
Операция умножения имеет смысл только в том случае, если количество строк левой матрицы совпадает с количеством столбцов правой. Кроме того, $$A\cdot B\neq B\cdot A$$.
При решении задачи будем использовать динамические матрицы и двойные указатели. Напишем следующие функции.
float **sum_m(float **A, float **B, int N, int M) — функция формирует матрицу, которая является суммой двух матриц. Здесь A, B — указатели на исходные матрицы, N, M — количество строк и столбцов матриц, функция возвращает указатель на сформированную матрицу, которая является суммой двух матриц $$A$$ и $$B$$.float **minus_m(float **A, float **B, int N, int M) — функция формирует матрицу, которая является разностью двух матриц. Здесь A, B — указатели на исходные матрицы, N, M — количество строк и столбцов матриц, функция возвращает указатель на сформированную матрицу, которая является разностью двух матриц $$A$$ и $$B$$.float **product_m(float **A, float **B, int N, int M, int L) — функция формирует матрицу, которая является произведением двух матриц. Здесь A, B — указатели на исходные матрицы. Матрица $$A$$ имеет $$N$$ строк и $$M$$ столбцов, матрица $$B$$ имеет $$M$$ строка и $$L$$ столбцов, функция возвращает указатель на сформированную матрицу, которая является произведением двух матриц $$A$$ и $$B$$.float **create_m(int N, int M) — функция создаёт матрицу, в которой будет $$N$$ строк и $$M$$ столбцов, осуществляет ввод элементов матрицы, функция возвращает указатель на сформированную матрицу.void output_m(float **A, int N, int M) — функция построчного вывода на экран матрицы $$A$$, которая имеет $$N$$ строк и $$M$$ столбцов.Далее приведён текст программы с комментариями.
#include <iostream>
using namespace std;
//функция вычисления суммы двух матриц.
float **sum_m( float **A, float **B, int N, int M)
{
int i, j;
float **temp; //указатель для хранения результирующей матрицы
temp=new float * [N ]; //выделение памяти для хранения результирующей матрицы
for ( i =0; i<N; i++)
temp [ i ]=new float [M];
for ( i =0; i<N; i++) //Вычисляем сумму двух матриц
for ( j =0; j<M; j++)
temp [ i ] [ j ]=A [ i ] [ j ]+B [ i ] [ j ];
return temp; //Возвращаем матрицу как двойной указатель
}
//функция вычисления разности двух матриц.
float **minus_m ( float **A, float **B, int N, int M)
{ int i, j;
float **temp; //указатель для хранения результирующей матрицы
temp=new float * [N ]; //выделение памяти для хранения результирующей матрицы
for ( i =0; i<N; i++)
temp [ i ]=new float [M];
for ( i =0; i<N; i++) //Вычисляем разность двух матриц
for ( j =0; j<M; j++)
temp [ i ] [ j ]=A [ i ] [ j ]-B [ i ] [ j ];
return temp; //Возвращаем матрицу как двойной указатель
}
//функция вычисления произведения двух матриц.
float **product_m ( float **A, float **B, int N, int M, int L)
{
int i, j, k;
float **temp; //указатель для хранения результирующей матрицы
temp=new float * [N ]; //выделение памяти для хранения результирующей матрицы
for ( i =0; i<N; i++)
temp [ i ]=new float [ L ];
//Вычисляем произведение двух матриц, последовательно формируя все элементы матрицы
for ( i =0; i<N; i++)
for ( j =0; j<L; j++)
//Элемент с индексами i, j — скалярное произведение i-й строки матрицы A
for ( temp [ i ] [ j ]=k=0;k<M; k++) //и j-го столбца матрицы B
temp [ i ] [ j ]+=A [ i ] [ k ] *B [ k ] [ j ];
return temp; //Возвращаем матрицу как двойной указатель
}
//функция создаёт динамическую матрицу вещественных чисел размерности N на M,
//в этой же функции осуществляется и ввод элементов матрицы
float ** create_m ( int N, int M)
{
int i, j;
float **temp;
temp=new float * [N ];
for ( i =0; i<N; i++)
temp [ i ]=new float [M];
cout<<"Ввод матрицы\n ";
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
cin>>temp [ i ] [ j ];
return temp;
}
//функция осуществляет построчный вывод матрицы A(N,M)
void output_m ( float **A, int N, int M)
{
int i, j;
//Цикл по строкам. По окончанию вывода всех элементов строки — переход на новую строку.
for ( i =0; i<N; cout<<endl, i++)
for ( j =0; j<M; j++) //Цикл по переменной j, в котором перебираем строки матрицы
cout<<A [ i ] [ j ]<<" \t "; //Вывод очередного элемента матрицы и символа табуляции.
}
int main ( int arg c, char ** argv )
{
float **A, **B, **C, **D, ** result; //указатели для хранения исходных и
результирующей матриц
int N,M;
cout<<" N = "; cin>>N; //Ввод размерностей матрицы
cout<<" M = "; cin>>M;
//Выделение памяти и ввод матриц A, B, C, D, обращением к функции create_m.
A=create_m (N,M);
B=create_m (N,M);
C=create_m (M,N);
D=create_m (M,N);
//Вычисление результирующей матрицы.
result=product_m ( product_m (sum_m(A, B,N,M),minus_m (C,D,M,N),N,M,N), product_m
(sum_m(A, B,N,M),minus_m (C,D,M,N),N,M,N),N,N,N);
output_m ( result,N,N); //Вывод результирующей матрицы.
return 0;
}
Далее без комментариев приведена программа решения задачи 6.9 с помощью динамических матриц и обычных
#include <iostream>
using namespace std;
float *sum_m( float *A, float *B, int N, int M)
{
int i, j;
float *temp;
temp=new float [N*M];
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
temp [ i *M+j ]=A [ i *M+j ]+B [ i *M+j ];
return temp;
}
float *minus_m ( float *A, float *B, int N, int M)
{ int i, j;
float *temp;
temp=new float [N*M];
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
temp [ i *M+j ]=A [ i *M+j ]-B [ i *M+j ];
return temp;
}
float *product_m ( float *A, float *B, int N, int M, int L)
{
int i, j, k;
float *temp;
temp=new float [N*L ];
for ( i =0; i<N; i++)
for ( j =0; j<L; j++)
for ( temp [ i *L+j ]=k=0;k<M; k++)
temp [ i *L+j ]+=A [ i *M+k ] *B [ k*L+j ];
return temp;
}
float *create_m ( int N, int M)
{
int i, j;
float *temp;
temp=new float [N*M];
cout<<"Ввод матрицы\n ";
for ( i =0; i<N; i++)
for ( j =0; j<M; j++)
cin>>temp [ i *M+j ];
return temp;
}
void output_m ( float *A, int N, int M)
{
int i, j;
for ( i =0; i<N; cout<<endl, i++)
for ( j =0; j<M; j++)
cout<<A [ i *M+j ]<<" \t ";
}
int main ( int arg c, char ** argv )
{
float *A, *B, *C, *D, * result;
int N,M;
cout<<" N = "; cin>>N;
cout<<" M = "; cin>>M;
A=create_m (N,M);
B=create_m (N,M);
C=create_m (M,N);
D=create_m (M,N);
result=product_m ( product_m (sum_m(A, B,N,M), minus_m (C,D,M,N),N,M,N),
product_m (sum_m(A, B,N,M), minus_m (C,D,M,N),N,M,N),N,N,N);
output_m ( result,N,N);
return 0;
}
Задача 6.10. Решить систему линейных алгебраических уравнений.
При решении этой задачи напишем универсальную функцию решения системы линейных алгебраических уравнений методом Гаусса, а в функции main() просто вызовем эту функцию. Вспомним метод Гаусса.
Пусть дана система линейных алгебраических уравнений (СЛАУ) с $$n$$ неизвестными
$$\left\{\begin{array}{ll} a_{00}x_0+a_{01}x_1+...+a_{0n-1}x_{n-1}=b_0,\\ a_{10}x_0+a_{11}x_1+...+a_{1n-1}x_{n-1}=b_1,\\ \hdotsfor{2}\\ a_{n-10}x_0+a_{n-11}x_1+...+a_{n-1n-1}x_{n-1}=b_{n-1} \end{array}\right.$$Обозначим через $$A=\left(\begin{matrix} a_{00}a_{01}...a_{0n-1}\\ a_{10}a_{11}...a_{1n-1}\\ ............\\ a_{n-10}a_{n-11}...a_{n-1n-1} \end{matrix}\right)$$ матрицу коэффициентов системы (6.1), через $$b=\left(\begin{matrix}b_0\\b_1\\...\\b_{n-1}\end{matrix}\right)$$- столбец её свободных членов, и через $$x=\left(\begin{matrix}x_0\\x_1\\...\\x_{n-1}\end{matrix}\right)$$- столбец из неизвестных (искомый вектор). Тогда система (6.1) может быть записана в виде матричного уравнения $$Ax = b$$.
Наиболее распространённым приёмом решения систем линейных уравнений является алгоритм последовательного исключения неизвестных — метод Гаусса.
При решении систем линейных алгебраических уравнений этим методом всевозможные преобразования производят не над уравнениями системы (6.1), а над так называемой расширенной матрицей системы, которая получается путём добавления к основной матрице $$A$$ столбца свободных членов $$b$$.
Первый этап решения системы уравнений, называемый прямым ходом метода Гаусса, заключается в приведении расширенной матрицы (6.2) к треугольному виду. Это означает, что все элементы матрицы (6.2) ниже главной диагонали должны быть равны нулю.
$$A'=\left(\begin{matrix} a_{00}a_{01}...a_{0n-1}b_0\\ a_{10}a_{11}...a_{1n-1}b_1\\ ...............\\ a_{n-10}a_{n-11}...a_{n-1n-1}b_{n-1} \end{matrix}\right)$$На первом этапе необходимо обнулить элементы 0-го столбца расширенной матрицы
$$A^{'}=\left(\begin{matrix} a_{00}a_{01}a_{02}...a_{0n-1}b_0\\ 0a_{11}a_{12}...a_{1n-1}b_1\\ 00a_{22}...a_{2n-1}b_2\\ 000...a_{3n-1}b_3\\ ..................\\ 000...a_{n-1n-1}b_{n-1} \end{matrix}\right)$$Для этого необходимо из каждой строки (начиная с первой) вычесть нулевую, умноженную на некоторое число $$M$$. В общем виде этот процесс можно записать так:
1-я строка = 1-я строка – $$M\times 0$$-я строка
2-я строка = 2-я строка – $$M\times 0$$-я строка
...
$$i$$-я строка =$$ i$$-я строка – $$M\times 0$$-я строка
...
$$n - 1$$-я строка = $$n - 1$$-я строка – M\times 0-я строка
Понятно, что преобразование элементов первой строки будет происходить по формулам:
$$a_{10}=a_{10}-Ma_{00}\\ a_{11}=a_{11}-Ma_{01}\\ … \\ a_{1i}=a_{1i}-Ma_{0i}\\ … \\ a_{1n-1}=a_{1n-1}-Ma_{0n-1}\\ b_1=b_1-Mb_0$$Так как целью данных преобразований является обнуление первого элемента строки, то $$M$$ выбираем из условия: $$a_{10}=a_{10}-Ma_{00}=0$$. Следовательно, $$M=\frac{a_{10}}{a_{00}}$$.
Элементы второй строки и коэффициент $$M$$ можно рассчитать аналогично:
$$a_{20}=a_{20}-Ma_{00}\\ a_{21}=a_{21}-Ma_{01}\\ …\\ a_{2i}=a_{2i}-Ma_{0i}\\ … \\ a_{2n-1}=a_{2n-1}-Ma_{0n-1}\\ b_2=b_2-Mb_0$\\ a_{20}=a_{20}-Ma_{00}=0 \Rightarrow M=\frac{a_{20}}{a_{00}$.$$Таким образом, преобразование элементов $$i$$–й строки будет происходить следующим образом:
$$a_{i0}=a_{i0}-Ma_{00}\\ a_{i1}=a_{i1}-Ma_{01}\\ …\\ a_{ii}=a_{ii}-Ma_{0i}\\ …\\ a_{in-1}=a_{in-1}-Ma_{0n-1}\\ b_i=b_i-Mb_0.$$Коэффициент $$M$$ для $$i$$–й строки выбирается из условия $$a_{i0}=a_{i0}-Ma_{00}=0$$ и равен $$M=\frac{a_{i0}}{a_{00}$$.
После проведения подобных преобразований для всех строк матрица (6.2) примет вид
$$A'=\left(\begin{matrix}a_{00}a_{01}...a_{0n-1}b_0\\0a_{11}...a_{1n-1}b_1\\0a_{21}...a_{2n-1}b_2\\...............\\0a_{n-11}...a_{n-1n-1}b_{n-1}\end{matrix}\right)$$Блок-схема обнуления первого столбца матрицы приведена на рис. 6.10.
Очевидно, что если повторить описанный выше алгоритм для следующих столбцов матрицы (6.2), то в результате будет получена матрица (6.3). Алгоритм этого процесса изображён на рис. 6.11.
(рис 6.10) Блок-схема обнуления первого столбца матрицы
(рис 6.11) Блок-схема алгоритма преобразования расширенной матрицы к треугольному виду
Заметим, что если в матрице (6.2) на главной диагонали встретится элемент $$a_{k,k}$$, равный нулю, то расчёт коэффициента $$M=\frac{a_{ik}}{a_{kk}}$$ для k$$-$$й строки будет невозможен. Избежать деления на ноль можно, избавившись от нулевых элементов на главной диагонали. Для этого перед обнулением элементов в $$k$$–м столбце необходимо найти в нём максимальный по модулю элемент (среди расположенных ниже $$a_{k,k}$$), запомнить номер строки, в которой он находится, и поменять её местами с $$k$$-й. Алгоритм, отображающий эти преобразования, приведён на рис. 6.12.
В результате выполнения прямого хода метода Гаусса матрица (6.2) преобразуется в матрицу (6.3), а система уравнений (6.1) будет иметь следующий вид:
$$\left\{\begin{array}{rl} a_{00}x_0+a_{01}x_1+a_{20}x_2+...+a_{0n-1}x_{n-1}=b_0,\\ a_{11}x_1+a_{21}x_2+...+a_{1n-1}x_{n-1}=b_1,\\ a_{22}x_2+...+a_{2n-1}x_{n-1}=b_2,\\ ...\\ a_{n-1n-1}x_{n-1}=b_{n-1} \end{array}\right.$$Решение системы (6.4) называют обратным ходом метода Гаусса.
Последнее $$(n - 1)$$-е уравнение системы (6.4) имеет вид: $$a_{n-1n-1}x_{n-1}=b_{n-1}$$. Тогда, если $$a_{n-1n-1}\neq 0$$, то $$x_{n-1}=\frac{b_{n-1}}{a_{n-1n-1}}$$. В случае, если a$$a_{n-1n-1}=0,$$, и $$b_{n-1}=0$$, то система (6.4), а следовательно, и система (6.1) имеют бесконечное множество решений.
При $$a_{n-1n-1}=0$$ и $$b_{n-1}\neq 0$$ система (6.4), а значит и система (6.1), решения не имеет. Предпоследнее $$(n-2)$$-е уравнение системы (6.4) имеет вид $$a_{n-2n-2}x_{n-2}+a_{n-2n-1}x_{n-1}=b_{n-2}$$.
(рис 6.12) Блок-схема алгоритма перестановки строк расширенной матрицы
(рис 6.13) Блок-схема алгоритма обратного хода метода Гаусса
Значит, $$x_{n-2}=\frac{b_{n-2}-a_{n-2n-1}x_{n-1}}{a_{n-2n-2}}$$.
Следующее $$(n - 3)$$-е уравнение системы (6.4) будет выглядеть так:
$$a_{n-3n-3}x_{n-3}+a_{n-3n-2}x_{n-2}+a_{n-3n-1}x_{n-1}=b_{n-3}.$$Отсюда имеем
$$x_{n-3}=\frac{b_{n-3}-a_{n-3n-2}x_{n-2}-a_{n-3n-1}x_{n-1}}{a_{n-3n-3}},x_{n-3}= \frac{b_{n-3}-\sum\limits_{j=n-2}^{n-1}{a_{n-3j}x_j}}{a_{n-3n-3}}.$$Таким образом, формула для вычисления $$i$$-го значения $$x$$ будет иметь вид:$$x_i=\frac{b_i-\sum\limits_{j=i+1}^{n-1}{a_{ij}x_j}}{a_{ii}}$$.
Алгоритм, реализующий обратный ход метода Гаусса, представлен в виде блок-схемы на рис. 6.13.
Объединив блок-схемы, изображённые на рис. 6.11,рис. 6.12 и рис. 6.13, получим общую блок-схему метода Гаусса (рис. 6.14). Блоки 2-6 содержат последовательный ввод данных, где $$n$$ — это размерность системы линейных алгебраических уравнений, а сама система задаётся в виде матрицы коэффициентов при неизвестных $$A$$ и вектора свободных коэффициентов $$b$$. Блоки 7-18 предусматривают прямой ход метода Гаусса, а блоки 23-27 — обратный. Для вывода результатов предусмотрено несколько блоков вывода. Если результат проверки условий 19 и 20 положительный, то выдаётся сообщение о том, что система имеет бесконечное множество решений (блок 21). Если условие 19 выполняется, а 20 — нет, то появляется сообщение о том, что система не имеет решений (блок 22). Сами же решения системы уравнений, представленные вектором $$x$$, вычисляются (блоки 23–26) и выводятся экран/печать (блок 27) только в случае невыполнения условия.
Теперь алгоритм решения СЛАУ, представленный на рис. 6.14, разобьём на
главную функцию main() и функцию решения СЛАУ методом Гаусса. В функции main() будет находиться ввод исходных данных, обращение к функции SLAU
и вывод вектора решения. Функция SLAU предназначена для решения системы
линейных алгебраических уравнений методом Гаусса.
При написании функции следует учитывать следующее: в методе Гаусса изменяются матрица коэффициентов и вектор правых частей. Поэтому, для того чтобы их не испортить, в функции SLAU матрицу коэффициентов и вектор правых частей необходимо скопировать во внутренние переменные, и в функции обрабатывать внутренние переменные-копии.
Функция SLAU возвращает значение 0, если решение найдено, -1 — если система имеет бесконечное множество решений, -2 — если система не имеет решений.
Ниже приведено решение задачи 6.10 с подробными комментариями.
#include <iostream>
#include <math.h>
using namespace std;
int SLAU( double ** matrica_a, int n, double *massiv_b, double *x )
//Функция SLAU возвращает значение типа int: 0, если решение найдено, _1 — если система имеет
//бесконечное множество решений, _2 — если система не имеет решений.
//Формальные параметры функции: n — размерность системы,
//matrica_a — матрица коэффициентов СЛАУ,
//massiv_b — вектор правых частей, x — решение СЛАУ, передаются как указатели.
{
int i, j, k, r;
double c,M, max, s;
//Матрица a — копия матрицы коэффициентов, массив b — копия вектора правых частей.
double **a, *b;
a=new double * [ n ]; //Выделение памяти для a и b.
for ( i =0; i<n; i++)
a [ i ]=new double [ n ];
b=new double [ n ];
//В a записываем копию матрицы коэффициентов, в b копию вектора правых частей.
for ( i =0; i<n; i++)
for ( j =0; j<n; j++)
a [ i ] [ j ]=matrica_a [ i ] [ j ];
for ( i =0; i<n; i++)
b [ i ]=massiv_b [ i ];
//Прямой ход метода Гаусса: приводим матрицу a (копию матрицы коэффициентов СЛАУ)
//к диагональному виду.
for ( k=0;k<n; k++)
{ //Поиск максимального по модулю элемента в k-м столбце.
max=fabs ( a [ k ] [ k ] );
r=k;
for ( i=k+1; i<n; i++)
if ( fabs ( a [ i ] [ k ] )>max)
{
max=fabs ( a [ i ] [ k ] );
r= i;
}
for ( j =0; j<n; j++) //Меняем местами k-ю и r-ю (строку, где находится
{ //максимальный по модулю элемент) строки.
c=a [ k ] [ j ];
a [ k ] [ j ]=a [ r ] [ j ];
a [ r ] [ j ]= c;
}
c=b [ k ];
b [ k ]=b [ r ];
b [ r ]= c;
for ( i=k+1; i<n; i++) //Приведение матрицы к диагональному виду.
{
for (M=a [ i ] [ k ] / a [ k ] [ k ], j=k; j<n; j++)
a [ i ] [ j ]-=M*a [ k ] [ j ];
b [ i ]-=M*b [ k ];
}
}
//Обратный ход метода Гаусса.
if ( a [ n-1 ] [ n-1]==0) //Если последний диагональный элемент равен 0 и
if ( b [ n-1]==0) //последний коэффициент вектора свободных членов равен 0,
return -1; //то система имеет бесконечное множество решений
else return -2; //последний коэффициент вектора свободных членов не равен 0,
//система решений не имеет.
else //Последний диагональный элемент не равен 0, начинается обратный ход метода Гаусса.
{
for ( i=n-1; i >=0; i --)
{
for ( s =0, j= i +1; j<n; j++)
s+=a [ i ] [ j ] * x [ j ];
x [ i ]=( b [ i ]- s ) / a [ i ] [ i ];
}
return 0;
}
}
int main ( )
{
int result, i, j,N;
double **a, *b, *x;
cout<<" N = "; //Ввод размерности системы.
cin>>N;
a=new double * [N ]; //Выделение памяти для матрицы правых частей и вектора свободных
членов.
for ( i =0; i<N; i++)
a [ i ]=new double [N ];
b=new double [N ];
x=new double [N ];
cout<<"Ввод матрицы A "<<endl; //Ввод матрицы правых частей
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
cout<<"Ввод вектора B "<<endl; //и вектора свободных членов.
for ( i =0; i<N; i++)
cin>>b [ i ];
//Вызов функции решения СЛАУ методом Гаусса. По значению result можно судить, сколько
//корней имеет система. Если result=0, то система имеет единственное решение, result= -1 -
//система имеет бесконечное множество решений, result=-2 — система не имеет решений.
result=SLAU( a,N, b, x );
if ( result ==0)
{ //Вывод массива решения.
cout<<" MassivX "<<endl;
for ( i =0; i<N; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
}
else if ( result ==-1)
cout<<"Бесконечное множество решений\n ";
else if ( result ==-2)
cout<<"Нет решений\n ";
}
(рис 6.14) Блок-схема алгоритма решения СЛАУ методом Гаусса
Задача 6.11. Найти обратную матрицу к квадратной матрице $$A(N,N)$$.
Один из методов вычисления обратной матрицы основан на решении систем линейных алгебраических уравнений. Пусть задана некоторая матрица $$A$$:
$$A=\left(\begin{matrix}a_{00}a_{01}a_{02}...a_{0n-1}\\a_{10}a_{11}a_{12}...a_{1n-1}\\...............\\a_{n-10}a_{n-11}a_{n-12}...a_{n-1n-1}\end{matrix}\right)$$Необходимо найти матрицу $$A^{-1}$$, которая является обратной к матрице $$A$$:
$$Y=A^{-1}=\left(\begin{matrix}y_{00}y_{01}y_{02}...y_{0n-1}\\y_{10}y_{11}y_{12}...y_{1n-1}\\...............\\y_{n-10}y_{n-11}y_{n-12}...y_{n-1n-1}\end{matrix}\right)$$Матрица (6.6) будет обратной к матрице (6.5), если выполняется соотношение $$A\cdot A^{-1}=E$$, где $$E$$ — это единичная матрица, или более подробно:
$$\left(\begin{matrix}a_{00}a_{01}...a_{0n-1}\\a_{10}a_{11}...a_{1n-1}\\............\\a_{n-10}a_{n-11}...a_{n-1n-1}\end{matrix}\right)\left(\begin{matrix}y_{00}y_{01}...y_{0n-1}\\y_{10}y_{11}...y_{1n-1}\\............\\y_{n-10}y_{n-11}...y_{n-1n-1}\end{matrix}\right)=E$$Результат перемножения матриц из соотношения (6.7) можно представить поэлементно в виде $$n$$ систем линейных уравнений. Умножение матрицы (6.5) на нулевой столбец матрицы (6.6) даст нулевой столбец единичной матрицы:
$$\left\{\begin{matrix} a_{00}y_{00}+a_{01}y_{10}+...+a_{0n-1}y_{n-10}=1,\\ a_{10}y_{00}+a_{11}y_{10}+...+a_{1n-1}y_{n-10}=0,\\ \hdotsfor{2}\\ a_{i0}y_{00}+a_{i1}y_{10}+...+a_{in-1}y_{n-10}=0,\\ \hdotsfor{2}\\ a_{n-10}y_{00}+a_{n-11}y_{10}+...+a_{n-1n-1}y_{n-10}=0 \end{matrix}\right.$$При умножении матрицы $$A$$ на первый столбец обратной матрицы получается следующая система линейных алгебраических уравнений.
$$\left\{\begin{matrix} a_{00}y_{01}+a_{01}y_{11}+...+a_{0n-1}y_{n-11}=0,\\ a_{10}y_{01}+a_{11}y_{11}+...+a_{1n-1}y_{n-11}=1,\\ \hdotsfor{2}\\ a_{i0}y_{01}+a_{i1}y_{11}+...+a_{in-1}y_{n-11}=0,\\ \hdotsfor{2}\\ a_{n-10}y_{01}+a_{n-11}y_{11}+...+a_{n-1n-1}y_{n-11}=0 \end{matrix}\right.$$Система, полученная в результате умножения матрицы (6.5) на $$i$$-й столбец матрицы (6.6), будет выглядеть следующим образом:
$$\left\{\begin{matrix} a_{00}y_{0i}+a_{01}y_{1i}+...+a_{0n-1}y_{n-1i}=0,\\ a_{10}y_{0i}+a_{11}y_{1i}+...+a_{1n-1}y_{n-1i}=0,\\ \hdotsfor{2}\\ a_{i0}y_{0i}+a_{i1}y_{1i}+...+a_{in-1}y_{n-1i}=1,\\ \hdotsfor{2}\\ a_{n-10}y_{0i}+a_{n-11}y_{1i}+...+a_{n-1n-1}y_{n-1i}=0 \end{matrix}\right.$$Понятно, что $$n$$-я система будет иметь вид:
$$\left\{\begin{matrix} a_{00}y_{0n-1}+a_{01}y_{1n-1}+...+a_{0n-1}y_{n-1n-1}=0,\\ a_{10}y_{0n-1}+a_{11}y_{1n-1}+...+a_{1n-1}y_{n-1n-1}=0,\\ \hdotsfor{2}\\ a_{i0}y_{0n-1}+a_{i1}y_{1n-1}+...+a_{in-1}y_{n-1n-1}=0,\\ \hdotsfor{2}\\ a_{n-10}y_{0n-1}+a_{n-11}y_{1n-1}+...+a_{n-1n-1}y_{n-1n-1}=1 \end{matrix}\right.$$Решением каждой из приведённых выше систем будет $$i$$-й столбец обратной матрицы. Количество систем равно размерности обратной матрицы. Для отыскания решений систем линейных алгебраических уравнений можно воспользоваться методом Гаусса.
Описанный алгоритм представлен в виде блок-схемы на рис. 6.15. Блоки 2–5 отражают формирование вектора правых частей системы линейных алгебраических уравнений. Если условие в блоке 3 выполняется и элемент находится на главной диагонали, то он равен единице, все остальные элементы нулевые. В блоке 6 происходит вызов подпрограммы для решения системы уравнений методом Гаусса. В качестве параметров в эту подпрограмму передаётся исходная матрица $$A$$, сформированный в блоках 2–5 вектор свободных коэффициентов $$B$$, размерность системы $$n$$. Вектор $$X$$ будет решением $$i$$-й системы уравнений и, следовательно, $$i$$-м столбцом искомой матрицы $$Y$$.
Как видно из блок-схемы, приведённой на рис. 6.15, при нахождении обратной матрицы понадобится функция SLAU, рассмотренная при решении задачи 6.10. Ниже приведён текст программы с подробными комментариями решения задачи 6.11. В функции main() будет находиться ввод исходной матрицы, обращение к функции INVERSE для вычисления обратной матрицы. Из функции INVERSE будет осуществляться вызов функции SLAU для решения системы линейных алгебраических уравнений.
#include <iostream>
#include <math.h>
using namespace std;
//Функция решения системы линейных алгебраических уравнений методом Гаусса.
int SLAU( double ** matrica_a, int n, double *massiv_b, double *x )
{
int i, j, k, r;
double c,M, max, s;
double **a, *b;
a=new double * [ n ];
for ( i =0; i<n; i++)
a [ i ]=new double [ n ];
b=new double [ n ];
for ( i =0; i<n; i++)
for ( j =0; j<n; j++)
a [ i ] [ j ]=matrica_a [ i ] [ j ];
for ( i =0; i<n; i++)
b [ i ]=massiv_b [ i ];
for ( k=0;k<n; k++)
{
max=fabs ( a [ k ] [ k ] );
r=k;
for ( i=k+1; i<n; i++)
if ( fabs ( a [ i ] [ k ] )>max)
{
max=fabs ( a [ i ] [ k ] );
r= i;
}
for ( j =0; j<n; j++)
{
c=a [ k ] [ j ];
a [ k ] [ j ]=a [ r ] [ j ];
a [ r ] [ j ]= c;
}
c=b [ k ];
b [ k ]=b [ r ];
b [ r ]= c;
for ( i=k+1; i<n; i++)
{
for (M=a [ i ] [ k ] / a [ k ] [ k ], j=k; j<n; j++)
a [ i ] [ j ]*=M_a [ k ] [ j ];
b [ i ]_=M_b [ k ];
}
}
if ( a [ n -1 ] [ n-1]==0)
if ( b [ n-1]==0)
return -1;
else return -2;
else
{
for ( i=n-1; i >=0; i --)
{
for ( s =0, j= i +1; j<n; j++)
s+=a [ i ] [ j ] * x [ j ];
x [ i ]=( b [ i ]- s ) / a [ i ] [ i ];
}
return 0;
}
for ( i =0; i<n; i++)
delete [ ] a [ i ];
delete [ ] a;
delete [ ] b;
}
//Функция вычисления обратной матрицы
int INVERSE( double **a, int n, double **y )
//Формальные параметры: a — исходная матрица, n — размерность матрицы, y — обратная
матрица.
//Функция будет возвращать 0, если обратная матрица существует, -1 — в противном случае.
{
int i, j, res;
double *b, *x;
//Выделение памяти для промежуточных массивов b и x.
b=new double [ n ];
x=new double [ n ];
for ( i =0; i<n; i++)
{
//Формирование вектора правых частей для нахождения i-го столбца матрицы.
for ( j =0; j<n; j++)
if ( j==i )
b [ j ]= 1;
else b [ j ]= 0;
//Нахождение i-го столбца матрицы путём решения СЛАУ Ax = b методом Гаусса.
res=SLAU( a, n, b, x );
//Если решение СЛАУ не найдено, то невозможно вычислить обратную матрицу.
if ( res !=0)
break;
else
//Формирование i-го столбца обратной матрицы.
for ( j =0; j<n; j++)
y [ j ] [ i ]=x [ j ];
}
//Проверка существования обратной матрицы, если решение одного из уравнений Ax=b не
//существует, то невозможно найти обратную матрицу, и функция INVERSE вернёт значение -1.
if ( res !=0)
return -1;
//Если обратная матрица найдена, то функция INVERSE вернёт значение 0,
//а обратная матрица будет возвращаться через указатель double **y.
else
return 0;
}
int main ( )
{
int result, i, j,N;
double **a, **b; //Двойные указатели для хранения исходной a и обратной b матрицы.
cout<<" N = "; //Ввод размера матрицы.
cin>>N;
a=new double * [N ]; //Выделение памяти для матриц a и b.
for ( i =0; i<N; i++)
a [ i ]=new double [N ];
b=new double * [N ];
for ( i =0; i<N; i++)
b [ i ]=new double [N ];
cout<<"Ввод матрицы A "<<endl; //Ввод исходной матрицы.
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
result=INVERSE( a,N, b ); //Вычисление обратной матрицы.
if ( result ==0) //Если обратная матрица существует, то вывести её на экран.
{
cout<<"Обратная матрица"<<endl;
for ( i =0; i<N; cout<<endl, i++)
for ( j =0; j<N; j++)
cout<<b [ i ] [ j ]<<" \t ";
}
else
//Если обратная матрица не существует, то вывести соответствующее сообщение.
cout<<"Нет обратной матрицы"<<endl;
}
Задача 6.12. Найти определитель квадратной матрицы $$A(N,N)$$.
(рис 6.15) Блок-схема алгоритма вычисления обратной матрицы
Пусть задана матрица (6.2), необходимо вычислить её определитель. Для этого матрицу необходимо преобразовать к треугольному виду (6.3), а затем воспользоваться свойством, известным из курса линейной алгебры, которое гласит, что определитель треугольной матрицы равен произведению её диагональных элементов: $$\det A=\prod\limits_{i=0}^{n-1}a_{ii}$$.
Преобразование матрицы (6.2) к виду (6.3) можно осуществить с помощью прямого хода метода Гаусса. Алгоритм вычисления определителя матрицы, изображённый в виде блок-схемы на рис. 6.16, представляет собой алгоритм прямого хода метода Гаусса, в процессе выполнения которого проводится перестановка строк матрицы. Эта операция приводит к смене знака определителя. В блок- схеме момент смены знака отражён в блоках 8–9. В блоке 8 определяется, будут ли строки меняться местами, и если ответ утвердительный, то в блоке 9 происходит смена знака определителя. В блоках 15–16 выполняется непосредственное вычисление определителя путём перемножения диагональных элементов преобразованной матрицы.
На листинге приведён текст программы решения задачи 6.12 с комментариями.
#include <iostream>
#include <math.h>
using namespace std;
//Функция вычисления определителя.
double determinant ( double ** matrica_a, int n )
//Формальные параметры: matrica_a — исходная матрица, n — размер матрицы,
//функция возвращает значение определителя (тип double.)
{
int i, j, k, r;
double c,M, max, s, det =1;
//a — копия исходной матрицы.
double **a;
//Выделение памяти для матрицы a .
a=new double * [ n ];
for ( i =0; i<n; i++)
a [ i ]=new double [ n ];
//В a записываем копию исходной матрицы.
for ( i =0; i<n; i++)
for ( j =0; j<n; j++)
a [ i ] [ j ]=matrica_a [ i ] [ j ];
//Прямой ход метода Гаусса.
for ( k=0;k<n; k++)
{
max=fabs ( a [ k ] [ k ] );
r=k;
for ( i=k+1; i<n; i++)
if ( fabs ( a [ i ] [ k ] )>max)
{
max=fabs ( a [ i ] [ k ] );
r= i;
}
//Если строки менялись местами, то смена знака определителя.
if ( r !=k ) det=-det;
for ( j =0; j<n; j++)
{
c=a [ k ] [ j ];
a [ k ] [ j ]=a [ r ] [ j ];
a [ r ] [ j ]= c;
}
for ( i=k+1; i<n; i++)
for (M=a [ i ] [ k ] / a [ k ] [ k ], j=k; j<n; j++)
a [ i ] [ j ]-=M*a [ k ] [ j ];
}
//Вычисление определителя.
for ( i =0; i<n; i++)
det*=a [ i ] [ i ];
//Возврат определителя в качестве результата функции
for ( i =0; i<n; i++)
delete [ ] a [ i ];
delete [ ] a;
return det;
}
int main ( )
{
int result, i, j,N;
double **a, b;
cout<<" N = ";
cin>>N;
a=new double _ [N ];
for ( i =0; i<N; i++)
a [ i ]=new double [N ];
//Ввод значений исходной матрицы.
cout<<"Ввод матрицы A "<<endl;
for ( i =0; i<N; i++)
for ( j =0; j<N; j++)
cin>>a [ i ] [ j ];
//Обращение к функции вычисления определителя.
cout<<"определитель= "<<determinant ( a,N)<<endl;
}
(рис 6.16) Блок-схема алгоритма вычисления определителя
В этой главе читатель познакомился с обработкой статических и динамических матриц в C++, а также с использованием функций для решения задач обработки динамических матриц.
Разработать программу на языке С++ для решения следующей задачи.
(рис 6.17)
(рис 6.18)
(рис 6.19)
(рис 6.20)
(рис 6.21)
(рис 6.22)
(рис 6.23)
(рис 6.24)
(рис 6.25)
(рис 6.26)
Разработать программу на языке С++ для решения следующей задачи.
Разработать программу на языке С++ для решения следующей задачи.
Решить матричное уравнение $$X(A + E) = 3B - E$$, где $$E$$ — единичная матрица.
Решить матричное уравнение $$(2A - E)X = B + E$$, где $$E$$ — единичная матрица.
Если образуют, то найти координаты вектора $$x=[1\ -1\ 3\ -1]^T$$ в этом базисе. Для решения задачи необходимо показать, что определитель матрицы $$F$$ со столбцами $$f_1, f_2, f_3, f_4$$ отличен от нуля, а затем вычислить координаты вектора $$x$$ в новом базисе по формуле $$y=F^{-1}\cdot x$$
.Если образуют, то найти координаты вектора $$x = [1\ 1\ 1\ 1]^T$$ в этом базисе. Для решения задачи необходимо показать, что определитель матрицы F со столбцами $$f_1, f_2, f_3, f_4$$ отличен от нуля, а затем вычислить координаты вектора $$x$$ в новом базисе, решив СЛАУ $$F\cdot y=x$$.
Решить матричное уравнение $$G+E)\cdot X=5B^T-E$$, где $$E$$ — единичная матрица.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.