프로그래밍/C++

C++ STL 컨테이너: 벡터, 리스트, 맵, 셋 활용 가이드

shimdh 2025. 2. 1. 14:30
728x90

1. 벡터 (Vector)

벡터는 동적 배열을 구현한 컨테이너로, 크기가 가변적이며 연속된 메모리에 데이터를 저장합니다. 벡터는 빠른 접근 속도와 유연한 크기 조절이 가능해 다양한 상황에서 활용됩니다.

1.1 벡터의 주요 특징

  • 동적 크기: 필요에 따라 자동으로 크기를 조절합니다.
  • 빠른 접근: 인덱스를 통해 O(1) 시간 복잡도로 요소에 접근할 수 있습니다.
  • 자동 메모리 관리: 사용자가 직접 메모리를 관리할 필요가 없습니다.
  • 연속된 메모리: 데이터가 연속된 메모리 공간에 저장되어 캐시 효율성이 높습니다.

1.2 벡터 사용 예제

#include <iostream>
#include <vector>

int main() {
    // 정수형 벡터 생성
    std::vector<int> numbers;

    // 값 추가
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);

    // 요소 출력
    for (size_t i = 0; i < numbers.size(); ++i) {
        std::cout << "Element at index " << i << ": " << numbers[i] << std::endl;
    }

    // 벡터의 크기와 용량 확인
    std::cout << "Size: " << numbers.size() << ", Capacity: " << numbers.capacity() << std::endl;

    // 벡터의 첫 번째와 마지막 요소 접근
    std::cout << "First element: " << numbers.front() << ", Last element: " << numbers.back() << std::endl;

    // 벡터의 모든 요소 제거
    numbers.clear();
    std::cout << "Size after clear: " << numbers.size() << std::endl;

    return 0;
}

출력 결과:

Element at index 0: 10
Element at index 1: 20
Element at index 2: 30
Size: 3, Capacity: 4
First element: 10, Last element: 30
Size after clear: 0

1.3 벡터의 활용 사례

  • 데이터 수집 및 처리: 데이터의 크기가 미리 정해지지 않은 경우 벡터를 사용하여 동적으로 데이터를 수집하고 처리할 수 있습니다.
  • 행렬 연산: 2차원 벡터를 사용하여 행렬을 표현하고 연산할 수 있습니다.
  • 캐싱: 빠른 접근 속도를 활용하여 캐시 데이터를 저장하고 관리할 수 있습니다.

1.4 벡터의 성능 고려 사항

  • 크기 조정: 벡터의 크기가 증가할 때마다 메모리 재할당이 발생할 수 있으며, 이는 성능에 영향을 미칠 수 있습니다. reserve() 함수를 사용하여 미리 메모리를 할당하면 이러한 문제를 완화할 수 있습니다.
  • 삽입 및 삭제: 벡터의 중간에 요소를 삽입하거나 삭제하는 작업은 O(n) 시간 복잡도를 가지므로, 이러한 작업이 빈번한 경우 리스트를 사용하는 것이 더 효율적일 수 있습니다.

예제: 벡터의 중간 삽입 및 삭제

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {10, 20, 30};

    // 중간에 값 삽입
    numbers.insert(numbers.begin() + 1, 15);

    // 삽입 후 출력
    std::cout << "After insertion: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 중간 값 삭제
    numbers.erase(numbers.begin() + 2);

    // 삭제 후 출력
    std::cout << "After deletion: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

출력 결과:

After insertion: 10 15 20 30 
After deletion: 10 15 30

2. 리스트 (List)

리스트는 연결 리스트를 기반으로 한 컨테이너로, 요소의 삽입과 삭제가 빈번한 상황에서 유용합니다. 벡터와 달리 인덱스를 통한 직접 접근은 불가능하지만, 순차 접근이 가능합니다.

2.1 리스트의 주요 특징

  • 연결 구조: 각 요소가 다음 및 이전 요소를 가리키는 포인터를 가지고 있습니다.
  • 빠른 삽입/삭제: 특정 위치에서의 삽입과 삭제가 O(1) 시간 복잡도로 가능합니다.
  • 순차 접근: 인덱스 접근이 불가능하며, 순차적으로 탐색해야 합니다.
  • 메모리 효율성: 요소가 연속된 메모리에 저장되지 않아 메모리 사용이 더 유연합니다.

2.2 리스트 사용 예제

#include <iostream>
#include <list>

