자료구조, 알고리즘 입문/정렬 알고리즘

[알고리즘] 버블 정렬

sungw00 2022. 5. 18. 22:20
728x90

버블 정렬이란?

- 이웃한 두 원소의 대소 관계를 비교하고, 교환을 반복하는 알고리즘(=단순 교환 정렬)

- 액체 속의 공기 방울이 가벼워서 위로 보글보글 올라오는 모습에 착안해서 붙인 이름

 

버블 정렬의 첫 번째 패스 진행 과정

오름차순 정렬하는 버블 정렬의 첫 번째 패스 진행 과정은 다음과 같다.

1) 오른쪽 끝에 있는 두 원소 9와 8에 주목한다.

2) 9와 8의 위치를 교환한다.

3) 다음 원소인 1과 8에 주목한다.

4) 1과 8의 위치는 교환하지 않는다.

 

원소 수가 n인 배열에서 n - 1번 비교 & 교환을 하면 가장 작은 원소인 1이 맨 앞으로 이동한다. 이러한 과정을 패스라고 한다.

 

버블 정렬의 두 번째 패스 진행 과정

위와 같이 패스를 한 번 수행할 때마다 정렬할 대상은 1개씩 줄어든다. 그러므로 두 번째 패스의 비교 횟수는 첫 번째 패스보다 1번 적은 n - 2번이다. 패스를 k번 수행하면 맨 앞부터 k개 원소가 정렬되고, 모든 정렬이 끝나려면 패스를 n - 1번 수행해야 한다.

(수행하는 패스 횟수가 n번이 아니라 n - 1번인 이유는 n - 1개 원소의 정렬이 끝나면 마지막 원소는 이미 끝에 놓이기 때문)

 

아래 코드는 버블 정렬 알고리즘으로 작성된 코드

# 버블 정렬 알고리즘 구현하기

from typing import MutableSequence

def bubble_sort(a: MutableSequence) -> None:
    """버블 정렬"""
    n = len(a)
    for i in range(n - 1):
        for j in range(n - 1, i, -1):
            if a[j - 1] > a[j]:
                a[j - 1], a[j] = a[j], a[j - 1],
                
if __name__ == '__main__':
    print('버블 정렬을 수행합니다.')
    num = int(input('원소 수를 입력하세요.: '))
    x = [None] * num    # 원소 수가 num인 배열을 생성
    
    for i in range(num):
        x[i] = int(input(f'x[{i}]: '))
        
    bubble_sort(x)  # 배열 x를 버블 정렬
    
    print('오름차순으로 정렬했습니다.')
    for i in range(num):
        print(f'x[{i}] = {x[i]}')

 

실행 결과

버블 정렬을 수행합니다.
원소 수를 입력하세요.: 7
x[0]: 6
x[1]: 4
x[2]: 3
x[3]: 7
x[4]: 1
x[5]: 9
x[6]: 8
오름차순으로 정렬했습니다.
x[0] = 1
x[1] = 3
x[2] = 4
x[3] = 6
x[4] = 7
x[5] = 8
x[6] = 9

Process finished with exit code 0

 

위 코드는 배열의 맨 끝(우측)에서 맨 앞(좌측)을 향해 스캔하기 때문에 j의 시작값은 n - 1이다. 

두 원소 a[j - 1]과 a[j]의 값을 비교해서 앞쪽 값(좌측)이 뒷쪽 값(우측)보다 크면 교환한다.

 

* 버블 정렬은 이와 같이 서로 이웃한 원소만 교환하기 때문에 안정적이다.

 

아래 코드는 원소를 어떤 순서로 비교하고 교환하는 지 구체적인 결과로 드러내주는 코드이다.

# 버블 정렬 알고리즘 구현하기(정렬 과정을 출력)

from typing import MutableSequence

def bubble_sort_verbose(a: MutableSequence) -> None:
    """버블 정렬(정렬 과정을 출력)"""
    ccnt = 0    # 비교 횟수
    scnt = 0    # 교환 횟수
    n = len(a)
    for i in range(n - 1):
        print(f'패스 {i + 1}')
        for j in range(n - 1, i, -1):
            for m in range(0, n - 1):
                print(f'{a[m]:2}' + ('  ' if m != j - 1 else
                                     ' +' if a[j - 1] > a[j] else ' -'),
                                     end='')
            print(f'{a[n - 1]:2}')
            ccnt += 1
            if a[j - 1] > a[j]:
                scnt += 1
                a[j - 1], a[j] = a[j], a[j - 1],
        for m in range(0, n - 1):
            print(f'{a[m]:2}', end='  ')
        print(f'{a[n - 1]:2}')
    print(f'비교를 {ccnt}번 했습니다.')
    print(f'교환을 {scnt}번 했습니다.')

