행렬 예제 프로그램
이번에는 DirectXMath에 포함되어있는 행렬 함수들을 라이브러리를 사용하지 않고 직접 코딩해서 작성해보았다.
2개의 클래스가 있다.
첫 번째 MATRIX 클래스는 1개의 행렬을 내부적으로 double **형으로 저장해서 관리하는 클래스이고,
Matrix.h)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | /* 1개의 행렬을 double **형으로 관리하는 행렬 클래스이다. Matrix.h */ #pragma once #include <iostream> #include <stdarg.h> using namespace std; class MATRIX { private: int row, col; // 이 행렬의 행과 열이다. double **matrix; // 이 행렬의 원소를 가지게 되는 행렬 자체이다. public: // 아무 인자없이 생성자로 생성하면 row와 col이 1로 초기화되어 할당된다. MATRIX() { row = col = 1; Allocator(); } // 행과 열을 입력하여 생성하면 그에 맞는 크기로 할당된다. MATRIX(int _row, int _col) : row(_row), col(_col) { Allocator(); } // double ** 형을 입력하면 그것을 행렬로 간주하고 저장한다. MATRIX(double ** _matrix) { if (_matrix != nullptr) matrix = _matrix; else cout << "유효하지 않은 행렬을 입력했습니다. 행렬 생성 실패" << endl; } // double ** 형 + 행 갯수 + 열 갯수 생성자 MATRIX(double ** _matrix, int _row, int _col) { if (_matrix != nullptr) { matrix = _matrix; row = _row; col = _col; } else cout << "유효하지 않은 행렬을 입력했습니다. 행렬 생성 실패" << endl; } void SetDemension(int _row, int _col); // 행렬의 차원을 설정하는 메소드 void Allocator(); // m x n의 행렬과 전치행렬을 동적할당하는 메소드 void Deallocator(); // 동적할당을 해제하는 메소드(사용 후 반드시 호출해야함) void PrintMatrix(); // 행렬의 원소를 출력하는 메소드 inline int GetRow(){return row;} // 행을 리턴하는 메소드 inline int GetCol(){return col;}; // 열을 리턴하는 메소드 inline void SetRow(int _row){row = _row;} // 행을 셋팅하는 메소드 inline void SetCol(int _col){col = _col;} // 열을 셋팅하는 메소드 bool SetElements(int _args, ...); // 원소를 셋팅하는 메소드(총 갯수로 따짐) double ** GetMatrix(); // 행렬을 얻음 bool SetMatrix(double ** _matrix); // 행렬을 셋팅함 }; | cs |
Matrix.cpp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | /* Matrix 클래스 정의 Matrix.cpp */ #include "Matrix.h" void MATRIX::Allocator() { if (matrix == nullptr) { // m x n의 배열을 메모리 동적할당 matrix = new double*[row]; for (int i = 0; i < row; i++) { matrix[i] = new double[col]; memset(matrix[i], 0, sizeof(double) * col); } } } void MATRIX::Deallocator() { if (matrix != nullptr) { // 메모리 동적할당 해제 for (int i = 0; i < row; i++) delete[]matrix[i]; delete[]matrix; matrix = nullptr; } } void MATRIX::PrintMatrix() { // 할당이 되어있어야만 출력이 가능 if (matrix != nullptr) { for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { cout << matrix[i][j] << "\t"; } cout << endl; } cout << endl; } } void MATRIX::SetDemension(int _row, int _col) { // 할당된게 이미 있다면 할당을 해제한다. if (matrix != nullptr) Deallocator(); // m, n 변수를 새로운 값으로 대체하고 row = _row; col = _col; // 새롭게 할당한다. Allocator(); } bool MATRIX::SetElements(int _args, ...) { double arg; // 원소의 갯수와 행렬의 차원이 같아야함 if (row * col != _args) { cout << "원소 입력 실패" << endl; return false; } va_list ap; // 가변 인자용 변수 ap va_start(ap, _args); // 첫 번째 가변인수를 가리키도록 셋팅 for (int k = 0, i = 0, j = 0; k < _args; k++, j++) { // 해당 행의 열을 다 채우면 다음 행을 채워야함 if (j == col) { j = 0; i++; } // 실제로 동적할당 배열에 저장 arg = va_arg(ap, double); matrix[i][j] = arg; } va_end(ap); // 뒷정리 cout << "원소 입력 성공" << endl; return true; } double ** MATRIX::GetMatrix() { return matrix; } bool MATRIX::SetMatrix(double ** _matrix) { if (_matrix == nullptr) return false; else { matrix = _matrix; return true; } } | cs |
두 번째 MATRIX_MANAGER 클래스는 static 메소드의 집합 클래스로, 행렬 연산에 필요한 메소드를 제공하는 클래스이다.
MatrixManager.h)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /* 행렬에 관련된 메소드를 제공하는 클래스이다.(선언) MatrixManager.h */ #pragma once #include <iostream> #include "Matrix.h" #define FIX 0 // 행렬식 공식에서 고정되는 인덱스 #define ERROR_CODE -99999 // 행렬식 에러코드 // 2x2 소행렬의 행렬식을 구하는 매크로 함수 #define TwoByTwo(matrix) ( ( (matrix[0][0]) * (matrix[1][1]) ) - ( (matrix[0][1]) * (matrix[1][0]) ) ); using namespace std; class MATRIX_MANGER { public: static double GetDeterminant(MATRIX _matrix); // 4x4 이하의 행렬식을 구하는 메소드 static MATRIX GetInverseMatrix(MATRIX _matrix); // 역행렬을 구하는 메소드 static MATRIX GetTransposeMatrix(MATRIX _matrix); // 전치행렬을 만들고 리턴하는 메소드(MATRIX 클래스 버전) static double ** GetTransposeMatrix(double ** _matrix, int _row, int _col); // 전치행렬을 만들고 리턴하는 메소드(double ** 버전) 사용 후에는 메모리 누수 방지를 위해 꼭 CustomDeallocator()를 사용해야한다. ////// 1번째인자:저장시킬 소행렬, 2번째:기준이 되는 원래 행렬식, 3번째:정방행렬 차원, 4번째:소거할 열 static void GetMinorMatix(double **_minor, double **_matrix, int _n, int _removeRow, int _removeCol); // 소행렬을 구하는 메소드 static MATRIX GetMultiplyMatrix(MATRIX _m1, MATRIX _m2); // 두 행렬의 곱을 리턴하는 메소드 static MATRIX GetIdentity(int _demension); // 인자로 넘어온 차원의 단위행렬을 리턴하는 메소드 static bool IsIdentity(MATRIX _matrix); // 단위 행렬인지 static double ** CustomAllocator(int _row, int _col); // 원하는 크기대로 동적할당시킬 메소드 static void CustomDeallocator(double **_matrix, int _demension); // 커스텀 동적할당 해제시킬 메소드 }; | cs |
MatrixManager.cpp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | /* MatrixManager의 정의 (행렬에 필요한 메소드를 정의) MatrixManager.cpp */ #include "MatrixManager.h" MATRIX MATRIX_MANGER::GetTransposeMatrix(MATRIX _matrix) { // 전치행렬은 원래 행렬의 행과 열의 갯수가 다르다. double ** transpose = MATRIX_MANGER::CustomAllocator(_matrix.GetCol(), _matrix.GetRow()); double ** matrix = _matrix.GetMatrix(); for (int i = 0; i < _matrix.GetRow(); i++) { for (int j = 0; j < _matrix.GetCol(); j++) { transpose[j][i] = matrix[i][j]; // 전치행렬은 원래행렬의 행과 열을 바꾼것과 같다. } } // 전치행렬은 행과 열이 바뀜에 주의하자 MATRIX ret(transpose, _matrix.GetCol(), _matrix.GetRow()); return ret; } double ** MATRIX_MANGER::GetTransposeMatrix(double ** _matrix, int _row, int _col) { // 전치행렬은 원래 행렬의 행과 열의 갯수가 다르다. double ** transpose = MATRIX_MANGER::CustomAllocator(_col, _row); for (int i = 0; i < _row; i++) { for (int j = 0; j < _col; j++) { transpose[j][i] = _matrix[i][j]; // 전치행렬은 원래행렬의 행과 열을 바꾼것과 같다. } } return transpose; // 사용 후에는 반드시 CustomDeallocator()를 사용해야함! } double MATRIX_MANGER::GetDeterminant(MATRIX _matrix) { // 4x4를 초과하는 행렬은 허용하지 않는다 if (_matrix.GetRow() != _matrix.GetCol() || (_matrix.GetRow() > 4)) return ERROR_CODE; double det = 0; // 실제 리턴할 행렬식 값 switch (_matrix.GetRow()) { // 2x2의 행렬식은 ad-bc로 정의된다. case 2: det = TwoByTwo(_matrix.GetMatrix()); break; // 3x3의 행렬식은 재귀적으로 2x2행렬의 집합으로 이루어진다. case 3: { double **matrix = _matrix.GetMatrix(); double **minor = nullptr; // 소행렬 bool sign = true; // 부호는 +,-,+,-가 반복된다. double val = 0; for (int i = 0; i < _matrix.GetRow(); i++) { minor = CustomAllocator(2, 2); // 소행렬을 위해 2x2 크기로 동적할당 GetMinorMatix(minor, matrix, _matrix.GetRow(), FIX, i); // 소행렬을 얻어서 minor에 저장시킴 val += (sign ? 1 : -1) * matrix[FIX][i] * TwoByTwo(minor); // 부호 곱하기 2x2행렬 공식인 ad-bc * 해당원소를 넣어주면 된다. // 아까 동적할당한 자원을 해제시킴 if (minor != nullptr) CustomDeallocator(minor, 2); sign = !sign; // 부호 스왑 } det = val; } break; // 4x4를 초과하는 행렬은 가우스소거법 같은 방법이 더 효율적이다. case 4: { double **matrix = _matrix.GetMatrix(); double **minor1 = nullptr; // 3x3 행렬식 double **minor2 = nullptr; // 2x2 행렬식 bool sign1 = true; // 3x3행렬식의 부호는 +,-,+,-가 반복된다. bool sign2 = true; // 2x2행렬식의 부호 double val[4];// 각 소행렬의 값을 저장할 배열 (4x4행렬식은 4개의 3x3행렬식으로 이루어짐) memset(val, 0, sizeof(double) * 4); // 값 초기화 for (int i = 0; i < _matrix.GetRow(); i++, sign2 = true) { minor1 = CustomAllocator(3, 3); // 소행렬을 위해 3x3 크기로 동적할당 GetMinorMatix(minor1, matrix, _matrix.GetRow(), FIX, i); // 소행렬을 얻어서 minor1에 저장시킴 for (int j = 0; j < _matrix.GetRow() - 1; j++) { // 방금 위에서 얻은 행렬식의 행렬식을 구한다(재귀적인 성질) minor2 = CustomAllocator(2, 2); // 소행렬을 위해 2x2 크기로 동적할당 GetMinorMatix(minor2, minor1, _matrix.GetRow() - 1, FIX, j); val[i] += (sign2 ? 1 : -1) * minor1[FIX][j] * TwoByTwo(minor2); // 행렬식의 공식 적용 sign2 = !sign2; // 부호 스왑 // 2x2 소행렬 할당 해제 if (minor2 != nullptr) CustomDeallocator(minor2, 2); } val[i] *= (sign1 ? 1 : -1) * matrix[FIX][i]; // 3x3행렬식의 부호*해당원소의 값을 곱해서 방금 나온 값에 더해줌 // 3x3 소행렬 할당 해제 if (minor1 != nullptr) CustomDeallocator(minor1, 3); sign1 = !sign1; // 부호 스왑 } // 나온 값을 모~두 더해줌 for (int i = 0; i < 4; i++) det += val[i]; } break; } return det; // 계산해서 나온 행렬식 리턴 } void MATRIX_MANGER::GetMinorMatix(double **_minor, double **_matrix, int _n, int _removeRow, int _removeCol) { // x=minor의 행, y=minor의 열 int x, y; bool checker = false; // 조건문에 들어갔는지 검사용 x = y = 0; for (int i = 0; i < _n; i++) { // i행을 소거해야할 때는 그냥 무시 if (i == _removeRow) continue; for (int j = 0; j < _n; j++) { // j열을 소거해야할 때에도 무시 if (j == _removeCol) continue; // matrix의 실직적인 소행렬 minor를 저장함 else { checker = true; _minor[x][y++] = _matrix[i][j]; } } // 소행렬을 다음 행, 첫 열로 이동 if (checker == true) { x++; y = 0; } } } double ** MATRIX_MANGER::CustomAllocator(int _row, int _col) { double ** matrix = nullptr; // m x n의 배열을 메모리 동적할당 matrix = new double*[_row]; for (int i = 0; i < _row; i++) { matrix[i] = new double[_col]; memset(matrix[i], 0, sizeof(double) * _col); } return matrix; } void MATRIX_MANGER::CustomDeallocator(double **_matrix, int _demension) { if (_matrix != nullptr) { // 메모리 동적할당 해제 for (int i = 0; i < _demension; i++) delete[]_matrix[i]; delete[]_matrix; _matrix = nullptr; } } MATRIX MATRIX_MANGER::GetInverseMatrix(MATRIX _matrix) { // 역행렬은 정방행렬에만 있다. if (_matrix.GetRow() != _matrix.GetCol()) { cout << "정방행렬이 아니므로 역행렬을 구할 수 없습니다." << endl; return NULL; } // 행렬식값이 0이면 특이행렬(역행렬이 없음) if (GetDeterminant(_matrix) == 0) { cout << "가역행렬이 아닙니다." << endl; return NULL; } // 4x4미만의 행렬만 역행렬을 구한다. if (_matrix.GetRow() > 4 || _matrix.GetCol() > 4) { cout << "4x4미만의 행렬만 가능합니다." << endl; return NULL; } double **inverse = CustomAllocator(_matrix.GetRow(), _matrix.GetCol()); // 일단 역행렬을 위한 할당 double **minor = nullptr; // 소행렬을 위한 배열 double val = 0; // 행렬식 값을 임시로 저장하기 위한 변수 bool sign = true; // 부호를 + - + - 로 활용하기 위한 변수 // 행, 열의 갯수만큼 반복문을 돌려서 for (int i = 0; i < _matrix.GetRow(); i++) { for (int j = 0; j < _matrix.GetCol(); j++) { minor = CustomAllocator(_matrix.GetRow() - 1, _matrix.GetCol() - 1); // 소행렬을 위해 (row-1)x(col-1) 크기로 동적할당 GetMinorMatix(minor, _matrix.GetMatrix(), _matrix.GetRow(), i, j); // 소행렬을 얻어서 minor에 저장시킴(소거행열은 i,j) MATRIX sudDet(minor, _matrix.GetRow() - 1, _matrix.GetCol() - 1); // 아까 얻은 소행렬을 MATRIX 인스턴스로 만듦 // (i+1)+(j+1)가 (공식으로는 i+j이지만 인덱스는 0부터 시작하므로) 짝 수 이면 부호가 양수이고, 홀 수 이면 음수가 된다. if (((i + 1) + (j + 1)) % 2 == 0) sign = true; else sign = false; // 부호 * 소행렬의 행렬식값을 곱해준다 val = (sign == true ? 1 : -1) * GetDeterminant(sudDet); // 0에 마이너스 부호 붙는 것 방지 if (val == 0.0) val = 0; inverse[i][j] = val; // 이를 역행렬 배열에 저장한다. // 사용한 자원은 반납해야 메모리 누수가 없다. if (minor != nullptr) CustomDeallocator(minor, _matrix.GetRow() - 1); } } // 위에서 만든 여인수행렬을 전치행렬화 하여 딸림행렬로 만든다.(리턴값이 딸림행렬이다) inverse = MATRIX_MANGER::GetTransposeMatrix(inverse, _matrix.GetRow(), _matrix.GetCol()); // 역행렬은 원래행렬의 행렬식(det) 값을 딸림행렬의 각 요소에 나눠주면 된다. double det = MATRIX_MANGER::GetDeterminant(_matrix); for (int i = 0; i < _matrix.GetRow(); i++) for (int j = 0; j < _matrix.GetCol(); j++) inverse[i][j] /= det; // 방금 만든 역행렬(double **형)을 MATRIX 인스턴스로 만들어서 리턴한다. MATRIX MatInverse(inverse, _matrix.GetRow(), _matrix.GetCol()); return MatInverse; } MATRIX MATRIX_MANGER::GetMultiplyMatrix(MATRIX _m1, MATRIX _m2) { // A행렬의 열의 갯수와 B행렬의 행의 갯수가 일치하지 않으면 곱셈은 성립하지 않는다. if (_m1.GetCol() != _m2.GetRow()) { cout << "곱셈이 정의되지 않습니다." << endl; return NULL; } // 곱셈이 성립할 때 생성되는 행렬의 행과 열의 수 int row = _m1.GetRow(); int col = _m2.GetCol(); double **mat1 = _m1.GetMatrix(); // 행렬 A의 double **형 double **mat2 = _m2.GetMatrix(); // 행렬 B의 double **형 double **ret = MATRIX_MANGER::CustomAllocator(row, col); // 곱셈으로 생성되는 행렬은 (A의 행 x B의 열) 차원이다. double val = .0; // 곱한 결과를 저장할 변수 int x, y; // 결과 행렬의 인덱스 x = y = 0; // 행렬의 곱 순서대로 곱한 결과를 ret에 저장한다. // A = m x n, B = n x p 일 때, C = m x p 와 같다. for (int i = 0, k = 0; i < _m1.GetRow(); k++, val = 0) { for (int j = 0; j < _m2.GetRow(); j++) { val += mat1[i][j] * mat2[j][k]; // 곱한 결과를 저장해둔다. } // 곱한 결과를 C(결과)행렬에 저장하고, 다음 인덱스를 넘어가야하면 조건에 맞게 처리한다. ret[x][y++] = val; if (y == col) { x++; // 다음 행 y = 0; // 첫 열로 k = -1; // B는 다시 처음처럼 곱셈을 해야하므로(바로 for문 조건으로 인해 1 증가하므로 -1이다) i++; // A를 다음 행으로 } } // 결과를 MATRIX로 만들어 리턴 MATRIX matRet(ret, row, col); return matRet; } MATRIX MATRIX_MANGER::GetIdentity(int _demension) { // 파라미터 차원의 행렬을 생성한다. double **ret = CustomAllocator(_demension, _demension); // 단위행렬을 만들고 for (int i = 0; i < _demension; i++) ret[i][i] = 1.0; // 리턴한다. MATRIX matRet(ret, _demension, _demension); return matRet; } bool MATRIX_MANGER::IsIdentity(MATRIX _matrix) { if (_matrix.GetRow() != _matrix.GetCol()) return false; double **matrix = _matrix.GetMatrix(); for (int i = 0; i < _matrix.GetRow(); i++) { for (int j = 0; j < _matrix.GetCol(); j++) { // main diagonal일 때 1이 아니면 단위행렬이 아니다. if (i == j) { if (matrix[i][j] != 1) return false; else continue; } // 그 외에는 모두 0이어야 한다. if (matrix[i][j] != 0) return false; } } return true; } | cs |
main 함수에서 행렬을 설정하고, 단위행렬을 얻고, 곱셈, 전치행렬, 행렬식, 역행렬 등을 구한다.
main.cpp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | /* 행렬 예제 프로그램 main.cpp */ #include <iostream> #include "MatrixManager.h" #include "Matrix.h" using namespace std; int main() { MATRIX A(4, 4); // 4x4 크기의 행렬 A를 생성한다. // 원소를 채운다. A.SetElements(16, 1.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 1.0, 2.0, 3.0, 1.0); MATRIX B = MATRIX_MANGER::GetIdentity(A.GetRow()); // 단위 행렬을 얻는다. MATRIX C = MATRIX_MANGER::GetMultiplyMatrix(A, B); // A와 B를 곱해서 나온 결과 행렬을 C에 저장한다. MATRIX D = MATRIX_MANGER::GetTransposeMatrix(A); // A행렬의 전치행렬을 얻어서 D에 저장한다. double det = MATRIX_MANGER::GetDeterminant(A); // A의 행렬식을 구해서 det에 저장한다. MATRIX E = MATRIX_MANGER::GetInverseMatrix(A); // A의 역행렬을 구해서 E에 저장한다. MATRIX F = MATRIX_MANGER::GetMultiplyMatrix(A, E); // A와 E를 곱한다. // 연산 결과를 출력한다. cout << "A = " << endl; A.PrintMatrix(); cout << endl; cout << "B = " << endl; B.PrintMatrix(); cout << endl; cout << "C = A*B = " << endl; C.PrintMatrix(); cout << endl; cout << "D = transpose(A) = " << endl; D.PrintMatrix(); cout << endl; cout << "det = determinant(A) = " << det << endl << endl; cout << "E = inverse(A) = " << endl; E.PrintMatrix(); cout << endl; cout << "F = A*E = " << endl; F.PrintMatrix(); cout << endl; // 메모리를 반납한다. A.Deallocator(); B.Deallocator(); C.Deallocator(); D.Deallocator(); E.Deallocator(); F.Deallocator(); return 0; } | cs |
실행화면)
원리를 생각하면서 한 번이라도 코딩해보는게 많은 도움이 되는 것 같다.
잘못된 점은 따끔히 지적해주시고, 더 좋은 의견이 있으시면 언제든지 덧글로 남겨주세요.