버블 정렬에 이어서 선택 정렬에 대해 알아보겠습니다.

 

선택 정렬의 아이디어도 간단합니다.

1. 첫 번째 데이터부터 탐색해 가장 작은 수를 찾아 첫 번째 위치로 옮긴다. (오름, 내림차순에 따라 다릅니다.)

2. 이제 첫 번째 위치는 정렬되었으니, 두 번째 데이터부터 탐색한다.

3. 정렬이 완료될때까지 계속 반복한다!

 

선택 정렬도 한 번 비교가 이루어질 때마다 데이터가 1개씩 정렬되므로 N-1, N-2, N-3 ... 2, 1 번 비교를 해야 합니다.

전체적인 비교의 횟수는 (N*(N-1))/2가 되고, 시간 복잡도는 O(N^2)와 같이 나타냅니다.

다만, 매 번 데이터의 교체가 필요한 버블정렬과 달리 선택 정렬은 위치를 찾은 뒤 마지막에 한 번만 교체하면 되니 버블 정렬보다는 빠르게 정렬이 이루어집니다.

대신 선택정렬의 경우는 stable하지 않은 불안정 정렬인데, 이 내용은 차후에 다루도록 하겠습니다.

그럼 그림을 통해 선택 정렬의 진행을 알아보도록 하겠습니다.

정렬 시작 전의 상태

그림에선 8칸의 배열에 각각 3,2,5,7,6,8,1,4의 값이 들어있습니다.

이제 이 배열을 선택 정렬을 통해 정렬해 보겠습니다.

우선 우리가 찾아야 할 데이터의 위치는 첫 번째, 즉 가장 작은 수를 찾아야 합니다.

반복 시행의 첫 시점에서 우리가 알고 있는 정보는 현 위치뿐이므로 가장 작은 수는 3이고 이 수의 위치는 0번 인덱스입니다.

다음 수로 진행하면서 전체를 탐색해 가장 작은 수를 찾아봅시다.

두번째 위치에서 min값이 갱신되면서 인덱스도 1번으로 바뀝니다.
세번째 수인 5는 2보다 크므로 갱신되지 않습니다.

뒤의 수인 7,6,8도 2보다 크므로 계속 진행되다가 일곱번째 수인 1에서 최솟값이 갱신됩니다.

모든 수를 탐색했습니다!

이제 첫번째 시행이 끝났습니다!

우리가 찾는 수는 첫 번째 위치에 올 수, 가장 작은 수이며 그 수의 위치는 6번째 인덱스입니다.

이제 0번과 6번 인덱스의 수를 바꿉니다.

첫 번째 수의 정렬이 완료되었습니다! 

이제 우리는 두 번째 자리에 위치할 수, 즉 일곱 개의 데이터 중에서 가장 작은 수를 찾으려 합니다.

두번째 시행의 스타트!

시행을 마쳤습니다!

두 번째 위치, 인덱스 1에 들어있는 2가 현재 정렬되지 않은 가장 작은 수였습니다.

교환은 일어나지 않고, 우리는 두 개의 수를 정렬시켰습니다.

같은 방법으로 세 번째 시행도 진행시켜 봅시다.

6번째 인덱스에서 정렬되지 않은 최솟값, 3을 찾았습니다.
우리가 찾고있던 세번째 위치와 바꿔줍니다.

이로서 우리는 1,2,3 세 개의 수를 정렬시켰습니다.

앞으로 5번의 시행도 같은 방식으로 진행하면 되지만 과정을 기록하지 않겠습니다.

머릿속으로 테스트를 해보시고 정렬 후의 모습과 비교해보시기 바랍니다.

 

4번째 시행 후
5번째 시행 후
6번째 시행 후
7번째 시행 후
8번째 시행 후

이렇게 선택 정렬을 통한 정렬이 완료되었습니다!

 

 

아래는 JAVA로 구현한 간단한 선택정렬 코드와 테스트입니다!

이것저것 바꿔보고 디버깅하면서 흐름이 이해되시길 바랍니다.

import java.util.Arrays;
import java.util.Random;

public class sort_02_selection {
	static int size = 10;
	static int bound = 1000;
	// 데이터의 갯수와 범위 설정