if __name__ == '__main__':
    print('버블 정렬을 수행합니다.')
    num = int(input('원소 수를 입력하세요.: '))
    x = [None] * num  # 원소 수가 num인 배열을 생성

    for i in range(num):
        x[i] = int(input(f'x[{i}]: '))

    bubble_sort_verbose(x)  # 배열 x를 버블 정렬

    print('오름차순으로 정렬했습니다.')
    for i in range(num):
        print(f'x[{i}] = {x[i]}')

 

실행 결과

버블 정렬을 수행합니다.
원소 수를 입력하세요.: 7
x[0]: 6
x[1]: 4
x[2]: 3
x[3]: 7
x[4]: 1
x[5]: 9
x[6]: 8
패스 1
 6   4   3   7   1   9 + 8
 6   4   3   7   1 - 8   9
 6   4   3   7 + 1   8   9
 6   4   3 + 1   7   8   9
 6   4 + 1   3   7   8   9
 6 + 1   4   3   7   8   9
 1   6   4   3   7   8   9
패스 2
 1   6   4   3   7   8 - 9
 1   6   4   3   7 - 8   9
 1   6   4   3 - 7   8   9
 1   6   4 + 3   7   8   9
 1   6 + 3   4   7   8   9
 1   3   6   4   7   8   9
패스 3
 1   3   6   4   7   8 - 9
 1   3   6   4   7 - 8   9
 1   3   6   4 - 7   8   9
 1   3   6 + 4   7   8   9
 1   3   4   6   7   8   9
패스 4
 1   3   4   6   7   8 - 9
 1   3   4   6   7 - 8   9
 1   3   4   6 - 7   8   9
 1   3   4   6   7   8   9
패스 5
 1   3   4   6   7   8 - 9
 1   3   4   6   7 - 8   9
 1   3   4   6   7   8   9
패스 6
 1   3   4   6   7   8 - 9
 1   3   4   6   7   8   9
비교를 21번 했습니다.
교환을 8번 했습니다.
오름차순으로 정렬했습니다.
x[0] = 1
x[1] = 3
x[2] = 4
x[3] = 6
x[4] = 7
x[5] = 8
x[6] = 9

Process finished with exit code 0

 

이 코드의 입력값에 원소 수(num) = 7, 값(x[i]) = 6, 4, 3, 7, 1, 9, 8를 입력하게 되면 비교를 21번, 교환을 8번 한 후 정렬이 끝난다.

 

다음 코드는 위 코드를 개선하여 교환 횟수에 따른 중단 방식을 적용한 코드이다.

# 버블 정렬 알고리즘 구현하기(알고리즘의 개선 1)

from typing import MutableSequence

def bubble_sort(a: MutableSequence) -> None:
    """버블 정렬(교환 횟수에 따른 중단)"""
    ccnt = 0  # 비교 횟수
    scnt = 0  # 교환 횟수
    n = len(a)
    for i in range(n - 1):
        print(f'패스 {i + 1}')
        exchng = 0  # 패스에서 교환 횟수
        for j in range(n - 1, i, -1):
            for m in range(0, n - 1):
                print(f'{a[m]:2}' + ('  ' if m != j - 1 else
                                     ' +' if a[j - 1] > a[j] else ' -'),
                                     end='')
            print(f'{a[n - 1]:2}')
            ccnt += 1
            if a[j - 1] > a[j]:
                scnt += 1
                a[j - 1], a[j] = a[j], a[j - 1],
                exchng += 1
        for m in range(0, n - 1):
            print(f'{a[m]:2}', end='  ')
        print(f'{a[n - 1]:2}')
        if exchng == 0:
            break
    print(f'비교를 {ccnt}번 했습니다.')
    print(f'교환을 {scnt}번 했습니다.')

if __name__ == '__main__':
    print('버블 정렬을 수행합니다.')
    num = int(input('원소 수를 입력하세요.: '))
    x = [None] * num    # 원소 수가 num인 배열을 생성

    for i in range(num):
        x[i] = int(input(f'x[{i}]: '))

    bubble_sort(x)  # 배열 x를 버블 정렬

    print('오름차순으로 정렬했습니다.')
    for i in range(num):
        print(f'x[{i}] = {x[i]}')

 

