Решение систем линейных уравнений методом Гаусса

Наиболее рапространённым методом решения систем линейных уравнений является метод Гаусса с частичным выбором ведущего элемента по столбцам. Первый вариант этого метода здесь реализован в виде класса SLU_Gauss, написанного на основе двух фортран-программ DECOMP и SOLVE из книги "Машинные методы математических вычислений" (Дж.Форсайт, М.Малькольм, К.Моулер). Этот класс позволяет вычислить определитель матрицы, оценить её число обусловленности, решить систему уравнений. Для того, чтобы найти обратную матрицу нужно n раз применить метод solve к соответствующему столбцу единичной матрицы, при этом будет получаться столбец обратной матрицы.

typedef nat int nat;

class SLU_Gauss
{
    const nat n;
    DynArray<nat> ipvt;
    DynArray2<double> a;
    double cond;
// Запрет конструктора копии и оператора присваивания:
    SLU_Gauss ( SLU_Gauss & );
    void operator = ( SLU_Gauss & );
public:
    SLU_Gauss ( nat k, const double * const * a );
    bool solve ( const double * b, double * x ) const; // b[n], x[n]
    bool solve ( const double * const * a, const double * b, double * x ) const;
    double condition () const { return cond; }
    double determinant () const;
};

Конструктор класса приводит исходную матрицу к треугольному виду и приблизительно вычисляет число обусловленности, которое здесь определено, как произведение 1-нормы матрицы A ( вычисляется явно ) на 1-норму матрицы A-1 ( оценивается приближённо ). Если число обусловленности очень большое, т.е. cond + 1 равен cond, то решать такую систему смысла нет и в этом случае методы solve возвращают значение false. Заметим, что в качестве аргументов для методов solve могут быть указатели на один и тот же массив ( b и x ). Метод solve с двумя параметрами просто решает систему уравнений, а метод solve с тремя параметрами ( первый из которых - это указатель на исходную матрицу ) вначале вызывает solve с двумя параметрами, а затем уточняет результат уменьшая квадратичную норму невязки. В моих экспериментах заметный эффект от уточнения наблюдался в редких случаях.

Второй вариант реализации метода Гаусса в отличии от первого не использует операторы new-delete:

bool slu_gauss ( ArrRef2<double> data ); // несколько правых столбцов
bool slu_gauss ( ArrRef2<double> data, double * x ); // один правый столбец

Здесь квадратная матрица и столбцы свободных членов записаны в один двухмерный массив data, который в процессе вычислений изменяется, и на месте правых столбцов будут находится столбцы с решениями.

Исходники находятся в файле mathem.cpp.

Следущий вариант метода Гаусса реализован в виде шаблонов:

//*************************** 19.02.2017 ******************************//
//
//      Метод исключений Гаусса.
//      Выбор ведущего элемента по строкам.
//      nRow, nCol - размеры матрицы.
//      index - массив для индексов выбранных столбцов.
//      mRow, mCol - размеры подматрицы, где выбираются ведущие элементы.
//
//*************************** 19.02.2017 ******************************//

template <class Matrix>
bool _doGaussRow ( Matrix & data, nat nRow, nat nCol, nat index[], nat mRow, nat mCol )
{
    if ( nRow == 0 || nCol <= nRow ) return false;
    if ( mRow > nRow ) mRow = nRow;
    if ( mCol > nCol ) mCol = nCol;
    if ( mRow > mCol ) return false;
// Прямой ход
    nat i, j, k;
    for ( k = 0; k < mRow; ++k )
    {
// Поиск максимального по модулю члена в k-ой строке
        nat m = 0;
        double * rk = & data[k][0];
        double max = fabs ( rk[0] );
        for ( i = 1; i < mCol; ++i )
        {
            const double t = fabs ( rk[i] );
            if ( max < t ) max = t, m = i;
        }
        if ( max == 0 ) return false;
        index[k] = m;
// Нормализация строки
        const double p = 1. / rk[m];
        for ( i = 0; i < nCol; ++i ) rk[i] *= p;
        rk[m] = 1;
// Вычитание строк
        for ( j = k+1; j < nRow; ++j )
        {
            double * rj = & data[j][0];
            const double t = rj[m];
            for ( i = 0; i < nCol; ++i )
            {
                if ( fabs ( rj[i] -= rk[i] * t ) < 1e-290 ) rj[i] = 0;
            }
        }
    }
// Обратная подстановка
    for ( j = mRow; --j > 0; )
    {
        const double * rj = & data[j][0];
        for ( i = 0; i < j; ++i )
        {
            double * ri = & data[i][0];
            const double t = ri[index[j]];
            for ( k = 0; k < nCol; ++k ) ri[k] -= rj[k] * t;
        }
    }
    return true;
}

template <class T> class IMatrix;

template <class T> inline
bool doGaussRow ( IMatrix<T> & data, nat nRow, nat nCol, nat * index, nat mRow, nat mCol )
{
    return _doGaussRow ( data, nRow, nCol, index, mRow, mCol );
}

template <class T> class ArrRef2;

template <class T> inline
bool doGaussRow ( ArrRef2<T> & data, nat nRow, nat nCol, nat * index, nat mRow, nat mCol )
{
    return _doGaussRow ( data, nRow, nCol, index, mRow, mCol );
}

Исходники находятся в файле LinAlg.h.

Наверх