카운팅 정렬 (Counting Sort)

 

수의 범위가 어느정도 한정적일 때에만 사용할 수 있다.

대략적으로 수의 범위가 천만 이하일때 사용한다고 보면 된다.

 

15688: 수 정렬하기 5

더보기
#include <iostream>

using namespace std;

int csarr[2000001];
int n;

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		int t;
		cin >> t;
		csarr[t + 1000000]++;
	}

	for (int i = 0; i < 2000001; ++i)
	{
		while (csarr[i]--)
		{
			cout << i - 1000000 << '\n';
		}
	}

	return 0;
}

 

기수 정렬 (Radix Sort)

 

자릿수를 이용해서 정렬을 수행하는 알고리즘.

각 자리수에 대해 카운팅 정렬을 수행하는 알고리즘이라고 볼 수도 있다.

자릿수가 D일때 시간복잡도는 O(DK)이다.

 

기수 정렬의 최대 단점은 N의 크기에 따라 공간 복잡도가 비례해서 증가한다는 점이다.

이 문제를 어느정도 해결하려면 각 리스트를 vector 또는 연결 리스트로 사용해야 하는데 vector나 연결 리스트나 둘다 STL이 없으면 구현이 많이 까다롭고, STL을 쓸수 있으면 STL sort 함수를 쓰는게 모든면에서 낫다.

 

한마디로 구현을 해야 할 상황이 아예 존재하지 않는다. 개념만 이해하고 STL을 쓸 수 없는 상황에서는 병합 정렬이나 힙 정렬을 이용하면 된다.

 

STL sort

 

퀵 정렬을 기반으로 하지만 리스트가 불균등하게 쪼개지는 상황이 반복되어 재귀의 깊이가 너무 깊어지면 O(NlogN)이 보장되는 알고리즘으로 나머지를 처리한다. 그렇기 때문에 최악의 경우에도 O(NlogN)이 보장된다.

퀵 정렬을 기반으로 한 만큼 Unstable Sort이다.

꼭 Stable Sort가 필요하다면 stable_sort 를 사용하면 된다.

 

pair, tuple을 정렬하는 경우 앞의 원소의 대소를 비교하고 값이 같으면 그 뒤의 원소의 대소를 비교하는 방식으로 처리한다.

 

비교 함수를 내가 정해서 넘겨줄 수도 있다.

 

비교 함수는 두 값이 같을 때(또는 우선순위가 같을 때) 반드시 false를 반환해주어야 한다.

인자로 STL 또는 객체 전달시 reference를 사용해야 한다. 그래야 불필요한 공간을 사용하지 않기 때문이다.

 

custom compare 함수를 직접 만들 때, 2개의 인자를 받을 수 있다.

순서대로 n+1, n이며 return이 true인 경우만 값의 교환이 일어난다.

 

번외 : Comparison Sort, Non-comparison Sort

 

버블, 병합, 퀵 정렬은 Comparison Sort

카운팅, 기수 정렬은 Non-comparison Sort

 

코딩 테스트에서 STL 사용을 의도적으로 막지 않은 이상, 정렬을 구현하고 앉아있으면 호구다.

 

연습 문제

 

1431: 시리얼 번호

더보기
#include <iostream>
#include <algorithm>
#include <string>

using namespace std;

string s[50];

// a는 n+1, b는 n이 들어감
// true인 경우만 교환이 이뤄진다
bool comp(const string& a, const string& b)
{
	if (a.length() != b.length())
	{
		return a.length() < b.length();
	}
	else if (a.length() == b.length())
	{
		int asum = 0;
		int bsum = 0;

		for (auto i = 0; i < a.length(); ++i)
		{
			if (isdigit(a[i])) asum += a[i] - '0';
			if (isdigit(b[i])) bsum += b[i] - '0';
		}
		if (asum != bsum)
			return asum < bsum;
	}
	return a < b;
}

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;

	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		cin >> s[i];
	}

	sort(s, s + n, comp);

	for (int i = 0; i < n; ++i)
	{
		cout << s[i] << '\n';
	}

	return 0;
}

 

custom compare 함수의 인자를 뭘로 받았는지, true/false시 어떻게 돌아가는지 몰라서 조금 찾아보고 금방 이해함

 

11652: 카드

더보기
#include <iostream>
#include <algorithm>

using namespace std;

using ll = long long;
ll arr[100001];

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;
	int cnt = 1, mxcnt = 1;
	ll mxval;

	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		cin >> arr[i];
	}

	sort(arr, arr + n);

	arr[n] = (1 << 62) + 1;

	mxval = arr[0];

	for (int i = 0; i < n; ++i)
	{
		if (arr[i] == arr[i + 1])
		{
			cnt++;
		}
		else
		{
			if (mxcnt < cnt)
			{
				mxcnt = cnt;
				mxval = arr[i];
			}
			cnt = 1;
		}
	}

	cout << mxval;

	return 0;
}

 

기본 문제

 

5648: 역원소 정렬

더보기
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;
using ll = long long;

vector<ll> arr;

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;

	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		string t;
		cin >> t;
		reverse(t.begin(), t.end());
		arr.push_back(stoll(t));
	}

	sort(arr.begin(), arr.end());

	for (auto& item : arr)
	{
		cout << item << '\n';
	}

	return 0;
}

 

 1181: 단어 정렬

