5. Sparse matices
0) 정의
1> sparse matrix : 원소의 다수가 0을 갖는 행렬.
2> 행렬을 2차원 배열로 표현할 때, 저장 공간의 낭비가 있다.
3> 선언 : arr[MAX_ROWS][MAX_COLS]
4> 접근 : arr[i][j] (행렬의 i행 j열에 접근)
1) ADT
ADT Spase_Matrix
1> object : <row, column, value> (3원소 쌍의 집합)
- value는 item 집합의 원소이다.
2> function
- Create(max_row, max_col) : maxItems(=max_rowXmax_col)개를 저장할 수 있는 sparse marix return
(최대 max_row개의 행과 max_col개의 열을 저장할 수 있는 sparse matix return)
- Transpose(a) : 3원소 쌍에서 row와 column을 모두 뒤바꿔 만든 matrix
- Add(a, b) : [1] a와 b의 dimension 같은지 확인 -> [2] row와 column이 같은 item들을 합한다.
- Multiply(a, b) : [1] a의 col==b의 row 확인 -> [2] d(i, j) = ∑ a(i, k)·b(k, j)으로 생성된 d가 원소인 matrix
2) Sparse Matrix Representation
(create 함수를 효과적으로 구현해보기 위해 명심할 것)
1> 3원소 쌍(row, col, value)의 array를 사용한다.
2> row의 index를 오름차순이 되도록 정렬한다.
3> 특정 row에 대한 3원소 쌍들은 column의 index가 오름차순이 되도록 정렬한다.
4> 연산의 종료를 보장하기 위해 행의 개수, 열의 개수, 0이 아닌 원소의 개수를 알아야 한다.
5> Create 함수
Sparse_Matrix Create(max_row, max_col)
{
#define MAX_TERMS 101 /*maximum number of terms +1*/
typedef struct {
int col;
int row;
int value;
} term;
term a[MAX_TERMS];
}
- MAX_TERM = 최대 원소의 개수 + 1
- 전체 행의 개수, 전체 열의 개수, 0이 아닌 원소의 개수를 저장할 공간을 하나 더 만들기 위해 1을 더함
6> sparse matrix 형태
3) Transposing a Matrix
1> simple algorithm : <i, j, value>를 <j, i, value>로 바꿔야 한다.
2> 문제
- row index 별로 transpose하면
- row가 우선으로 정렬되는 특성상
- transpose된 matrix는 col이 작은 것부터 생성되므로
- 정확히 (구조체의) 몇 번째 index에 저장해야 할 지 정확히 알 수 없다.
3> 해결
- column index를 기준으로 transpose를 진행한다.
- for all element in column j, place element <i, j, value> in element <j, i, value>
4> Transpose 함수
- [0] 0번째 index에 들어있는 행의 개수, 열의 개수 할당하고 0이 아닌 값의 개수는 교환
- [1] a의 col 개수만큼 loop 만들기 => col 오름차순대로 시행하게 함
- [2] a의 원소 개수만큼 loop 만들기 => a의 모든 원소에 대해 transpose할 수 있게 한다
- [3] 그래서 'a[j].col==i' 일 때마다 transpose => a의 col이 작은 것부터 transpose
void transpose (term a[], term b[])
{/*b is set to the transpose of a */
int n, i, j, currentb;
n = a[0].value; /*total number of elements*/
b[0].row = a[0].col; /*rows in b = columns in a*/
b[0].col = a[0].row; /*columns in b = rows in a*/
b[0].value = n;
if (n > 0) {
/* nonzero matrix */
currentb = 1;
for (i = 0; i < a[0].col; i++) /* 0번째 col부터 max_col까지 시도 */
for (j = 0; j <= n; j++) /* a의 원소 개수 n번 만큼 시도*/
if (a[j].col == i) { /* i번째 부터 (오름차순) transpose 연산 시도 */
b[currentb].row = a[j].col;
b[currentb].col = a[j].row;
b[currentb].value = a[j].value;
currentb++
}
}
}
- Time complexity = O(column*elements) <2개의 for loop 곱한다.>
- If elements = rows·columns, (모든 원소가 0이 아닌 경우)
Time complexity = O(column*elements)=O(rows*columns^2)
5> fast transpose 예시
- row_terms[MAX_COL] : col index 별로 몇 개의 nonzero element가 있는지 array 형태로 기록
- starting_pos[MAX_COL] : col index 별로 transpose 후에 b의 몇 번째 index부터 각 element를 기록할지
- [0] 적절한 값 할당
- [1] row_terms array를 일단 col 존재하는 것 0으로 초기화
- [2] row_terms array에 index 별로 row_terms 값 저장
- [3] starting_pos 초기화 : 0번째 index에 1 할당
- [4] starting_pos 값 할당 : 이 방식으로 자리 확보 => starting_pos[i]=starting_pos[i-1]+row_terms[i-1]
- [5] tranpose
#include <stdio.h>
#include <stdlib.h>
#define MAX_COLS 101
typedef struct{
int col;
int row;
int value;
}term;
void fast_transpose(term a[], term b[])
{ /* the transpose of a is placed in b */
int row_terms[MAX_COL]; // col index 별로 몇 개의 nonzero element가 있는지
int starting_pos[MAX_COL]; // col index 별로 transpose 후 b에 저장된 위치를 알려주는 array
int i, j;
int num_cols = a[0].col;
int num_terms = a[0].value;
b[0].row = num_cols;
b[0].col = a[0].row;
b[0].value = num_terms;
if (num_terms > 0) { /* nonzero matrix */
for (i = 0; i < num_cols; i++) row_terms[i] = 0; // row_terms 초기화
for (i = 1; i <= num_terms; i++) row_terms[a[i].col]++; // index 별로 row_terms 값 저장
starting_pos[0] = 1;
for (i = 1; i < num_cols; i++)
starting_pos[i] = starting_pos[i - 1] + row_terms[i - 1]; // col별 원소 개수 만큼 여유 공간 확보
for (i = 1; i <= num_terms; i++) {
j = starting_pos[a[i].col]++; // col index별로 중복을 피하기 위해 j에 대입하고 1 증가시킨다.
// transpose
b[j].row = a[i].col;
b[j].col = a[i].row;
b[j].value = a[i].value;
}
}
}
- Time complexity = O(columns + elements) <columns, elements 만큼의 loop가 여러개 있어서>
- If elements = rows·columns, (모든 원소가 0이 아닌 경우)
Time complexity = O(columns + elements) = O(rows·columns)
6> dense representation 형태의 transpose
for (int j = 0; j < col; j++)
for (int i = 0; i < row; i++)
b[j][i] = a[i][j];
- Time complexity = O(rows·columns)
4) Matrix Multiplication
0> 정의
1> dense represenatation 형태의 matrix multiplication
int a[rows_a][cols_a];
int b[rows_b][cols_b];
int d[rows_a][cols_b; ]
for (int i = 0; i < rows_a; i++) {
for (int j = 0; j < cols_b; j++) {
sum = 0;
for (int k = 0; k < cols_a; k++) {
sum += a[i][k] * b[k][j];
}
d[i][j] = sum;
}
}
- Time complexity = O(rows_a*cols_a*cols_b)
- 2개의 sparse matrix의 곱의 결과는 dense할 수 있다.
2> sparse matrix의 multiplication 원리
- Matrix A, B, D : term array a, b, d 내부에 3원소 쌍 (추가로 B_T도 term array new_b에 저장)
- row : B의 column에 곱하고 있는 A의 row
- row_begin : 현재 행에서 첫 번째 element가 term array a에서 위치
- column : A의 row가 곱하고 있는 B의 column
- totald : D matrix에서(term array d에서) 원소의 개수
- i, j : A의 row, B의 column을 연속적으로 검사하기 위한 pointer들
3> sparse marix의 multiplication
#include <stdio.h>
#include <stdlib.h>
#define MAX_TERMS 101 /*maximum number of terms 1*/
typedef struct{
int col;
int row;
int value;
}term;
void storesum(term d[], int *totald, int row, int column, int *sum)
{ /* if *sum != 0, then it along with its row and column position is stored as the *totald+1 entry in d */
if(*sum)
if (*totald < MAX_TERMS) {
d[++*totald].row = row;
d[*totald].col = column;
d[*totald].value = *sum;
*sum = 0;
}
else {
fprintf(stderr, "Numbers of terms exceeds %d" , MAX_TERMS);
exit(1);
}
}
// a의 원소 개수 = 1(행렬 정보 저장) + nonzero element 개수 + 1(여유분)
void mmult(term a[], term b[], term d[])
/* multiply two sparse matrices */
{
// 0. 변수 설정
int i, j, column;
int rows_a = a[0].row, cols_a = a[0].col, totala = a[0].value; // a의 행렬 정보 변수화
int cols_b = b[0].col, totalb = b[0].value; // b의 행렬 정보 변수화
if (cols_a != b[0].row) {
fprintf(stderr, "Incompatible matrices n");
exit(1);
} // 1> b의 row 개수 확인 2> 행렬곱 가능 여부 확인
term new_b[MAX_TERMS];
fast_transpose(b, new_b);
// 1. 초기화
int totald = 0; // d의 nonzero element 개수 초기화
int row = a[1].row; // row 초기화: a의 원소 중 첫 번째 원소의 row로 (B의 column에 곱하고 있는 A의 row)
int row_begin = 1; // row_begin 초기화: row 행의 1번째 원소가 nonzero라 가정하고 1로 초기화
int sum = 0; // A의 특정 행과 B의 특정 열의 행렬곱 결과가 저장될 장소 (일단 0으로 초기화)
/* set boundary condition */
a[totala + 1].row = rows_a;
new_b[totalb + 1].row = cols_b;
new_b[totalb + 1].col = -1;
for (i = 1; i <= totala; ) { // a의 nonzero element 개수 만큼의 loop
column = new_b[1].row; // column 초기화 : A의 row가 곱하고 있는 B의 column
for (j = 1; j <= totalb + 1; ) { // new_b의 nonzero element 개수 만큼의 loop
/* multiply row of a by column of b */
// D의 원소 하나 생성할 때마다 if나 else if로 간다.
if (a[i].row != row) { // 현재 a의 row가 내가 연산하려고 하는 row가 아니라면
storesum(d, totald, row, column, &sum);
// 1> totald의 값 1 증가 => d의 nonzero element개수 counting
// 2> d의 원소 중 증가된 totald번째에 이전에 진행한 row, column을 저장
// 3> value에는 행렬곱 연산한 sum을 저장하고 0으로 초기화
i = row_begin; // a의 첫번째 원소(a[1])부터 다시 시작
for (; new_b[j].row == column; j++);
// 이전에 연산한 d의 column과 이제 연산할 b의 column이 다를 때까지 j 증가
column = new_b[j].row; // 이제 연산할 b의 column으로 d의 column도 초기화
}
else if (new_b[j].row != column) { // 현재 b의 col이 내가 연산하려고 하는 col이 아니라면
storesum(d, totald, row, column, &sum);
i = row_begin;
column = new_b[j].row;
}
else switch (COMPARE(a[i].col, new_b[j].col)) { // a의 열(i)과 b의 행(j)을 비교 (자리가 맞아야 곱함)
case -1: /* go to next term in a */
i++; break;
case 0: /* add terms, go to next term in a and b */
sum += (a[i++].value * new_b[j++].value);
break;
case 1: /* go to next term in b */
j++;
}
} /* end of for j <= totalb+1 */
for (; a[i].row == row; i++);
row_begin = i;
row = a[i].row;
} /* end of for i <= totala */
d[0].row = rows_a;
d[0].col = cols_b;
d[0].value = totald;
}
- Time complexity = O(cols_b*totala+rows_a*totalb)
'자료구조' 카테고리의 다른 글
3-1강 - Stack, Stack using Dynamic Arrays (스택, 동적 배열로 만든 스택) (1) | 2020.04.21 |
---|---|
2-5강 - Multidimensional Arrays, Strings (다차원 배열, 문자열) (0) | 2020.04.15 |
2-3강 - Polynomials (다항식) (0) | 2020.04.10 |
2-2강 - 구조체와 공용체 (0) | 2020.04.09 |
2-1강 - ADT array, 동적 할당 (0) | 2020.04.09 |