내일배움캠프 12일차 - 자료구조 & 알고리즘 2주차 : 데이터를 담는 그릇

vector

C스타일 배열(int arr[10])은 크기가 고정이다. 선언할 때 크기를 정하면 나중에 늘릴 수 없다.
 → vector는 이 문제를 해결하는 동적 배열이다.

vector에는 크기 개념이 두 가지 있다.
 - size: 실제 들어있는 원소 개수 ("가방에 든 아이템 수")
 - capacity: 현재 확보된 메모리 공간 ("가방의 전체 칸 수")

vector 재할당의 동작원리

capacity 4, size4인 vector에 push_back(5) 호출

1) 새 공간 확보(capacity × 2 만큼 할당)
2) 기존 원소를 새 공간으로 복사
3) 기존 메모리 해제 + 새 원소 추가

재할당 = O(n) 복사 비용 → push_back의 숨은 비용

※ 복사할 때 원소를 하나하나 옮겨야 하기 때문이다.


왜 2배씩 늘릴까?

매번 1칸씩 늘리면 push_back할 때마다 전부 복사 : O(n)*n번 = O(n^2)
 → 2배씩 늘리면 재할당 횟수가 log n번으로 줄어듦

push_back은 평균 O(1)
이것을 분할 상환 분석(Amortized Analysis)이라고 한다.
미리 크기를 안다면 reserve(n)으로 재할당 자체를 방지할 수 있다.


vector 연산별 시간 복잡도

push_back(): 평균 O(1)   < 뒤에 추가만 하면 됨 >
pop_back(): O(1)   < 마지막 원소만 제거 >
v[i]: O(1)   < 연속 메모리, 바로 접근 >
insert(중간): O(n)   < 삽입한 칸 뒤의 원소를 전부 밀어야함(비용발생) >
erase(중간): O(n)   < 제거한 칸 뒤의 원소를 전부 당겨야함(비용발생) >

insert, erase반복문 안에서 매번 호출하면 O(n)×n = O(n^2)


순회 방법 비교

1) 인덱스 for문 (인덱스가 필요할때)

 : for (int i = 0; i < v.size(); i++)

2) 범위 기반 for (향상된 for문, 읽기만 할 때)

 : for (int x : v)정적배열에도 사용가능

3) 범위 기반 + 참조 (원소를 수정할 때)

 : for (int& x : v)


vector는 힙에 한줄로 저장된다.

정적배열vector의 스택메모리, 힙메모리 구조

스택(Stack) = 함수 호출 시 자동으로 잡히는 작은 공간(data ptr, size, capacity 정보 등)

힙(Heap) = 프로그래머가 요청해서 잡는 넓은 공간


캐시 히트 vs 캐시미스

CPU는 메모리에서 데이터를 가져올 때 주변 데이터도 함께 가져온다. (캐시 라인, 보통 64바이트)
 : 기본적으로 주변에 있는 것들도 같이 쓸 확률이 높으므로

캐시 히트(Cache hit): vector처럼 데이터가 연속적으로 붙어있으면빠름!

캐시 미스(Cache miss): Linked List처럼 데이터가 산발적으로 흩어져 있으면느림

※인덱스 접근 v[i] O(1)인 이유: 연속 메모리 + 원소 크기 고정
 → 시작 주소 + i * sizeof(원소) = 덧셈 한번


게임·웹·데이터베이스의 사례

1) 게임 개발

 : 몬스터 목록, 총알 리스트, 파티클 배열

 → 매 프레임 순회하며 업데이트. 캐시 효율이 핵심

UE5에서도 TArray = vector와 동일 구조


2) 웹/서버

 : 사용자 목록, 상품 리스트

 → vector에 담아 처리

페이지네이션(20개씩 끊기)도 인덱스 접근 O(1) 덕분


3)일반 CS

 : Python list, Java ArrayList = 전부 같은 원리

"배열의 인덱스 접근이 O(1)인 이유" = 연속 메모리 (시작주소 + i번째 칸 → 덧셈)


오늘의 핵심

 - vector는 연속 메모리에 데이터를 담는 동적 배열

 - 뒤에 추가/삭제는 O(1), 중간 삽입/삭제는 O(n)

 - capacity가 부족하면 2배로 재할당, 비용을 줄이려면 reserve() 사용

 - 연속 메모리 → 캐시 효율이 좋음: 순회가 빈번한 상황에 가장 적합


※ 시간복잡도 팁

10만개의 원소가 있는 vector에서
맨 앞 원소를 삭제하는 erase(v.begin())100번 반복하면
총 시작 복잡도는?

정답: O(n)

설명: O(100n) = 사실상 O(n)

100번은 상수이므로 O(100*n) = O(n)

하지만 n번 반복하면 O(n)*n=O(n^2)

상수 vs 변수의 차이가 핵심

댓글

이 블로그의 인기 게시물

내일배움캠프 사전캠프 - 사전캠프설 연휴 커피 파밍 이벤트 작품 [ EXTREMITY ]

내일배움캠프 29일차 - 커리어데이 2일차 : 클라이언트 프로그래머로서 포트폴리오, 입사준비팁