Search
Duplicate
✏️

chap2 배열과 구조체

작성일시
2022/09/24 03:57
자료
복습
태그
자료구조

배열과 구조체

배열의 정의

C에서 배열

배열의 선언
int list[5]; *plist[5];
C
복사
배열의 구현
list[0]의 주소 = a
list[1]의 주소 = a + sizeof(int)
배열을 인자로 전달 → 배열의 시작 주소가 복사
Call - by - Reference와 동일한 효과
list + i = &list[i] *(list + i) = list[i]
C
복사
배열 ADT
Array Create(j, list) // j 차원의 배열을 반환, list는 j-tuple로서 i번째 원소의 값은 i번째 차원의 크기를 나타냄 Item Retrieve(A, i) Array Store(A, i, x)
C
복사

배열의 동적 할당

1차원 배열 동적 할당
int *A = (int *)malloc(sizeof(int) * 100); int *B = (int *)calloc(100, sizeof(int)); A = (int *)realloc(A, sizeof(int) * 200); free(A);
C
복사
2차원 배열 동적 할당
int **C = (int **)malloc(sizeof(int *) * 10); for (int i = 0; i < 10; i++) { C[i] = (int *)malloc(sizeof(int) * 20); }
C
복사

구조체(Structure)

하나 이상의 기본 자료형을 기반으로, 사용자 정의 자료형을 만들 수 있는 문법 요소
배열과의 차이점
배열 : 동일한 자료형
구조체 : 다양한 자료형
// 구조체 struct humanBeing { char name[10]; int age; double salary; }; typedef struct humanBeing human_being;
C
복사
구조체의 내용이 동일한 지를 검사하는 방법
memcmp 사용
int humans_qual(human_geing person1, human_being person2) { if (strcmp(person1.name, person2.name)) return FALSE; if (person1.age != person2.age) return FALSE; if (person1.salary != person2.salary) return FALSE; return TRUE; } // memcmp(&person1, &person2, sizeof(human_being))
C
복사
Memory Alignment를 고려
구조체 4btye 배수씩 시작한다
자기 참조 구조체 (Self-Referential Structure)
구조체의 속성 중 하나가 스스로를 가리키는 구조체
struct list { char data; struct list *link; };
C
복사

Union

Union의 필드들은 메모리를 공유
struct human { enum {female, male} gender; union { int children; int beard; } u; } person1, person2; person1.gender = female; person1.u.children = 4; person2.gender = male; person2.u.beard = FALSE;
C
복사
C는 union 필드들의 적절한 사용을 검사하지 않음

다항식의 표현

순서리스트 (Ordered list)
데이터들의 순서가 유지되는 집합
ex) {일, 월, 화, 수, 목, 금, 토}
순서 리스트에 적용 가능한 연산들
리스트의 길이 n의 계산
리스트의 모든 데이터들을 왼쪽→오른쪽으로 읽기
리스트의 i번째 데이터를 검색, 대체 (0 ≤ i < n)
리스트의 i번째 위치에 새로운 데이터 추가 → 연결리스트 Good
리스트의 i번째 데이터 삭제
다항식
순서화 리스트를 구현하는 두가지 방법
배열 → i번째 데이터를 배열 인덱스 i에 저장
연결리스트
p(x)=a1xe1+...+anxenp(x) = a_1x^{e_1} + ... + a_nx^{e_n}
→ 순서화된 <ei,aie_i, a_i> 쌍들의 집합으로 표현
계수 (coefficient) 지수 (exponent)
Polynomial Zero() Boolean IsZero(poly) Coefficient Coef(poly, expon) Exponent Lead_Exp(poly) Polynomial Attach(poly, coef, expon) Polynomial Remove(poly, expon) Polynomial SingleMult(poly, coef, expon) Polynomial Add(poly1, poly2) Polynomial Mult(poly1, poly2)
C
복사

C 다항식 구현 (1)

1) 모든 지수의 계수들을 내림차순으로 저장
#define MAX_DEGREE 101 typedef struct { int degree; float coef[MAX_DEGREE]; } polynomial; // 2x^3 + x^2 -1. => [3, (2, 1, 0, -1]
C
복사
→ x^100 + 1
⇒ 계수가 0인 것을 계속 저장 해야함

C 다항식 구현 (2)

2) 지수와 계수를 모두 저장
#define MAX_TERMS 100 typedef struct { float coef; int expon; } polynomial; polynomial terms[MAX_TERMS]; int avail = 0;
C
복사
x100+1x^{100} + 1 ⇒ {(1, 100), (1, 0)}
→ 모든 다항식을 저장하기 위한 전역변수 terms[] 사용
→ 각 다항식은 <start, end>의 쌍으로 표현

다항식 합

