Company
교육 철학

unordered_map, counting sort(계수 정렬)

계수정렬 (Counting Sort) & 해시테이블 (Hash Table)

계수정렬 (Counting Sort)

핵심 개념

Counting sort는 정렬 알고리즘중 하나로, 키 값의 범위가 제한된 정수 일때 매우 효율적인 방법. 다른 비교기반 정렬 알고리즘과 달리, 비교를 하지 않고 숫자의 발생 횟수를 카운트하여 정렬을 수행한다.
시간 복잡도가 O(n+k)이다. 데이터크기와 데이터 값의 범위에 비례한다.

카운팅소트의 동작원리

1. 입력데이터와 범위를 미리 알아두어야 한다. 예를 들어 0에서 100사이의 정수만을 다룬다면, 해당범위에 맞춰 알고리즘 동작

2. 각 데이터가 몇번씩 등장했는지 기록하는 카운트 배열을 만든다.

3. 카운트 배열을 통해 해당 숫자보다 작은 숫자가 몇개 있는지 계산한다.

4. 원래 배열의 숫자를 하나씩 꺼내면서 정렬된 위치에 배치한다.

예제: [5, 2, 2, 1, 2] 정렬

1단계: 정렬할 값들을 count 배열에 저장한다.

원본 배열: [5, 2, 2, 1, 2] 카운트 배열 구성: 숫자 | 0 | 1 | 2 | 3 | 4 | 5 | 개수 | 0 | 1 | 3 | 0 | 0 | 1 | 각 숫자의 등장 횟수를 센다: - 1이 1번 등장 - 2가 3번 등장 - 5가 1번 등장
Plain Text
복사

2단계: 누적합을 구해준다.

누적합 계산 과정: index 0: 0 index 1: 0 + 1 = 1 index 2: 1 + 3 = 4 index 3: 4 + 0 = 4 index 4: 4 + 0 = 4 index 5: 4 + 1 = 5 결과 배열: [0, 1, 4, 4, 4, 5]
Plain Text
복사

누적합을 구하는 이유

신기하게도 정렬할 값들이 result 배열에 들어 갈 수 있는 LastIndex가 결정된다.
예시 분석: - 숫자 5는 result[5-1]에 들어가면 된다. - 숫자 2는 result[4-1]에 들어가면 된다. - 숫자 1은 result[1-1]에 들어가면 된다. (index에 -1을 해주면 result[0]부터 채울 수 있다.) 최종 결과: [1, 2, 2, 2, 5]
Plain Text
복사

3단계: 누적합을 이용하여 정렬 결과 저장

원본 배열을 뒤에서부터 처리하여 안정 정렬 보장: 3단계 과정 (5단계별 세부 처리): 1. 숫자 2 처리: count[2]=4 → result[3]=2, count[2]=3 2. 숫자 1 처리: count[1]=1 → result[0]=1, count[1]=0 3. 숫자 2 처리: count[2]=3 → result[2]=2, count[2]=2 4. 숫자 2 처리: count[2]=2 → result[1]=2, count[2]=1 5. 숫자 5 처리: count[5]=5 → result[4]=5, count[5]=4 정렬된 값을 하나씩 담색하면서, 제 위치에다가 값을 저장한다.
Plain Text
복사

계수정렬 완전 구현

#include <iostream> #include <queue> int main() { int arr[5] = { 5, 2, 2, 1, 2 }; int bucket[6] = { }; // 카운트 배열 int result[5] = { }; // 결과 배열 //1. bucket에 arr의 값을 넣어준다. for (size_t i = 0; i < 5; i++) { bucket[arr[i]]++; } //2. bucket의 값을 누적시킨다. for (size_t i = 1; i < 6; i++) { bucket[i] += bucket[i - 1]; } //3. result에 bucket의 값을 넣어준다. for (size_t i = 0; i < 5; i++) { int index = bucket[arr[i]] - 1; result[index] = arr[i]; bucket[arr[i]]--; // 같은 값의 다음 위치를 위해 감소 } // 결과 출력 std::cout << "정렬 결과: "; for (int i = 0; i < 5; i++) { std::cout << result[i] << " "; } std::cout << std::endl; return 0; }
C++
복사

