본문 바로가기
SWE/코테

Selection Problem 선택 알고리즘

by S나라라2 2018. 11. 7.
반응형

Selection Problem 선택 알고리즘



Q. n개의 원소 중 k번째로 큰 숫자는 무엇인가?

{45, 33, 21, 5, 6, 8, 63, 54, 76, 82}


선택 알고리즘이란 다음과 같은 문제가 주어졌을 때, O(n) 시간 복잡도를 가지는 풀이법이다.

선택 알고리즘을 설명하기에 앞서, 다른 방식으로 문제를 해결할 때의 시간 복잡도를 확인해보자.


방법1.  가장 큰 숫자를 찾고, 그 숫자를 제외한 나머지에서 가장 큰 숫자를 찾고, ... 이를 반복하는 방법.

- k번째 큰 숫자를 찾을 때?

1번째 큰 수는 n번의 비교,

2번째 큰 수는 n-1번의 비교,

...

k번째 큰 수는 n-(k-1)번 비교 => 따라서, O(kn)


- k=1번째 큰 수는 O(n)

- k=n번째 큰 수는 O(n^2) 


방법2. 모든 수를 정렬하고 k번째 숫자를 결정하는 방법.

정렬할 때, Bubble, insert등의 sort를 사용하면 O(n^2)를 가지고,

Quick, Selection sort를 사용하면 O(nlogn)의 시간복잡도를 가진다. 


그러나 이 방법은 불필요한 부분까지 정렬을 하게되고 지금 하게 될 선택 알고리즘보다 느리다.



개념


선택 알고리즘은 Quick sort에서 pivot이 자리를 결정하는 과정을 응용한 방법이다.

pivot이 자신의 자리를 찾아가고 원소의 갯수가 n/2으로 나눠지면서 그 절반만(혹은 절반보다 큰 양) 확인을 해보면 된다.

따라서 O(n)의 시간복잡도를 가진다.


예시.

{45, 33, 21, 5, 6, 8, 63, 54, 76, 82} 에서 k=4번째로 큰 수는 무엇인가?



1. 첫 번째 원소인 pivot이 자신의 위치를 찾아간다.

2. pivot이 6번째 위치로 옮겨가고, pivot을 기준으로 왼쪽만 확인하면 된다. (k=4 < pivot=6)

3. 왼쪽 부분 배열에서 pivot을 새로 선정하고, 자신의 위치를 찾아간다.

4. pivot이 3번째 위치로 옮겨가고, pivot을 기준으로 오른쪽만 확인하면 된다. (k=4 > pivot=3)

   오른쪽 부분 배열에서 pivot을 새로 선정하고, 자신의 위치를 찾아가는데, 자신의 위치가 4이다.(찾으려는 숫자)

   끝~



시간 복잡도 계산

pivot에 의해 나눠지는 2개 부분이 원래 길이의 3/4 미만이 될 때, good division임. -> 근데 이유모르겠음



c언어 구현 코드


#include <stdio.h>

#define SZ 10

int numbers[SZ] = {45,33,21,5,6,
					8,63,54,76,82};

// _which : 찾고자 하는 숫자의 index -> _which번째 큰 수
void doSelection(int _pivot, int _left, int _right, int _which) {
	
	// 탈출조건
	if ( (_pivot <0 || _pivot>=SZ) ||
		(_left <0 || _left >= SZ) ||
		(_right <0 || _right >= SZ)) {
		printf("exit:: found location %d, which is %d\n", _which+1, numbers[_which]);
		return;
	}

	// 기본적인 quick sort
	int left = _left;
	int right = _right;

	while (1) {
		// left를 이동 : pivot보다 작은 숫자들을 skip
		while (numbers[_pivot] > numbers[left]) {
			left++;
		}
		// right를 이동 : pivot보다 큰 숫자들을 skip
		while (numbers[_pivot] < numbers[right]) {
			right--;
		}

		// left와 right의 자리를 바꿔준다.
		if (left < right) { 
			int temp = numbers[left];
			numbers[left] = numbers[right];
			numbers[right] = temp;
		}
		else {
			if (right==_which) { // 내가 찾고자 하는 위치 which가 right와 동일
				printf("found %d number is %d\n",right+1,numbers[_pivot]);
				return;
			}
			else {
				// 일단 pivot과 right의 위치를 바꾸고
				int temp = numbers[_pivot];
				numbers[_pivot] = numbers[right];
				numbers[right] = temp;
				// 내가 찾고자하는 _which가 right의 왼쪽인지, 오른쪽인지 검사
				if (_which < right) { // 왼쪽 부분 배열만 확인하면 된다.
					doSelection(_pivot,_pivot+1,right-1,_which);
				}
				else { // 오른쪽 부분 배열만 확인하면 된다.
					doSelection(right+1,right+2,_right,_which);
				}
				return;
			}
		}
	}
}

int main(void) {

	doSelection(0, 1, SZ - 1, 3); // 4번째로 큰수 -> 인자 3

	return 0;
}


doSelection()함수의 가장 첫 부분 탈출조건에서 k번째 수를 출력해주는 이유?


탈출조건에 doSelection 함수가 들어온 경우는 내가 찾는 k번째 수가 포함된 부분배열이 모두 정렬되었다는 의미이다. 

따라서 바로 배열의 k인덱스로 참조하여 출력할 수 있다.


예시) 아래와 같은 원소들 중에서, k=10번째 수는?

doSelection의 함수인자 중에서 left가 배열사이즈를 넘어가기때문에 탈출조건에 들어간다.



반응형