void padd (int startA, int finishA, int startB, int finishB, int *startD, int *finishD) { float coefficient; *startD = avail; while (startA <= finishA && startB <= finishB) switch (COMPARE(terms[startA].expon, terms[startB].expon)) { case -1: // a.expon < b.expon attach(terms[startB].coef, terms[startB].expon); startB++; break; case 0: coefficient = terms[startA].coef + terms[startB].coef; if (coefficient) attach(coefficient, terms[startA].expon); startA++; startB++; break; case 1: // a.expon > b.expon attach(terms[startA].coef, terms[startA].expon); startA++; } for (; startA <= finishA; startA++) attach(terms[startA].coef, terms[startA].expon); for (; startB <= finishB; startB++) attach(terms[startB].coef, terms[startB].expon); *finishD = avail - 1; }
C
복사
void attach(float coefficient, int exponent) { // 다항식에 새로운 항을 추가하는 함수 if (avail >= MAX_TERMS) { fprintf(stderr, "Too many terms in the polynomial\n"); exit(1); } terms[avail].coef = coefficient; terms[avail++].expon = exponent; }
C
복사

희소 행렬 (Sparse Matrix)

보통 행렬의 표현 : a[MaxRows][MaxCols]
희소 행렬 → 0이 많이 포함될 경우

C에서 희소 행렬의 구현

<row, column, value>의 쌍을 저장
빠른 전치를 위하여 row의 오름차순으로 저장
typedef struct { int row; int col; int value; } term; term a[MAX_TERMS];
Java
복사

희소 행렬 ADT

Sparse_Matrix Create(rows, cols) // rows x cols개의 항목을 저장할 수 있는 행렬 반환 Sparse_Matrix Transpose(a) // a[i][j] = v 일때, c[j][i] = v 인 전치 행렬 c를 반환 Sparse_Matrix Add(a, b) // if (a와 b의 dimension이 동일) // c[i][j] = a[i][j] + b[i][j]인 c 반환 // else return error Sparse_Matrix Multiply(a, b) // if (a의 열의 수 == b의 행의 수) // c[i][j] = Sum(a[i][k] x b[k][j])인 c 반환 // else return error
C
복사

행렬의 전치 (Transposing a Matrix)

row와 column을 교환
for each row i take element <i, j, value> and store it as element <j, i, value> of the transpose
C
복사
ex)
(0, 0, 15) → (0, 0, 15)
(0, 3, 22) → (3, 0, 22)
(0, 5, -15) → (5, 0, -15)
전치 연산을 위한 연속적인 삽입으로 인해, 기존에 저장된 항목들의 이동이 불가피

2차원 배열에서 transpose 구현

for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) b[i][j] = a[j][i]; // 시간복잡도 = O(rows * cols)
C
복사

Transpose

void transpose(term a[], term b[]) { // a는 입력행렬, b는 출력행렬, b = a^T int count, i, j, currentB; count = a[0].value; // a에서 0이 아닌 원소의 수 b[0].row = a[0].col; // b의 행의 수 = a의 열의 수 b[0].col = a[0].row; // b의 열의 수 = a의 행의 수 b[0].value = count; // 0이 아닌 원소 수는 a와 동일 if (count > 0) { // a가 empty matrix가 아니라면 currentB = 1; // 새로운 원소가 b에 저장될 위치 for (i = 0; i < a[0].col; i++) { // a의 열 순서로 전치 연산 실행 (i: 현재 열) for (j = 1; j <= count; j++) { // a에서 현재 열을 찾자 (a는 행 순서로 저장) if (a[j].col == i) { // 현재 열의 원소 발견, b에 추가 b[currentB].row = a[j].col; b[currentB].col = a[j].row; b[currentB].value = a[j].value; currentB++; // b의 저장될 위치를 1 증가 } }}} } // 시간복잡도 = O(columns * elements) -> O(n^3)
C
복사

Fast Transpose

각 column이 저장될 곳을 미리 파악
Column Index 저장을 위한 추가적인 공간 사용
→ O(columns + elements) ≅ O(columns * rows)
→ 배열을 하나만 사용하여 구현 가능
#define MAX_COL 50 void fast_transpose(term a[], term b[]) { // a는 입력행렬, b는 출력행렬, b = a^T int row_terms[MAX_COL]; // a의 열의 원소 수 저장 int starting_pos[MAX_COL]; // 각 열의 시작 위치 저장 int num_col = a[0].col; int num_terms = a[0].value; b[0].row = num_col; b[0].col = a[0].row; b[0].value = num_terms; if (num_terms > 0) { // a에 0이 아닌 원소들이 존재 for (int i = 0; i < num_col; i++) row_terms[i] = 0; // 초기화 for (int i = 1; i <= num_col; i++) row_terms[a[i].col]++; // a의 각 열의 원소 수 계산 // row_terms를 이용하여 시작위치 계산 starting_pos[0] = 1; for (int i = 1; i < num_col; i++) starting_pos[i] = starting_pos[i-1] + row_terms[i-1]; // 시작위치를 이용하여, 특정 원소의 저장위치 파악 for (int i = 1; i <= num_terms; i++) { j = starting_pos[a[i].col]++; b[j].row = a[i].col; b[j].col = a[i].row; b[j].value = a[i].value; // b에서 동일한 행에 대해 열 순서로 저장되는가? } } }
C
복사

Fast Transpose의 개선

for (i = 1; i <= num_terms; i++) row_terms[a[i].col]++; tmp1 = 1; for (i = 0; i < num_cols; i++) { tmp2 = row_terms[i]; row_terms[i] = tmp1; tmp1 += tmp2; } for (i = 1; i <= num_terms; i++) { j = row_terms[a[i].col]++; b[j].row = a[i].col; b[j].col = a[i].row; b[j].value = a[i].value; }
C
복사

희소 행렬의 곱셈

행렬의 곱셈 방법

A = m x n 행렬, B = n x p 행렬, A x B = D
D = m x p 행렬
dij=k=0n1aikbkjd_{ij} = \sum^{n-1}_{k=0}a_{ik}b_{kj}
for 0 ≤ i < m, 0 ≤ j < p
→ 희소 행렬의 곱셈 결과는 희소 행렬이 아닐 수 있음

희소 행렬의 곱셈의 예

void mmult(term a[], term b[], term d[]) { // a, b:입력, d:출력, d=a*b int i, j, column, totalD = 0; int totalA = a[0].value; int totalB = b[0].value; int row_begin = 1, row = a[1].row, sum = 0; // row : a의 현재 행 term new_b[MAX_TERMS]; if (a[0].col != b[0].row) { // a의 열의 수와 b의 행의 수는 동일 fprintf(stderr, "Incompatible matricses\n"); exit(1); } else fast_transpose(b, new_b); // 경계 조건을 설정 a[totalA+1].row = a[0].row; new_b[totalB+1].row = b[0].col; for (i = 1; i <= totalA;) { column = new_b[1].row; // b의 현재열 for (j = 1; j <= totalB + 1;) { // a의 현재 행과 b의 현재 열에 대해 곱셈 수행 if (a[i].row != row) { // a의 현재 행을 벗어남 storesum(d, &totalD, row, column, &sum); i = row_begin; // b는 다음 열로, a는 원위치로 for (; new_b[j].row == column; j++); column = new_b[j].row; } else if (new_b[j].row != column( { // b의 현재 열을 벗어남 storesum(d, &totalD, row, column, &sum); i = row_begin; // a는 원위치 column = new_b[j].row // b는 당므 열로 } else switch(COMPARE(a[i].col, new_b[j].col)) { case -1: // a[i].col < new_b[j].col, a증가 i++; break; case 0: // 계산 후, a와 b를 모두 진행 sum += (a[i++].value * new_b[j++].value); break; case 1: // a[i].col > new_b[j].col, b증가 j++; } } for (; a[i].row == row; i++); // b의 모든 원소를 처리한 후 row_begin = i; row = a[i].row; // a의 현재 행을 다음 행으로 } // end of for i <= totalA d[0].row = a[0].row; d[0].col = b[0].col; d[0].value = totalD; }
C
복사
void storesum(term d[], int *totalD, int column, int *sum) { // sum이 0이 아니면, d배열의 *totalD+1 위치에 row, column 값과 함께 저장 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 in product exceeds %d\n", MAX_TERMS); exit(1); } }
C
복사
Complexity
→ O(cols_b * totalA + rows_a * totalB)

전통적인 행렬 곱셈 알고리즘

for (int i = 0; i < row_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; } }
C
복사
Complexity : O(rows_a * cols_b * cols_a)

다차원 배열

다차원 배열을 저장하는 두가지 방법

행 우선 순서의 주소 계산

2차원 배열 : A[upper0][upper1]

a가 A[0][0]의 주소라고 가정
A[i][0]의 주소 = a + i*upper1
A[i][j]의 주소 = a + i*upper1 + j

3차원 배열 : A[upper0][upper1][upper2]

a가 A[0][0][0]의 주소라고 가정
A[i][j][k]의 주소 = a + i*upper1*upper2 + j*upper2 + k
ex)
A[10][20][30] 배열에서 A[0][0][0]의 주소가 a
A[4][2][5]의 주소 = a + 4*20*30 + 2*30 + 5
= a + 2465
ex2)
A[5][7][6] 배열에서 A[0][0][0]의 주소가 a
A[4][2][5]의 주소 = a + 4*7*6 + 2*6 + 5
= a + 185

행 우선(Row Major) 저장의 규칙