int main() {
    // 정수형 리스트 생성
    std::list<int> myList;

    // 값 추가
    myList.push_back(10);  // 끝에 추가
    myList.push_back(20);
    myList.push_front(5);  // 앞에 추가

    // 출력
    std::cout << "리스트 내용: ";
    for (const int& value : myList) {
        std::cout << value << " ";
    }

    // 리스트의 크기 확인
    std::cout << "\nSize of list: " << myList.size() << std::endl;

    // 특정 위치에 값 삽입
    auto it = myList.begin();
    std::advance(it, 1); // 두 번째 위치로 이동
    myList.insert(it, 15);

    // 삽입 후 출력
    std::cout << "리스트 내용 after insertion: ";
    for (const int& value : myList) {
        std::cout << value << " ";
    }

    // 특정 값 삭제
    myList.remove(20);

    // 삭제 후 출력
    std::cout << "\n리스트 내용 after removal: ";
    for (const int& value : myList) {
        std::cout << value << " ";
    }

    return 0;
}

출력 결과:

리스트 내용: 5 10 20 
Size of list: 3
리스트 내용 after insertion: 5 15 10 20 
리스트 내용 after removal: 5 15 10

2.3 리스트의 활용 사례

  • 실시간 데이터 처리: 데이터의 삽입과 삭제가 빈번한 실시간 시스템에서 리스트를 사용하여 효율적으로 데이터를 관리할 수 있습니다.
  • 큐와 스택 구현: 리스트를 사용하여 큐나 스택을 구현할 수 있습니다.
  • 네트워크 패킷 처리: 네트워크 패킷과 같이 순차적으로 처리해야 하는 데이터에 적합합니다.

2.4 리스트의 성능 고려 사항

  • 순차 접근: 리스트는 인덱스를 통한 직접 접근이 불가능하므로, 특정 위치의 요소에 접근하려면 순차적으로 탐색해야 합니다. 이는 O(n) 시간 복잡도를 가집니다.
  • 메모리 오버헤드: 각 요소가 다음 및 이전 요소를 가리키는 포인터를 저장하므로, 메모리 사용량이 벡터에 비해 더 많을 수 있습니다.

예제: 리스트의 순차 접근

#include <iostream>
#include <list>

int main() {
    std::list<int> myList = {5, 10, 15, 20};

    // 순차 접근을 통한 특정 값 찾기
    auto it = myList.begin();
    std::advance(it, 2); // 세 번째 요소로 이동
    std::cout << "Third element: " << *it << std::endl;

    return 0;
}

출력 결과:

Third element: 15

3. 맵 (Map)

맵은 키-값 쌍을 저장하는 연관 컨테이너로, 특정 키에 대한 값을 빠르게 조회할 수 있습니다. 내부적으로 이진 탐색 트리를 사용하여 데이터를 정렬된 상태로 유지합니다.

3.1 맵의 주요 특징

  • 고유한 키: 각 키는 유일하며 중복을 허용하지 않습니다.
  • 자동 정렬: 키를 기준으로 데이터가 자동으로 정렬됩니다.
  • 빠른 검색: 평균 O(log n) 시간 복잡도로 검색이 가능합니다.
  • 유연한 데이터 관리: 키와 값을 통해 복잡한 데이터 구조를 쉽게 관리할 수 있습니다.

3.2 맵 사용 예제

#include <iostream>
#include <map>
#include <string>

