C++ 다중 기준 정렬 구현 방법

이원 정렬의 개념

알고리즘 문제 풀이 과정에서 두 가지 이상의 데이터를 묶어서 정렬해야 하는 상황을 자주 접하게 됩니다. 예를 들어, 길이가 $n$인 두 수열 $A = \{a_1, a_2, \dots, a_n\}$과 $B = \{b_1, b_2, \dots, b_n\}$이 주어졌을 때, 먼저 $a_i$를 기준으로 오름차순 정렬하고, $a_i$ 값이 같다면 $b_i$를 기준으로 다시 오름차순 정렬하는 방식입니다. 이를 C++에서 구현하는 다양한 방법을 정리합니다.

구조체와 std::sort 활용

가장 범용적인 방법은 데이터를 구조체(struct)로 묶고 std::sort 함수를 사용하는 것입니다. 비교 로직을 정의하는 방식에 따라 크게 세 가지로 나뉩니다.

1. 외부 비교 함수 정의

별도의 비교 함수를 만들어 std::sort의 세 번째 인자로 전달합니다.

struct Element {
    int primary;
    int secondary;
};

bool compareElements(const Element& lhs, const Element& rhs) {
    if (lhs.primary == rhs.primary) {
        return lhs.secondary < rhs.secondary;
    }
    return lhs.primary < rhs.primary;
}

// 사용 예시
Element arr[MAX_N];
std::sort(arr, arr + n, compareElements);

2. 연산자 오버로딩 (Operator Overloading)

구조체 내부에 < 연산자를 직접 정의하여 std::sort가 기본적으로 이 규칙을 사용하도록 합니다.

struct Point {
    int x, y;

    // 상수 멤버 함수로 정의
    bool operator<(const Point& other) const {
        if (x != other.x) {
            return x < other.x;
        }
        return y < other.y;
    }
};

Point pts[MAX_N];
std::sort(pts, pts + n);

std::pair 활용

std::pair는 이미 내부적으로 first를 우선 비교하고, 값이 같을 경우 second를 비교하도록 < 연산자가 구현되어 있습니다. 별도의 구조체 정의 없이 간단하게 이원 정렬을 수행할 수 있습니다.

#include <vector>
#include <algorithm>
#include <utility>

std::vector<std::pair<int, int>> vec;
for (int i = 0; i < n; ++i) {
    int u, v;
    std::cin >> u >> v;
    vec.push_back({u, v});
}

std::sort(vec.begin(), vec.end());

STL 컨테이너별 정렬 특성

데이터의 저장 방식과 목적에 따라 정렬을 유지하는 컨테이너를 선택할 수 있습니다.

std::priority_queue (우선순위 큐)

데이터를 삽입할 때마다 정렬된 상태를 유지하지만, 가장 큰 값(기본 설정)부터 접근 가능하며 임의 접근은 불가능합니다.

// std::pair를 사용하면 자동으로 first, second 순으로 우선순위 결정
std::priority_queue<std::pair<int, int>> pq;

pq.push({10, 20});
pq.push({10, 5});

// pq.top()은 {10, 20}을 반환

std::set / std::multiset

균형 이진 탐색 트리(Red-Black Tree) 기반의 컨테이너로, 삽입과 동시에 정렬이 이루어집니다. std::set은 중복을 제거하며, std::multiset은 중복을 허용합니다.

struct Node {
    int val1, val2;
    bool operator<(const Node& o) const {
        if (val1 == o.val1) return val2 < o.val2;
        return val1 < o.val1;
    }
};

std::set<Node> s;
s.insert({1, 5});
s.insert({1, 2});

for (auto it = s.begin(); it != s.end(); ++it) {
    std::cout << it->val1 << " " << it->val2 << std::endl;
}

시간 복잡도 분석

위에서 소개한 std::sort, std::priority_queue, std::set을 이용한 방식은 모두 데이터 $N$개에 대해 $O(N \log N)$의 시간 복잡도를 가집니다. 다만, 데이터의 동적 추가가 빈번하다면 set이나 priority_queue가 유리하고, 한 번에 모든 데이터를 받아 정렬한다면 std::vectorstd::sort를 조합하는 방식이 메모리와 속도 측면에서 가장 효율적입니다.

태그: C++ STL sorting algorithm std::pair

6월 4일 17:03에 게시됨