더보기
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

vector<string> arr;

bool comp(const string& a, const string& b)
{
	if (a.length() != b.length())
	{
		return a.length() < b.length();
	}

	return a < b;
}

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;

	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		string t;
		cin >> t;
		arr.push_back(t);
	}

	sort(arr.begin(), arr.end(), comp);

	for (int i = 0; i < n - 1; ++i)
	{
		if (arr[i] == arr[i + 1]) continue;
		cout << arr[i] << '\n';
	}
	cout << arr.back();

	return 0;
}

 

 2910: 빈도 정렬

더보기
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<pair<int, int>> v;

bool comp(const pair<int, int>& a, const pair<int, int>& b)
{
	return a.second > b.second;
}

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n, c;

	cin >> n >> c;

	for (int i = 0; i < n; ++i)
	{
		int t;
		cin >> t;

		// find를 사용시 pair 구조체의 멤버함수에 operator== 가 정의되어 있지 않아서 문제가 생긴다.
		// 이런경우 find_if로 조건을 직접 만든 메소드를 인자로 넘겨줘야 한다
		// sort의 custom compare와 같은 개념이다
		auto iter = find_if(v.begin(), v.end(), [&t](const pair<int, int>& elem) { return elem.first == t; });

		if (iter != v.end())
		{
			(*iter).second += 1;
		}
		else
		{
			v.push_back({ t, 1 });
		}
	}

	stable_sort(v.begin(), v.end(), comp);

	for (auto& elem : v)
	{
		for (int i = 0; i < elem.second; ++i)
		{
			cout << elem.first << ' ';
		}
	}

	return 0;
}

 

람다식과 stable_sort를 같이 사용함

 

10814: 나이순 정렬

더보기
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

vector<pair<int, string>> arr;

bool comp(const pair<int, string>& a, const pair<int, string>& b)
{
	return a.first < b.first;
}

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;

	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		int t;
		string ts;
		cin >> t >> ts;
		arr.push_back({ t, ts });
	}

	stable_sort(arr.begin(), arr.end(), comp);

	for (auto& data : arr)
	{
		cout << data.first << ' ' << data.second << '\n';
	}

	return 0;
}

 

stable_sort만 주의하면 큰 문제 없다.

 

11656: 접미사 배열

더보기
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

vector<string> v;

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	string s;

	cin >> s;

	for (auto i = 0; i < s.length(); ++i)
		v.push_back(s.substr(i, s.length()-i));

	sort(v.begin(), v.end());

	for (auto& elem : v)
		cout << elem << '\n';

	return 0;
}

 

string의 substr 사용. 해당 문자열의 1번째 인자 시작 위치부터 2번째 인자의 갯수만큼 반환한다.

 

10825: 국영수

더보기
#include <iostream>
#include <string>
#include <vector>
#include <tuple>
#include <algorithm>

using namespace std;
using Elements = pair<string, tuple<int, int, int>>;

vector<Elements> v;

bool comp(const Elements& a, const Elements& b)
{
	if (get<0>(a.second) != get<0>(b.second))
		return get<0>(a.second) > get<0>(b.second);
	else if (get<1>(a.second) != get<1>(b.second))
		return get<1>(a.second) < get<1>(b.second);
	else if (get<2>(a.second) != get<2>(b.second))
		return get<2>(a.second) > get<2>(b.second);
	return a.first < b.first;
}

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n;
	cin >> n;

	while (n--)
	{
		string name;
		int lan, eng, mat;

		cin >> name >> lan >> eng >> mat;

		v.push_back({ name, {lan, eng, mat} });
	}

	sort(v.begin(), v.end(), comp);

	for (auto& elem : v)
		cout << elem.first << '\n';

	return 0;
}

 

comp함수의 조건을 잘 조절하는게 포인트

 

응용 문제

 

7795: 먹을 것인가 먹힐 것인가

더보기
#include <iostream>
#include <algorithm>

using namespace std;

int arrA[20001];
int arrB[20001];

int main()
{
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int tc;
	cin >> tc;

	while (tc--)
	{
		int n, m;
		size_t answer = 0;

		cin >> n >> m;

		for (int i = 0; i < n; ++i)
			cin >> arrA[i];
		for (int i = 0; i < m; ++i)
			cin >> arrB[i];

		sort(arrB, arrB + m);

		for (int i = 0; i < n; ++i)
			answer += lower_bound(arrB, arrB + m, arrA[i]) - arrB;
		cout << answer << '\n';
	}

	return 0;
}

 

바킹독님 얘기로는 이분 탐색을 쓰지 않고 정렬만으로도 풀수 있다고 했다.

나중에 고민해보기로.

'알고리즘 > 바킹독의 실전 알고리즘' 카테고리의 다른 글

[0x11] 그리디  (0) 2022.08.04
[0x10] 다이나믹 프로그래밍  (0) 2022.08.03
[0x0E] 정렬 I  (0) 2022.08.02
[0x0D] 시뮬레이션  (0) 2022.08.01
[0x0C] 백트래킹  (0) 2022.07.31

+ Recent posts