	public static void main(String[] args) {
		int[] data = new int[size];
		Random random = new Random();
		int count = 0;

		for (int i = 0; i < size; i++) {
			data[i] = random.nextInt(bound);
		}
		// 랜덤 값 넣어주기

		System.out.println(Arrays.toString(data));
		// 랜덤하게 들어간 데이터 확인

		/////////////////////// 선택정렬 구현////////////////////////////
		for (int find = 0; find < size; find++) {
			int minIdx = find;
			for (int now = find; now < size; now++) {
				if(data[now] < data[minIdx]) {
					minIdx = now;
				}
				count++;
			}
			int save = data[find];
			data[find] = data[minIdx];
			data[minIdx] = save;
		}
		//////////////////////////////////////////////////////

		System.out.println(Arrays.toString(data));
		System.out.println("비교횟수 : " + count);
		// 정렬 후 데이터와 비교횟수 확인

		int[][] data2 = { { 1, 1 }, { 3, 1 }, { 5, 1 }, { 7, 1 }, { 9, 1 }, { 1, 2 }, { 2, 2 }, { 4, 2 }, { 1, 3 },
				{ 7, 2 }, { 9, 3 }, { 3, 3 }, { 4, 3 }, { 8, 3 }, { 6, 3 } };
		
		for (int find = 0; find < 15; find++) {
			int minIdx = find;
			for (int now = find; now < 15; now++) {
				if(data2[now][0] < data2[minIdx][0]) {
					minIdx = now;
				}
				count++;
			}
			int[] save = data2[find].clone();
			data2[find] = data2[minIdx].clone();
			data2[minIdx] = save.clone();
		}

		for (int i = 0; i < 15; i++) {
			System.out.printf("정렬된 수 : %d, stable: %d\n", data2[i][0], data2[i][1]);
		}
		// stable 정렬이 아님!
	}
}
728x90

알고리즘 공부를 하며 배운 여러 자료구조, 알고리즘 등을 정리해보려고 합니다.

정렬 알고리즘들은 기본적인 알고리즘이자 정말 많이 활용되는 알고리즘입니다.

보통 언어의 기본 라이브러리 안에서 구현이 되어있어 호출로 간단히 사용할 수 있지만 직접 구현해야 하는 경우도 있고 무엇보다 어떤 식으로 돌아가는지를 알아야 다른 방법으로도 활용할 수 있습니다.

오늘은 정렬 중에서도 가장 쉽게 구현 가능한 버블정렬에 대해 알아보겠습니다.

 

버블정렬의 아이디어는 간단합니다. 

1. N개의 데이터가 있을 때, N-1번의 비교를 통해 가장 뒤에 (방향에 따라 달라집니다.) 위치할 데이터를 알아내자. 

2. 이제 하나의 데이터는 자기 자리에 있으니 나머지 N-1개의 데이터를 가지고 비교를 해보자.

3. 정렬이 완료될 때까지 계속 반복하자!

 

그럼 한 번 비교가 이루어질때마다 1개씩 정렬되므로 N-1, N-2, N-3 ... 2,1 번 비교를 해야 합니다.

전체적인 비교의 횟수는 (N*(N-1))/2가 되는데, 시간 복잡도를 말할 때는 보통 작은 차수나 계수를 지우고 표현하기 때문에 O(N^2)와 같이 나타냅니다.

예를 들어 시간 복잡도가 2N인 프로그램과 3N인 프로그램, N^2번인 프로그램이 있다고 생각해봅시다.
데이터의 개수 N이 5개이고 하나를 처리할 때 1초가 걸린다고 가정하면 각각의 프로그램은 실행되는데 10초, 15초, 25초가 걸리게 됩니다. 이렇게 보면 계수의 차이가 유의미해 보이지만 보통 처리해야 할 데이터의 개수는 적게는 수백 개에서 수십억 개까지 늘어납니다.
1만 개의 데이터를 처리한다고 생각해봐도 첫 번째 프로그램은 2만 초로 실행에 대략 5시간이 걸리고 두 번째 프로그램은 3만 초, 약 8시간이 걸립니다.
하지만 세 번째 프로그램은 2만 7천 시간, 1157일이 필요합니다. 
자잘한 계수의 비교는 크게 의미가 없는 것이죠.

