[2026] C++ Algorithm Remove | 제거 알고리즘 가이드

[2026] C++ Algorithm Remove | 제거 알고리즘 가이드

이 글의 핵심

C++ remove, remove_if, unique와 erase-remove 관용구. 실제 삭제까지 이어지는 제거 패턴과 주의점을 예제로 정리합니다.

들어가며

STL 제거 알고리즘은 요소를 제거하는 것이 아니라 끝으로 이동합니다. 실제 제거는 erase와 함께 사용하는 erase-remove idiom이 표준 패턴입니다.

코딩 테스트 준비하며 깨달은 것

알고리즘 문제를 풀다 보면 “이게 실무에 무슨 도움이 될까?” 하는 의문이 들 때가 있습니다. 저도 그랬습니다. 하지만 실제 프로젝트에서 성능 문제에 부딪히면, 알고리즘 지식이 얼마나 중요한지 깨닫게 됩니다. 예를 들어, 사용자 검색 기능이 느려서 고민하다가 해시 테이블을 적용하니 응답 시간이 10초에서 0.1초로 줄어든 경험이 있습니다. 코딩 테스트는 단순히 취업을 위한 관문이 아니라, 문제 해결 능력을 키우는 훈련장입니다. 처음엔 브루트 포스로 풀다가, 시간 복잡도를 개선하는 과정에서 “아, 이렇게 생각하면 되는구나” 하는 깨달음을 얻을 때의 쾌감은 말로 표현하기 어렵습니다. 이 글에서는 단순히 정답 코드만 제시하는 게 아니라, 문제를 어떻게 접근하고 최적화하는지 사고 과정을 함께 공유하겠습니다.

1. remove 알고리즘

기본 동작

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
    
    std::cout << "원본: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    // remove: 2를 끝으로 이동
    auto newEnd = std::remove(v.begin(), v.end(), 2);
    
    std::cout << "remove 후: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    std::cout << "크기: " << v.size() << std::endl;  // 7 (변경 안됨!)
    
    // erase: 실제 제거
    v.erase(newEnd, v.end());
    
    std::cout << "erase 후: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    std::cout << "크기: " << v.size() << std::endl;  // 4
    
    return 0;
}

출력: 아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

원본: 1 2 3 2 4 2 5
remove 후: 1 3 4 5 4 2 5  (끝 부분은 쓰레기 값)
크기: 7
erase 후: 1 3 4 5
크기: 4

erase-remove idiom

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
    
    // ✅ erase-remove idiom (한 줄)
    v.erase(std::remove(v.begin(), v.end(), 2), v.end());
    
    for (int x : v) std::cout << x << " ";  // 1 3 4 5
    std::cout << std::endl;
    
    return 0;
}

2. remove_if

조건부 제거

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 짝수 제거
    v.erase(
        std::remove_if(v.begin(), v.end(),  {
            return x % 2 == 0;
        }),
        v.end()
    );
    
    for (int x : v) std::cout << x << " ";  // 1 3 5 7 9
    std::cout << std::endl;
    
    return 0;
}

복잡한 조건

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
struct Person {
    std::string name;
    int age;
};
int main() {
    std::vector<Person> people = {
        {"Alice", 25},
        {"Bob", 17},
        {"Charlie", 30},
        {"David", 15}
    };
    
    // 미성년자 제거
    people.erase(
        std::remove_if(people.begin(), people.end(),  {
            return p.age < 18;
        }),
        people.end()
    );
    
    for (const auto& p : people) {
        std::cout << p.name << " (" << p.age << ")" << std::endl;
    }
    
    return 0;
}

출력:

Alice (25)
Charlie (30)

3. unique

중복 제거

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 1, 2, 2, 2, 3, 3, 4, 5, 5};
    
    std::cout << "원본: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    // ❌ 정렬 안 하면 인접 중복만 제거
    auto v1 = v;
    v1.erase(std::unique(v1.begin(), v1.end()), v1.end());
    
    std::cout << "정렬 안 함: ";
    for (int x : v1) std::cout << x << " ";  // 1 2 3 4 5
    std::cout << std::endl;
    
    // ✅ 정렬 후 중복 제거
    std::vector<int> v2 = {1, 2, 1, 3, 2, 4, 3, 5};
    std::sort(v2.begin(), v2.end());
    v2.erase(std::unique(v2.begin(), v2.end()), v2.end());
    
    std::cout << "정렬 후: ";
    for (int x : v2) std::cout << x << " ";  // 1 2 3 4 5
    std::cout << std::endl;
    
    return 0;
}

커스텀 비교

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <cctype>
int main() {
    std::vector<std::string> words = {"apple", "APPLE", "banana", "Banana"};
    
    // 대소문자 무시 중복 제거
    std::sort(words.begin(), words.end(),  {
        return std::lexicographical_compare(
            a.begin(), a.end(),
            b.begin(), b.end(),
             { return std::tolower(c1) < std::tolower(c2); }
        );
    });
    
    words.erase(
        std::unique(words.begin(), words.end(),  {
            return std::equal(
                a.begin(), a.end(),
                b.begin(), b.end(),
                 { return std::tolower(c1) == std::tolower(c2); }
            );
        }),
        words.end()
    );
    
    for (const auto& word : words) {
        std::cout << word << std::endl;
    }
    
    return 0;
}