계수정렬의 특징

장점

1.
매우 빠른 속도: O(n+k) 시간복잡도
2.
안정 정렬: 같은 값의 상대적 순서 유지
3.
비교 없음: 값의 비교 연산 불필요

단점

1.
범위 제한: 정수만 가능, 범위가 클 수록 비효율
2.
추가 메모리: O(k) 공간 필요 (k = 값의 범위)
3.
범위 의존적: 값의 범위가 크면 사용 불가

사용 적합한 경우

정수 데이터
값의 범위가 작을 때 (보통 n과 비슷하거나 작을 때)
안정 정렬이 필요한 경우
최고 성능이 필요한 경우

해시테이블 (Hash Table) - unordered_map

핵심 개념

C++에서 제공하는 해시테이블 기반의 컨테이너입니다. 이 컨테이너는 키값과 데이터값 쌍으로 이루어져있다.
키를 기준으로 인덱스에 접근하기 때문에 데이터를 검색, 삽입할때 빠른속도로 사용 할 수 있다.

해시테이블의 특징

1. 해시 기반: 내부적으로 해시함수를 사용해서 키를 계산하기 때문에, 평균 시간 복잡도가 삽입, 삭제, 검색 전부 O(1)이다.

2. 키의 순서 보장이 되지 않는다: 삽입되는 순서가 유지되지 않는다. 내부적으로 해시테이블에 저장되기때 문에, 순서가 무작위이다.

3. 중복되는 키값을 허용 않는다: 각 키는 고유해야 하며, 중복된 값을 사용하면 그냥 덮어 씌어진다

4. 시간복잡도는 O(1)이다: 그치만 해시 충돌이 많이질 경우 O(n)까지도 늘어날수도 있다.

unordered_map 기본 사용법

#include <iostream> #include <unordered_map> int main() { std::unordered_map<std::string, int> age_map; // 삽입 age_map["Alice"] = 25; age_map.insert(std::make_pair("Bob", 30)); age_map["Bob"] = 30; age_map["Charlie"] = 22; // 값 접근 std::cout << "Alice's age: " << age_map["Alice"] << std::endl; // 존재 여부 확인 if (age_map.find("Bob") != age_map.end()) { std::cout << "Bob is in the map." << std::endl; } // 삭제 age_map.erase("Charlie"); // 전체 순회 for (const auto& pair : age_map) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; }
C++
복사

주요 함수들

삽입 연산

// 방법 1: [] 연산자 map["key"] = value; // 방법 2: insert 함수 map.insert(std::make_pair("key", value)); map.insert({"key", value}); // C++11 이후
C++
복사

조회 연산

// 방법 1: [] 연산자 (없으면 기본값으로 생성) int value = map["key"]; // 방법 2: at() 함수 (없으면 예외 발생) int value = map.at("key"); // 방법 3: find() 함수 (가장 안전) auto it = map.find("key"); if (it != map.end()) { int value = it->second; }
C++
복사

삭제 연산

// 키로 삭제 map.erase("key"); // 반복자로 삭제 auto it = map.find("key"); if (it != map.end()) { map.erase(it); } // 전체 삭제 map.clear();
C++
복사

기타 유용한 함수들

// 크기 확인 size_t size = map.size(); // 비어있는지 확인 bool empty = map.empty(); // 키 존재 여부 (C++20) bool exists = map.contains("key"); // 개수 세기 (0 또는 1) size_t count = map.count("key");
C++
복사

해시테이블 vs 다른 자료구조

구분
unordered_map
map
vector
평균 시간복잡도
O(1)
O(log n)
O(n)
최악 시간복잡도
O(n)
O(log n)
O(n)
순서 보장
(정렬됨)
(삽입순)
메모리 사용
높음
보통
낮음
사용 적합성
빠른 검색
정렬된 데이터
순차 접근