그림을 통해 버블 정렬의 진행을 알아보도록 하겠습니다.

 

정렬 시작 전의 상태

그림에선 8칸짜리 배열에 8,5,3,2,1,6,4,7의 값이 들어 있습니다.

버블정렬을 통해 이 배열의 데이터들을 오름차순으로 정렬해 보겠습니다.

 

첫 번째 데이터부터 차근차근 비교해봅시다.

8개의 데이터가 있으므로 첫 반복에서 우리는 N-1번, 즉 7번의 비교를 해야 합니다. 

지금 보고 있는 데이터와 다음 데이터를 비교해 더 크다면 위치를 바꿔줍니다.

첫 번째 데이터인 8이 두번째 데이터인 5보다 크기 때문에 8과 5의 위치를 바꿔줍시다.

 

 

8과 3을 비교해 크다면 위치를 바꿉니다

이제 5와 8의 위치가 바뀌어 8이 두번째 데이터가 되었습니다.

두번째 데이터인 8과 다음 데이터 3을 비교해 더 크다면 둘의 위치를 바꿔줍시다.

8이 정렬된 모습

같은 방식으로 계속 비교를 해주고 위치를 바꿔주었더니 8이 가장 뒤에 위치하게 되었습니다.

첫번째 시행에서 우리는 N번째로 큰 수를 찾아서 N번 위치에 놓았습니다.

이로서 하나의 데이터가 자기 위치를 찾아 정렬되었고, N-1개의 정렬할 데이터가 남았습니다.

 

8은 이미 정렬되었으므로 5,3,2,1,6,4,7의 7개 데이터를 정렬해봅시다.

방법은 위와 똑같이 지금 보고 있는 수가 다음 수보다 크다면 자리를 바꿔줍니다.

 

지금 보는 수보다 다음 수가 크다면 바꾸지 않는다

비교를 진행하다 보니, 뒤의 수가 더 큰 경우가 나왔습니다.

지금 보고 있는 수 5보다 다음 수 6이 크다면 둘의 위치를 바꿔주지 않습니다.

 

위치를 바꾸지 않는다면, 다음 비교에서 우리는 자연스럽게 5보다 더 큰 수인 6을 기준으로 비교를 하게 됩니다.

5를 대신해 6이 자기보다 큰 수가 나타날 때까지 계속해서 위치를 바꾸게 됩니다.

마지막 비교에서 6보다 7이 크므로 둘의 위치는 바뀌지 않습니다.

이로서 두 번째 시행의 시작인 5,3,2,1,6,4,7중 가장 큰 수인 7을 찾았고 자신의 위치인 7번 자리에 7이 위치하게 됩니다.

이제 두 개의 수가 정렬되었고 우리는 N-2개의 수를 대상으로 다시 비교를 시작합니다.

이다음 시행은 과정을 기록하지 않겠습니다.

머릿속으로 테스트를 해보시고 정렬 후의 모습과 비교해보시기 바랍니다.

세번째 시행 후!
네번째 시행 후!

네 번째 시행이 끝나고 나서 우리 눈으로 보기엔 정렬된 데이터가 완성되었습니다.

하지만 컴퓨터의 입장에선, 5 6 7 8 네 개의 데이터만 정렬되었다고 인식하므로 1,2,3,4에 대해서도 같은 시행을 반복하게 됩니다.

 

 

아래에는 JAVA로 구현한 간단한 버블정렬 코드와 테스트입니다!

이것저것 바꿔보고 디버깅하면서 흐름이 이해되시길 바랍니다.

import java.util.Arrays;
import java.util.Random;

public class 01_sort_01_bubble {
	static int size = 100;
	static int bound = 1000;
	// 데이터의 갯수와 범위 설정