출력:

apple
banana

4. 실전 예제

예제 1: 문자열 정리

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <string>
#include <cctype>
#include <iostream>
std::string cleanString(std::string str) {
    // 공백 제거
    str.erase(
        std::remove_if(str.begin(), str.end(),  {
            return std::isspace(c);
        }),
        str.end()
    );
    
    return str;
}
std::string removeNonAlpha(std::string str) {
    // 알파벳 아닌 문자 제거
    str.erase(
        std::remove_if(str.begin(), str.end(),  {
            return !std::isalpha(c);
        }),
        str.end()
    );
    
    return str;
}
int main() {
    std::string text1 = "  Hello   World  ";
    std::cout << "[" << cleanString(text1) << "]" << std::endl;
    // [HelloWorld]
    
    std::string text2 = "Hello123World456";
    std::cout << "[" << removeNonAlpha(text2) << "]" << std::endl;
    // [HelloWorld]
    
    return 0;
}

예제 2: 데이터 필터링

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
struct Product {
    std::string name;
    double price;
    bool inStock;
};
int main() {
    std::vector<Product> products = {
        {"Laptop", 1200.0, true},
        {"Mouse", 25.0, false},
        {"Keyboard", 75.0, true},
        {"Monitor", 300.0, false},
        {"Headset", 80.0, true}
    };
    
    // 재고 없는 제품 제거
    products.erase(
        std::remove_if(products.begin(), products.end(),  {
            return !p.inStock;
        }),
        products.end()
    );
    
    std::cout << "재고 있는 제품:" << std::endl;
    for (const auto& p : products) {
        std::cout << "- " << p.name << ": $" << p.price << std::endl;
    }
    
    return 0;
}

출력: 다음은 간단한 code 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

재고 있는 제품:
- Laptop: $1200
- Keyboard: $75
- Headset: $80

예제 3: 중복 제거

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
    
    std::cout << "원본: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    // 정렬 후 중복 제거
    std::sort(v.begin(), v.end());
    v.erase(std::unique(v.begin(), v.end()), v.end());
    
    std::cout << "중복 제거: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    return 0;
}

출력:

원본: 3 1 4 1 5 9 2 6 5 3
중복 제거: 1 2 3 4 5 6 9

5. 자주 발생하는 문제

문제 1: erase 누락

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 3, 2, 4};
    
    // ❌ erase 없음 (크기 변경 안됨)
    auto newEnd = std::remove(v.begin(), v.end(), 2);
    
    std::cout << "크기: " << v.size() << std::endl;  // 5 (변경 안됨!)
    std::cout << "내용: ";
    for (int x : v) std::cout << x << " ";  // 1 3 4 2 4 (쓰레기 값)
    std::cout << std::endl;
    
    // ✅ erase 호출
    v = {1, 2, 3, 2, 4};
    v.erase(std::remove(v.begin(), v.end(), 2), v.end());
    
    std::cout << "크기: " << v.size() << std::endl;  // 3
    std::cout << "내용: ";
    for (int x : v) std::cout << x << " ";  // 1 3 4
    std::cout << std::endl;
    
    return 0;
}

문제 2: unique 정렬

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 1, 3, 2};
    
    // ❌ 정렬 안 됨 (인접 중복만 제거)
    auto v1 = v;
    v1.erase(std::unique(v1.begin(), v1.end()), v1.end());
    
    std::cout << "정렬 안 함: ";
    for (int x : v1) std::cout << x << " ";  // 1 2 1 3 2 (중복 남음)
    std::cout << std::endl;
    
    // ✅ 정렬 후
    auto v2 = v;
    std::sort(v2.begin(), v2.end());
    v2.erase(std::unique(v2.begin(), v2.end()), v2.end());
    
    std::cout << "정렬 후: ";
    for (int x : v2) std::cout << x << " ";  // 1 2 3
    std::cout << std::endl;
    
    return 0;
}

문제 3: 성능

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // ❌ 여러 번 호출 (비효율)
    v.erase(std::remove(v.begin(), v.end(), 2), v.end());
    v.erase(std::remove(v.begin(), v.end(), 4), v.end());
    v.erase(std::remove(v.begin(), v.end(), 6), v.end());
    
    // ✅ 한 번에 (효율적)
    v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    v.erase(
        std::remove_if(v.begin(), v.end(),  {
            return x == 2 || x == 4 || x == 6;
        }),
        v.end()
    );
    
    for (int x : v) std::cout << x << " ";  // 1 3 5 7 8 9 10
    std::cout << std::endl;
    
    return 0;
}

문제 4: list vs vector

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <list>
#include <iostream>
int main() {
    // vector: erase-remove idiom
    std::vector<int> vec = {1, 2, 3, 2, 4};
    vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());
    
    std::cout << "vector: ";
    for (int x : vec) std::cout << x << " ";
    std::cout << std::endl;
    
    // list: remove 멤버 함수 (더 효율적)
    std::list<int> lst = {1, 2, 3, 2, 4};
    lst.remove(2);  // O(n), 재할당 없음
    
    std::cout << "list: ";
    for (int x : lst) std::cout << x << " ";
    std::cout << std::endl;
    
    return 0;
}

