문제

버블 소트 알고리즘을 다음과 같이 C++로 작성했다.

bool changed = false;
for (int i=1; i<=N+1; i++) {
    changed = false;
    for (int j=1; j<=N-i; j++) {
        if (A[j] > A[j+1]) {
            changed = true;
            swap(A[j], A[j+1]);
        }
    }
    if (changed == false) {
        cout << i << '\n';
        break;
    }
}

위 소스에서 N은 배열의 크기이고, A는 정렬해야 하는 배열이다. 배열은 A[1]부터 사용한다.

위와 같은 소스를 실행시켰을 때, 어떤 값이 출력되는지 구해보자.

입력

첫째 줄에 N이 주어진다. N은 500,000보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에 A[1]부터 A[N]까지 하나씩 주어진다. A에 들어있는 수는 1,000,000보다 작거나 같은 자연수 또는 0이다.

 

 

풀이

 

문제에 나온 소스를 그대로 돌리면 시간초과가 나온다. 버블 정렬의 시간복잡도는 O(n^2)이기 때문이다. 따라서 버블 정렬의 규칙을 찾아내어 출력값인 정렬 수행 횟수를 구해야 한다.

 

해당 코드에서의 버블 정렬은 오름차순으로, 큰 수는 우측으로, 작은 수는 좌측으로 이동하며 정렬 된다.

이 때, 비교는 좌측(인덱스 0)부터 시작하고 우측부터 정렬이 완료 되는데, 이러한 규칙을 통해 버블 정렬이 몇 번 수행될 것인지 계산할 수 있다. 

 

문제에 나온 입력 예시를 토대로 설명하자면

 

아래와 같은 배열이 있다.

인덱스 0 1 2 3 4
10 1 5 2 3

 

이 배열의 각각 요소들에 대한 인덱스를 기억해두고, 어떤 정렬이어도 상관 없으니 성능이 괜찮은 정렬을 수행 한다.

 

이전 인덱스 1 3 4 2 0
현재 인덱스 0 1 2 3 4
value 1 2 3 5 10

 

위와 같이 정렬이 되었을 것이다.

이전 인덱스와 현재 인덱스만을 비교해보자.

이전 인덱스 1 3 4 2 0
현재 인덱스 0 1 2 3 4

 

 

첫 번째 항목을 보면, 1번 인덱스에 있던 값이 0번 인덱스로 이동했다는 것을 알 수 있다.

 

1번에서 0번으로 가려면, 몇 번 이동해야할까? 1번이다. 

3번에서 1번으로 가려면? 2번.

4번에서 2번으로 가려면? 2번.

 

즉, 이전 인덱스에서 현재 인덱스의 값을 빼주면 이동 횟수를 알 수 있다.

 

그러면 모두 계산을 해보자.

 

이전 인덱스 1 3 4 2 0
현재 인덱스 0 1 2 3 4
인덱스의 차 1 2 2 -1 -4

 

계산을 마치고 생각해보면 양수는 좌측으로 이동한 횟수, 음수는 우측으로 이동한 횟수를 의미하는 것을 알 수 있다.

하지만 우측으로 이동한 횟수는 고려할 필요가 없다. 한 번의 정렬을 시행할 때 좌측부터 비교를 시작하고 맨 우측부터 정렬이 완료된다는 것은 내부 for문을 한 바퀴 돌 때 한 원소당 좌측으로는 한 번만 이동할 수 있고, 우측으로는 여러 번 이동할 수 있다는 것을 의미 한다. 즉, 좌측으로 이동한 횟수가 우리가 원하는 정렬을 시행한 횟수이다. 

 

따라서 이전 인덱스에서 현재 인덱스를 뺀 값들 중 가장 큰 값을 구해주면 된다.

단, 문제에서 원하는 출력 답은 내부 for문을 한 번도 돌지 않아 flag를 세우는 1번의 횟수까지 카운팅해야 하기에 가장 큰 값 + 1 을 해주면 된다.

 

 

+ Recent posts