int main() {
    // 맵 선언
    std::map<std::string, int> ageMap;

    // 요소 추가
    ageMap["Alice"] = 30;
    ageMap["Bob"] = 25;
    ageMap["Charlie"] = 35;

    // 특정 키에 대한 값 접근
    std::cout << "Alice's Age: " << ageMap["Alice"] << std::endl;

    // 모든 요소 출력
    for (const auto& pair : ageMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    // 특정 요소 삭제
    ageMap.erase("Bob");

    // 삭제 후 출력
    std::cout << "\nAfter deleting Bob:\n";
    for (const auto& pair : ageMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    // 키 존재 여부 확인
    if (ageMap.find("Bob") == ageMap.end()) {
        std::cout << "Bob is not in the map." << std::endl;
    }

    return 0;
}

출력 결과:

Alice's Age: 30
Alice: 30
Bob: 25
Charlie: 35

After deleting Bob:
Alice: 30
Charlie: 35
Bob is not in the map.

3.3 맵의 활용 사례

  • 데이터베이스 인덱싱: 데이터베이스에서 키를 기반으로 빠르게 데이터를 검색할 때 사용됩니다.
  • 사전 구현: 단어와 그에 대한 설명을 저장하는 사전을 구현할 수 있습니다.
  • 캐싱 시스템: 키를 기반으로 데이터를 캐싱하고 빠르게 조회할 수 있습니다.

3.4 맵의 성능 고려 사항

  • 정렬 오버헤드: 맵은 내부적으로 이진 탐색 트리를 사용하므로, 데이터가 항상 정렬된 상태를 유지합니다. 이는 삽입 및 삭제 시 추가적인 오버헤드를 발생시킬 수 있습니다.
  • 메모리 사용량: 각 노드가 키와 값을 저장하므로, 메모리 사용량이 벡터나 리스트에 비해 더 많을 수 있습니다.

예제: 맵의 정렬 기능 활용

#include <iostream>
#include <map>

int main() {
    std::map<int, std::string> sortedMap;

    // 요소 추가
    sortedMap[3] = "Three";
    sortedMap[1] = "One";
    sortedMap[2] = "Two";

    // 자동 정렬된 맵 출력
    for (const auto& pair : sortedMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

출력 결과:

1: One
2: Two
3: Three

4. 셋 (Set)

셋은 고유한 값을 저장하는 컨테이너로, 중복을 허용하지 않으며 자동으로 정렬됩니다. 내부적으로 이진 탐색 트리를 사용하여 데이터를 관리합니다.

4.1 셋의 주요 특징

  • 고유성: 중복된 값을 허용하지 않습니다.
  • 자동 정렬: 데이터가 자동으로 정렬됩니다.
  • 빠른 탐색: 평균 O(log n) 시간 복잡도로 검색이 가능합니다.
  • 집합 연산: 합집합, 교집합 등의 집합 연산을 쉽게 수행할 수 있습니다.

4.2 셋 사용 예제

#include <iostream>
#include <set>

int main() {
    // 정수형 셋 선언
    std::set<int> mySet;

    // 값 추가
    mySet.insert(10);
    mySet.insert(20);
    mySet.insert(30);
    mySet.insert(20); // 중복된 값은 무시됨

    // 출력
    for (int value : mySet) {
        std::cout << value << " ";
    }

    // 값 찾기
    if (mySet.find(20) != mySet.end()) {
        std::cout << "\n20 is found in the set." << std::endl;
    }

    // 값 삭제
    mySet.erase(10);

    // 크기 확인
    std::cout << "Size of set: " << mySet.size() << std::endl;

    // 집합 연산 예제
    std::set<int> setA = {1, 2, 3};
    std::set<int> setB = {3, 4, 5};
    std::set<int> union_set;

    // 합집합 계산
    union_set.insert(setA.begin(), setA.end());
    union_set.insert(setB.begin(), setB.end());

    // 합집합 출력
    std::cout << "Union of setA and setB: ";
    for (int value : union_set) {
        std::cout << value << " ";
    }

    return 0;
}

출력 결과:

10 20 30 
20 is found in the set.
Size of set: 2
Union of setA and setB: 1 2 3 4 5

4.3 셋의 활용 사례

  • 중복 제거: 리스트나 배열에서 중복된 값을 제거할 때 사용됩니다.
  • 집합 연산: 두 개 이상의 데이터 집합을 비교하거나 결합할 때 유용합니다.
  • 유니크한 데이터 관리: 고유한 데이터만을 저장해야 하는 상황에서 사용됩니다.

4.4 셋의 성능 고려 사항

  • 정렬 오버헤드: 셋은 내부적으로 이진 탐색 트리를 사용하므로, 데이터가 항상 정렬된 상태를 유지합니다. 이는 삽입 및 삭제 시 추가적인 오버헤드를 발생시킬 수 있습니다.
  • 메모리 사용량: 각 노드가 값을 저장하므로, 메모리 사용량이 벡터나 리스트에 비해 더 많을 수 있습니다.

예제: 셋을 이용한 중복 제거

#include <iostream>
#include <set>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 2, 3, 4, 4, 5};
    std::set<int> uniqueNumbers(numbers.begin(), numbers.end());

    // 중복 제거 후 출력
    std::cout << "Unique numbers: ";
    for (int num : uniqueNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

출력 결과:

Unique numbers: 1 2 3 4 5

결론

C++ STL의 벡터, 리스트, 맵, 셋은 각각의 특징과 장점을 가지고 있으며, 상황에 맞게 적절히 활용하면 효율적인 프로그래밍이 가능합니다. 벡터는 빠른 접근이 필요할 때, 리스트는 삽입/삭제가 빈번할 때, 맵은 키-값 쌍을 관리할 때, 셋은 고유한 값을 저장할 때 사용하면 좋습니다. 이러한 컨테이너들을 잘 이해하고 활용하면 C++ 프로그래밍의 효율성을 크게 높일 수 있습니다. 또한, 각 컨테이너의 성능 특성을 이해하고 적절한 상황에 맞게 선택하는 것이 중요합니다.

728x90