밍경송의 E.B

<3> 배열, 구조체, 포인터 본문

CSE/자료구조&알고리즘

<3> 배열, 구조체, 포인터

m_gyxxmi 2023. 10. 25. 19:31

이 글은 시험이 2일하고 18시간정도 남은 사람이 내용 정리를 위해 적는 글입니다.
*[C언어로 쉽게 풀어쓴 자료구조]  책과 숙영리 교수님의 강의를 참고하여 작성하였음을 밝힙니다.

🃏배열

: 같은 형의 변수를 여러 개 만드는 경우에 사용하는 자료 구조

🤔연속적인 메모리 공간이 할당되고 인덱스 번호를 이용하여 쉽게 접근이 가능함(readability ↑)

- random access lookup time = O(1)

- add new element at Kth index = O(N)

 

🤔0부터 N까지의 ID가 할당되는 데이터를 처리하기 위해서는 (N+1) element 크기의 배열이 필요함

-> 인덱스는 0부터!

 

 


*예시

더보기

double A[10];  <= 80byte의 연속된 메모리 블럭이 할당됨.

: A[i] : i = 시작번지로부터의 (sizeof(double) 크기 만큼의) offset

 ex) A[4] = A[0] + 4*sizeof(double) = base + 4*sizeof(double)

 

 

🤔자료형 크기 간단 정리

  • Bool, char : 1B
  • Short : 2B
  • int, float : 4B
  • double : 8B
  • long : 4B(32 OS) / 8B(64 OS)
  • pointer :  4B(32 OS) / 8B(64 OS)

 


 

 


 

🃏 구조체

: 타입이 다른 데이터를 하나로 묶는 방법

 

- sturct 구조체이름 구조체변수; <- 위의 예제에서는 생략됐으나 위 과정이 있어야 구조체 생성됨.

- 구조체 안에 들어있는 멤버를 사용하려면 '.' <- 멤버 연산자 이용

- typedef를이용해 구조체를 새로운 타입으로 선언 가능 / 위의 예제에서 구조체의 이름은 student

 

 


 

🃏 배열의 응용 : 다항식

 

 

Q: 두 다항식의 덧셈을 해야한다고 가정할 때 아래의 질문을 고민해보자!

  1. coefficient(계수)와 exponent(차수)를 컴퓨터에 어떻게 표현할까?
  2. N차 다항식에서 n= exponen가 가장 큰 값일 때, n을 기억하는 것이 필요할까?
  3. coefficient가 0인 항과 아닌 항을 구별하는 것이 좋을까?
  4. 차수는 높은데, 항의 개수가 적은 (ex. x^100 + 10) 다항식은 어떻게 표현하는 것이 공간/시간적으로 효율적일까?

 


1) 배열을 이용한 첫번째 방법

: 모든 차수의 계수(coef)값을 배열에 저장하는 것. 하나의 다항식 = 하나의 배열

🤔장점

: 다항식의 덧셈, 뺄셈 연산 알고리즘이 간단함.

 

🤔단점

: 희소다항식( 대부분의 항의 계수가 0인 경우)을 표현할 때 공간 낭비가 심함.

 

 

*덧셈 프로그램

구조체로 다항식 배열 선언

: 구조체 A, B의 coef 배열을 스캔하면서 차수가 큰 항을 구조체 C로 이동함. 

if 차수가 같으면 구조체 A,B의 coef 값을 더하여 C의 coef에 대입함.

 

 


2) 배열을 이용한 두번째 방법

: 다항식에서 0이 아닌 항만을 배열에 저장하는 것. (계수, 차수) 형식으로 배열에 저장

 

 

🤔장점

: terms의 항의 총 개수가 MAX_TERMS만 넘지 않으면 많은 다항식을 저장 가능함.

 

🤔단점

: 인덱스 변수 관리 + 차수 저장으로 인해 더 많은 공간이 필요함 + 첫번째 방법보다 연산이 어려움.

 

* avail 변수 : 현재 비어있는 요소의 인덱스를 가리킴 = 현재 available한 가장 작은 값을 가진 index를 가리킴.

 

 

 

*덧셈 프로그램

 

: attach() = 해당 항목을 배열의 다음 빈 공간에 더하는 함수. 이때 avail 변수가 증가됨에 유의!

 

-> 순서대로 A와 B의 각 항의 차수를 비교하여, 차수가 같으면 A와 B 각 항의 계수를 더하여 C로 옮기고 차수가 다르면 A와 B 중 큰 항을 C로 옮김.

 

* 어느 한쪽 다항식이 끝나게 되면 A나 B 중 남아있는 항목들을 전부 옮기면 됨. 

두 식을 뺀 마지막 차수(expo)가 동일하지 않은 경우에 실행됨

 

 


 

🃏 희소행렬 (Sparse Matrix)

*배열을 이용하여 행렬을 표현하는 방법은 다항식을 표현하는 방법과 유사함.

 

 