실행 결과

버블 정렬을 수행합니다.
원소 수를 입력하세요.: 7
x[0]: 6
x[1]: 4
x[2]: 3
x[3]: 7
x[4]: 1
x[5]: 9
x[6]: 8
패스 1
 6   4   3   7   1   9 + 8
 6   4   3   7   1 - 8   9
 6   4   3   7 + 1   8   9
 6   4   3 + 1   7   8   9
 6   4 + 1   3   7   8   9
 6 + 1   4   3   7   8   9
 1   6   4   3   7   8   9
패스 2
 1   6   4   3   7   8 - 9
 1   6   4   3   7 - 8   9
 1   6   4   3 - 7   8   9
 1   6   4 + 3   7   8   9
 1   6 + 3   4   7   8   9
 1   3   6   4   7   8   9
패스 3
 1   3   6   4   7   8 - 9
 1   3   6   4   7 - 8   9
 1   3   6   4 - 7   8   9
 1   3   6 + 4   7   8   9
 1   3   4   6   7   8   9
패스 4
 1   3   4   6   7   8 - 9
 1   3   4   6   7 - 8   9
 1   3   4   6 - 7   8   9
 1   3   4   6   7   8   9
비교를 18번 했습니다.
교환을 8번 했습니다.
오름차순으로 정렬했습니다.
x[0] = 1
x[1] = 3
x[2] = 4
x[3] = 6
x[4] = 7
x[5] = 8
x[6] = 9

Process finished with exit code 0

 

새로 추가한 변수 exchng는 패스를 시작하기 전 0으로 초기화되고, 원소를 교환할 때마다 1씩 증가시킨다. 

하나의 패스를 마친 상태에서는 exchng값은 패스에서의 교환 횟수와 같아지게 된다.

 

이 코드도 마찬가지로 입력값에 원소 수(num) = 7, 값(x[i]) = 6, 4, 3, 7, 1, 9, 8를 입력한 후 이전 코드와 비교해본다.

비교를 18번, 교환을 8번 한 후 정렬이 끝난다. 이전 코드보다 더 효율적으로 수행되었다.

 

다음 코드는 이미 정렬된 원소를 제외한 나머지만 비교, 교환하도록 스캔 범위를 제한하는 방법으로 개선한 프로그램이다.

# 버블 정렬 알고리즘 구현하기(알고리즘의 개선 2)

from typing import MutableSequence

def bubble_sort(a: MutableSequence) -> None:
    """버블 정렬(스캔 범위를 제한)"""
    n = len(a)
    k = 0
    while k < n - 1:
        last = n - 1
        for j in range(n - 1, k, -1):
            if a[j - 1] > a[j]:
                a[j - 1], a[j] = a[j], a[j - 1]
                last = j
        k = last

if __name__ == '__main__':
    print('버블 정렬을 수행합니다.')
    num = int(input('원소 수를 입력하세요.: '))
    x = [None] * num    # 원소 수가 num인 배열을 생성

    for i in range(num):
        x[i] = int(input(f'x[{i}]: '))

    bubble_sort(x)  # 배열 x를 버블 정렬

    print('오름차순으로 정렬했습니다.')
    for i in range(num):
        print(f'x[{i}] = {x[i]}')

 

실행 결과

버블 정렬을 수행합니다.
원소 수를 입력하세요.: 7
x[0]: 1
x[1]: 3
x[2]: 9
x[3]: 4
x[4]: 7
x[5]: 8
x[6]: 6
오름차순으로 정렬했습니다.
x[0] = 1
x[1] = 3
x[2] = 4
x[3] = 6
x[4] = 7
x[5] = 8
x[6] = 9

Process finished with exit code 0

 

실제로 위의 코드를 실행시켜 원소 수는 7, 배열 값에는 [1, 3, 9, 4, 7, 8, 6]을 입력 하면 가장 적은 횟수를 비교하여 정렬을 수행한다.

 

하지만 이제까지 살펴본 코드 모두 [9, 1, 3, 4, 6, 7, 8]을 대입하면 전부 21번 비교하고 6번 교환을 하기때문에 개선이 무의미해진다.

이러한 코드를 개선하기 위해 다음 글에서는 셰이커 정렬을 알아본다.

 

 

728x90