	public static void main(String[] args) {
		int[] data = new int[size];
		Random random = new Random();
		int count = 0;

		for (int i = 0; i < size; i++) {
			data[i] = random.nextInt(bound);
		}
		// 랜덤 값 넣어주기

		System.out.println(Arrays.toString(data));
		// 랜덤하게 들어간 데이터 확인

		/////////////////////// 버블정렬 구현////////////////////////////
		for (int find = size - 1; find >= 0; find--) {
			for (int now = 0; now < find; now++) {
				int next = now + 1;
				if (data[now] > data[next]) {
					int save = data[now];
					data[now] = data[next];
					data[next] = save;
				}
				count++;
			}
		}
		//////////////////////////////////////////////////////

		System.out.println(Arrays.toString(data));
		System.out.println("비교횟수 : " + count);
		// 정렬 후 데이터와 비교횟수 확인

		int[][] data2 = { { 1, 1 }, { 3, 1 }, { 5, 1 }, { 7, 1 }, { 9, 1 }, { 1, 2 }, { 2, 2 }, { 4, 2 }, { 1, 3 },
				{ 7, 2 }, { 9, 3 }, { 3, 3 }, { 4, 3 }, { 8, 3 }, { 6, 3 } };
		for (int find = 14; find >= 0; find--) {
			for (int now = 0; now < find; now++) {
				int next = now + 1;
				if (data2[now][0] > data2[next][0]) {
					int[] save = data2[now].clone();
					data2[now] = data2[next].clone();
					data2[next] = save.clone();
				}
			}
		}

		for (int i = 0; i < 15; i++) {
			System.out.printf("정렬된 수 : %d, stable: %d\n", data2[i][0], data2[i][1]);
		}
		//stable 정렬임!
	}
}
728x90

문제풀이 루틴에 관한 글은
https://nodingco.tistory.com/23  <- 여기를 참고해주세요!

https://www.acmicpc.net/problem/10610

 

10610번: 30

어느 날, 미르코는 우연히 길거리에서 양수 N을 보았다. 미르코는 30이란 수를 존경하기 때문에, 그는 길거리에서 찾은 수에 포함된 숫자들을 섞어 30의 배수가 되는 가장 큰 수를 만들고 싶어한

www.acmicpc.net



1. 이해 (5분) - 1분

문제에서 제시하는 조건은 30의 배수이지만, 사실상 3의 배수라고 생각을 해도 무방합니다.

3의 배수가 갖는 특징은 자릿수의 숫자를 모두 더하면 3의 배수가 나온다는 점입니다.

3, 6, 9, 12(3), 15(6)... 10진법에서 올림연산이 발생할때 3의 경우는 무조건 1만큼이 부족하기 때문에 앞의 자릿수는 1이 오르고 자기 자릿수에선 1만큼이 부족해지면서 합이 유지되는겁니다.

  

2. 계획 (5분) - 1분

문제에선 3의 배수가 아닌 30의 배수를 요구했으므로, 최소한 한개의 0이 필요합니다.

또한 가장 큰 수를 출력하라고 했으니 각 자리의 숫자들을 내림차순으로 정렬해 출력할 필요도 있겠네요.

다만 이 모든 계산은 이렇게 만들어진 수가 30의 배수일 경우, 즉 각 자릿수의 합이 3의 배수일 때만 해주면 됩니다.


3. 검증 (5분) - 1분

예외가 발생할 경우는 0 같은 특이한 수가 입력될때일텐데요, 조건에서 숫자가 0으로 시작하지 않는다고 하니 문제되지 않을것 같습니다.

각 자리의 숫자들을 내림차순으로 정렬한 수(만들 수 있는 가장 큰 수)도 3의 배수가 맞을까요?

예시를 떠올려보면 12345이나 54321이나 전부 3의 배수입니다! 각 자릿수의 합이 3의 배수기만 하면 전부 3의 배수인게 확실합니다.


4. 풀이 (30분) - 10분

오랜만에 Python을 사용하다보니 살짝 버벅였지만 작성에 크게 어려운 부분은 없었습니다.

짧은 코드인데도 시간이 꽤 걸렸네요.


5. 채점, 디버깅 (+@)

3의 배수가 갖는 특징을 알고 Python 언어를 알면 쉽게 풀 수 있는 문제여서 한번에 통과했습니다.

문제 풀이에는 총 15분 가량이 걸렸습니다.

 

정답코드입니다.

N = list(input())
N = sorted(N, reverse=True)

if N[-1] != '0' :
    print(-1)
else:
    sum = 0
    for i in N:
        sum += int(i)
    if sum % 3 != 0 :
        print(-1)
    else :
        print(''.join(N))
728x90

https://www.acmicpc.net/problem/18248

 

18248번: 제야의 종

