계수정렬 (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) |
순서 보장 | |||
메모리 사용 | 높음 | 보통 | 낮음 |
사용 적합성 | 빠른 검색 | 정렬된 데이터 | 순차 접근 |