1) 2차원 배열을 이용하여 배열의 전체 요소를 저장하는 방법

장점: 행렬의 연산을 간단하게 구현 가능

단점: 대부분의 항이 0인 희소행렬의 경우 MEM 낭비 ↑

 

 

🤔 행렬의 전치 연산(tranpose)을 구현하는 경우

: 2차원 배열의 요소 (i,j)를 (j,i)로 교환 + 배열을 함수의 매개변수로 전달하고 전달 받음.

( ✨C에서 함수로 배열을 전달하면 항상 원본이 전달됨 <= 배열의 이름이 포인터 역할을 하기 때문)

 

 

 

 

2) 0이 아닌 요소들만 저장하는 방법

장점: 희소 행렬의 경우 메모리가 절약됨.

단점: 각종 행렬 연산들의 구현이 복잡해짐.

 

 

🤔 행렬의 전치 연산(tranpose)을 구현하는 경우

: 행렬에서 하나의 요소는 (row, col, value)로 표현. (구조체로 정의)

->  기존 행렬의 열이 행으로 변경되기 때문에 0열 처리 -> 1열 처리 -> ... 이렇게 하면 행 순서대로 작성됨.

*처리: 0열에 있는 요소를 모두 찾아서 0행에 저장하는 것.

 

낮은 행부터 높은 행까지 오름차순으로 저장되어야 한다는 가정이 필요함.

 

 


 

🃏 포인터

: 다른 변수의 주소를 가지고 있는 변수 

* 포인터로 선성된 변수에 저장된 값은 메모리 주소값

 

 

 

🤔포인터 연산자

& : 변수의 주소를 추출하는 연산자

* : 포인터가 가리키는 메모리에 값을 변경하는 연산자

 

더보기

int a = 10;

int *ptr = &a;

printf(" ", &a) = printf("  ", ptr) !=  printf("  ", &ptr)

=> *ptr과 a는 전적으로 동일하다!

 

cf) 포인터가 어떤 객체도 가리키고 있지 않을 때는 NULL 포인터 상태로 두는 것이 좋다

 


🤔 함수 에서,  매개변수로 전달된 포인터를 이용하여 외부 변수의 값을 변경 가능!

 

출력 결과

: swap을 호출하기 전: a=1, b=2

  swap을 호출한 다음: a=2, b=1

 


 

🃏배열과 포인터

* 배열의 이름은 사실상 포인터와 같은 역할을 함.

✨ A = &A[0] = 10 / A+1 = &A[1] = 14 [주소]

      *(A+2) = A[2] = 3  [값]

 

=> *ptr과 a는 전적으로 동일하다!

 

 


 

🃏 동적 메모리 할당 (Dynamic memory allocation)

: 프로그램 실행 도중에 메모리를 할당 받는 것 => 필요한 만큼만 할당 받고, 사용 후 반납함.

 

🤔Heap : OS가 사용하지 않는 메모리 공간을 모아 놓은 것, 동적 메모리가 할당되는 공간

=> Heap 메모리 매니저가 할당해주는 영역이기 때문에 이 영역에는 변수를 선언할 수 없음 (anonymous memory)

** Heap memory는 반드시 pointer로만 사용가능!

 

 

🤔포인터 변수의 활용

 

- 정적으로 할당된 메모리 블록의 값들을 copy&paste로 sub-function에 전달하는 대신, 시작번지만을 매개변수로 전달하고 이를 포인터 변수로 받아서 사용함 => 시공간 절약 가능!

 

- 런타임때가 되어야 비로소 필요한 메모리 크기를 알 수 있는 경우 (input으로 입력된 값 등), 프로그램 실행 중간에 할당된 stack memory를 포인터로 표기 후 사용하여 유동성을 높일 수 있음!

 


✔️malloc() 

: size 바이트만큼의 메모리 블록을 할당한 후, 이 메모리 블록의 시작 주소(가장 낮은 주소) 를 return

*메모리 블록의 경우, 요청한 크기의 연속된 heap 메모리임.

 

ex) int *p

     p = (int *)malloc(SIZE * sizeof(int));

 

✔️free()

: 할당된 메모리 블록을 OS에게 반환함. 

*반드시 malloc()이 반환한 포인터 값을 기억했다가 반환해야 함.

 

 

free( ptr ) 호출뒤에 ptr = NULL 을 넣으면 실수로 ptr을 사용하여 의도치 않는 결과를 초래하는 것을 complier가 막아줌.

 

 


🃏 구조체와 포인터

: 포인터를 통하여 구조체의 멤버에 접근하는 편리한 표기법 = ' -> '

 

*동적으로 생성된 구조체는 포인터를 통해서만 접근할 수 있다. malloc() 함수를 이용하여 구조체를 동적 생성함.

: (*p).name보다 p->name이 더 용이함!