첫 줄에 N(1 ≤ N ≤ 1,000)과 M(1 ≤ M ≤ 100)이 주어진다. i+1(1 ≤ i ≤ N)번째 줄에는 M개의 정수 ai,1, ai,2, ..., ai,M 이 주어지는데, ai,j가 1이면 사람 i가 j 번째 타종을 들었음을 의미하고, 0이면 듣

www.acmicpc.net

상당히 재밌는 문제였습니다. 

 

 

얼핏보면 이게 뭔소리지 싶은 문제일 수도 있습니다.

하지만 소리가 발생한 지점으로부터 원형으로 퍼져나간다는 사실을 생각해보면 쉽게 풀이방법을 알아 낼 수 있습니다.

주어진 예제가 너무 단편적이니 제가 직접 예제를 만들어 보겠습니다.

 

10 5
1 0 1 0 1
0 0 0 0 0
0 0 1 0 0
1 0 1 0 1
1 0 1 0 0
0 0 1 0 0
1 0 1 0 0
0 0 0 0 0
1 0 1 1 1
0 0 1 0 0

조건에 성립하는 예제라고 가정하고, 이 예제를 시각적으로 나타내 볼까요?

 

네, 그림을 보고 슬슬 느끼셨을것 같은데요.

이 문제는 정렬 문제입니다.

소리가 원형으로 퍼지기 때문에 바깥 사람이 들은 종소리를 더 가까운 사람은 무조건 들을 수 밖에 없죠.

따라서 종소리를 들은 숫자대로 사람들을 내림차순으로 정렬한다면,

안의 사람이 듣지 못한 종소리를 더 바깥에 있는 사람이 듣는 일은 없습니다.

(안의 사람이 들은 종소리를 바깥사람이 못듣는 경우는 당연히 존재합니다.)

 

예시를 살짝 바꿔 볼까요??

 

10 5
1 0 1 0 1
0 0 0 0 0
0 0 1 0 0
1 0 1 0 1
1 0 1 0 0
0 0 1 0 0
1 0 1 0 0
0 1 0 0 0
1 0 1 1 1
0 0 1 0 0

만약 바꾼 예시처럼 8번 사람이 아무도 듣지 못한 2번 종소리를 들었다고 가정해 봅시다.

그럼 8번은 다른 모두보다 종에 가까이 있어야 하는데, 그렇다면 다른 종소리도 자연스럽게 듣게 됩니다.

옳바른 예시와 잘못된 예시를 정렬 해보면 이렇게 되겠네요.

 

옳은 예

10 5

1 0 1 1 1
1 0 1 0 1
1 0 1 0 1
1 0 1 0 0
1 0 1 0 0
0 0 1 0 0

0 0 1 0 0

0 0 1 0 0

0 0 0 0 0

0 0 0 0 0

 

틀린 예

10 5

1 0 1 1 1
1 0 1 0 1
1 0 1 0 1
1 0 1 0 0
1 0 1 0 0
0 0 1 0 0

0 0 1 0 0

0 0 1 0 0

0 1 0 0 0

0 0 0 0 0

 

모순관계가 생기면 문제에서 주어진 예시 2번 처럼 어떻게든 0->1로 바뀌는 모습이 나타날 수 밖에 없습니다.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
	static StringTokenizer st;

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		st = new StringTokenizer(br.readLine());
		int N = Integer.parseInt(st.nextToken());
		int M = Integer.parseInt(st.nextToken());

		int[][] people = new int[N][M + 1];

		for (int n = 0; n < N; n++) {
			st = new StringTokenizer(br.readLine());
			int sum = 0;
			for (int m = 0; m < M; m++) {
				people[n][m] = Integer.parseInt(st.nextToken());
				sum += people[n][m];

			}
			people[n][M] = sum;
		}

		Arrays.sort(people, (e1, e2) -> {
			return e2[M] - e1[M];
		});

		boolean answer = true;

		loop: for (int m = 0; m < M; m++) {
			int check = people[0][m];
			for (int n = 0; n < N; n++) {
				if(check < people[n][m]) {
					answer = false;
					break loop;
				}
				check = people[n][m];
			}
		}

		if (answer) {
			System.out.println("YES");
		} else {
			System.out.println("NO");
		}
	}
}

 

728x90

+ Recent posts