6. C++20 std::erase

간결한 제거

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
    
    // C++20: std::erase (간결)
    std::erase(v, 2);
    
    for (int x : v) std::cout << x << " ";  // 1 3 4 5
    std::cout << std::endl;
    
    return 0;
}

std::erase_if

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <iostream>
int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // C++20: std::erase_if (간결)
    std::erase_if(v,  { return x % 2 == 0; });
    
    for (int x : v) std::cout << x << " ";  // 1 3 5 7 9
    std::cout << std::endl;
    
    return 0;
}

7. 실전 예제: 데이터 정리 유틸리티

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <string>
#include <cctype>
#include <iostream>
class DataCleaner {
public:
    // 빈 문자열 제거
    static void removeEmpty(std::vector<std::string>& vec) {
        vec.erase(
            std::remove_if(vec.begin(), vec.end(),  {
                return s.empty();
            }),
            vec.end()
        );
    }
    
    // 중복 제거
    static void removeDuplicates(std::vector<int>& vec) {
        std::sort(vec.begin(), vec.end());
        vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
    }
    
    // 범위 밖 값 제거
    static void removeOutOfRange(std::vector<int>& vec, int min, int max) {
        vec.erase(
            std::remove_if(vec.begin(), vec.end(), [min, max](int x) {
                return x < min || x > max;
            }),
            vec.end()
        );
    }
    
    // 공백 문자 제거
    static std::string removeWhitespace(std::string str) {
        str.erase(
            std::remove_if(str.begin(), str.end(),  {
                return std::isspace(c);
            }),
            str.end()
        );
        return str;
    }
    
    // 연속 공백을 하나로
    static std::string normalizeSpaces(std::string str) {
        // 앞뒤 공백 제거
        auto start = std::find_if_not(str.begin(), str.end(),  {
            return std::isspace(c);
        });
        auto end = std::find_if_not(str.rbegin(), str.rend(),  {
            return std::isspace(c);
        }).base();
        
        str = std::string(start, end);
        
        // 연속 공백을 하나로
        str.erase(
            std::unique(str.begin(), str.end(),  {
                return std::isspace(a) && std::isspace(b);
            }),
            str.end()
        );
        
        return str;
    }
};
int main() {
    // 빈 문자열 제거
    std::vector<std::string> strings = {"hello", "", "world", "", "test"};
    DataCleaner::removeEmpty(strings);
    std::cout << "빈 문자열 제거: ";
    for (const auto& s : strings) std::cout << s << " ";
    std::cout << std::endl;
    
    // 중복 제거
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
    DataCleaner::removeDuplicates(numbers);
    std::cout << "중복 제거: ";
    for (int x : numbers) std::cout << x << " ";
    std::cout << std::endl;
    
    // 범위 밖 제거
    std::vector<int> values = {1, 5, 10, 15, 20, 25, 30};
    DataCleaner::removeOutOfRange(values, 10, 20);
    std::cout << "범위 밖 제거: ";
    for (int x : values) std::cout << x << " ";
    std::cout << std::endl;
    
    // 공백 제거
    std::string text = "  Hello   World  ";
    std::cout << "공백 제거: [" << DataCleaner::removeWhitespace(text) << "]" << std::endl;
    
    // 공백 정규화
    std::cout << "공백 정규화: [" << DataCleaner::normalizeSpaces(text) << "]" << std::endl;
    
    return 0;
}

출력: 아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

빈 문자열 제거: hello world test
중복 제거: 1 2 3 4 5 6 9
범위 밖 제거: 10 15 20
공백 제거: [HelloWorld]
공백 정규화: [Hello World]

정리

핵심 요약

  1. remove: 끝으로 이동 (크기 변경 안됨)
  2. erase: 실제 제거 (크기 변경)
  3. erase-remove: v.erase(remove(...), v.end())
  4. unique: 인접 중복 제거 (정렬 필요)
  5. C++20: std::erase, std::erase_if

제거 알고리즘 비교

알고리즘동작시간복잡도사용 시기
remove값 제거O(n)특정 값
remove_if조건 제거O(n)조건부
unique중복 제거O(n)정렬 후
erase실제 제거O(n)remove 후
list::remove직접 제거O(n)list 전용

실전 팁

사용 원칙:

  • remove는 항상 erase와 함께
  • unique는 정렬 후 사용
  • list는 멤버 함수 remove 사용
  • C++20은 std::erase 사용 성능:
  • 여러 조건은 remove_if로 한 번에
  • list는 멤버 함수가 더 효율적
  • vector는 erase-remove idiom
  • 대량 제거는 새 컨테이너 생성 고려 주의사항:
  • remove만 호출하면 쓰레기 값 남음
  • unique는 인접 중복만 제거
  • 반복자 무효화 주의
  • 빈 범위 체크

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3