根据此处给出的算法https://en.wikipedia.org/wiki/Gaussian_elimination#Pseudocode,
我试图实现高斯消除的C#版本:
public Matrix GaussianElimination(double epsilon = 1e-10)
{
var result = Copy();
var kMax = Math.Min(result.RowCount, result.ColumnCount);
for (var k = 0; k < kMax; k++)
{
// Find k-th pivot, i.e. maximum in column max
var iMax = result.FindColumnAbsMax(k);
if (Math.Abs(result[iMax, k]) < epsilon)
{
throw new ArithmeticException("Matrix is singular or nearly singular.");
}
// Swap maximum row with current row
SwapRows(k, iMax);
// Make all rows below the current one, with 0 in current column
for (var i = k + 1; i < result.RowCount; i++)
{
var factor = result[i, k] / result[k, k];
for (var j = k + 1; j < result.ColumnCount; j++)
{
result[i, j] = result[i, j] - result[k, j] * factor;
}
result[i, k] = 0;
}
}
return result;
}
在大多数情况下都适用(请注意,这不执行后退替换步骤)。但是,对于维基百科中给出的第一个示例,算法停止在奇异矩阵的情况下,并在返回替换步骤之前在行梯形表上抛出相关的异常:
public class Program
{
public static void Main(string[] args)
{
var matrix = Matrix.Parse("[1 3 1 9; 1 1 -1 1; 3 11 5 35]");
Console.WriteLine(matrix);
Console.WriteLine(matrix.GaussianElimination());
Console.ReadKey();
}
}
[编辑] Matrix实现有点冗长,但这里是字符串解析和消除方法的用法:
public class Matrix
{
public const string MatrixStart = "[";
public const string MatrixStop = "]";
public const char RowSeparator = ';';
public const char RowCellSeparator = ' ';
public int RowCount { get; }
public int ColumnCount { get; }
public int CellCount => _data.Length;
public bool IsSquare => RowCount == ColumnCount;
public bool IsVector => ColumnCount == 1;
private readonly double[] _data;
public double this[int rowIndex, int columnIndex]
{
get => _data[Convert2DIndexTo1DIndex(rowIndex, columnIndex)];
set => _data[Convert2DIndexTo1DIndex(rowIndex, columnIndex)] = value;
}
public Matrix(Matrix matrix)
: this(matrix.RowCount, matrix.ColumnCount)
{
for (var i = 0; i < _data.Length; i++)
{
_data[i] = matrix._data[i];
}
}
public Matrix(int size, double placeHolder = 0)
: this(size, size, placeHolder)
{
}
public Matrix(int rowCount, int columnCount, double initializationValue = 0)
{
if (rowCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(rowCount));
}
if (columnCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(columnCount));
}
RowCount = rowCount;
ColumnCount = columnCount;
_data = new double[RowCount * ColumnCount];
if (Math.Abs(initializationValue) > 0)
{
Set(initializationValue);
}
}
public Matrix(int rowCount, int columnCount, double[] initializationValues)
{
if (rowCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(rowCount));
}
if (columnCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(columnCount));
}
if (initializationValues.Length != rowCount * columnCount)
{
throw new ArgumentOutOfRangeException(nameof(initializationValues));
}
RowCount = rowCount;
ColumnCount = columnCount;
_data = new double[initializationValues.Length];
initializationValues.CopyTo(_data, 0);
}
private int Convert2DIndexTo1DIndex(int rowIndex, int columnIndex)
{
return rowIndex * ColumnCount + columnIndex;
}
public void Set(double value)
{
for (var i = 0; i < _data.Length; i++)
{
_data[i] = value;
}
}
public Matrix Transpose()
{
var result = new Matrix(ColumnCount, RowCount);
for (var i = 0; i < result.RowCount; i++)
{
for (var j = 0; j < result.ColumnCount; j++)
{
result[i, j] = this[j, i];
}
}
return result;
}
public Matrix Copy()
{
return new Matrix(this);
}
private int FindColumnAbsMax(int index)
{
return FindColumnAbsMax(index, index);
}
private int FindColumnAbsMax(int rowStartIndex, int columnIndex)
{
var maxIndex = rowStartIndex;
for (var i = rowStartIndex + 1; i < RowCount; i++)
{
if (Math.Abs(this[maxIndex, columnIndex]) <= Math.Abs(this[i, columnIndex]))
{
maxIndex = i;
}
}
return maxIndex;
}
public void SwapRows(int rowIndexA, int rowIndexB)
{
for (var j = 0; j < ColumnCount; j++)
{
var indexA = Convert2DIndexTo1DIndex(rowIndexA, j);
var indexB = Convert2DIndexTo1DIndex(rowIndexB, j);
_data.Swap(indexA, indexB);
}
}
public void SwapColumns(int columnIndexA, int columnIndexB)
{
for (var i = 0; i < RowCount; i++)
{
var indexA = Convert2DIndexTo1DIndex(i, columnIndexA);
var indexB = Convert2DIndexTo1DIndex(i, columnIndexB);
_data.Swap(indexA, indexB);
}
}
public static Matrix Parse(string matrixString)
{
if (!matrixString.StartsWith(MatrixStart) || !matrixString.EndsWith(MatrixStop))
{
throw new FormatException();
}
matrixString = matrixString.Remove(0, 1);
matrixString = matrixString.Remove(matrixString.Length - 1, 1);
var rows = matrixString.Split(new[] { RowSeparator }, StringSplitOptions.RemoveEmptyEntries);
if (rows.Length <= 0)
{
return new Matrix(0, 0);
}
var cells = ParseRow(rows[0]);
var matrix = new Matrix(rows.Length, cells.Length);
for (var j = 0; j < cells.Length; j++)
{
matrix[0, j] = cells[j];
}
for (var i = 1; i < matrix.RowCount; i++)
{
cells = ParseRow(rows[i]);
for (var j = 0; j < cells.Length; j++)
{
matrix[i, j] = cells[j];
}
}
return matrix;
}
private static double[] ParseRow(string row)
{
var cells = row.Split(new [] { RowCellSeparator }, StringSplitOptions.RemoveEmptyEntries);
return cells.Select(x => Convert.ToDouble(x.Replace(" ", string.Empty))).ToArray();
}
}
用于交换两个项目的1D Array扩展:
public static class ArrayHelpers
{
public static void Swap<TSource>(this TSource[] source, int indexA, int indexB)
{
var tmp = source[indexA];
source[indexA] = source[indexB];
source[indexB] = tmp;
}
}