📚 Computer Science/Algorithm

[Algorithm] C++ Bubble Sort (거품정렬)

스쳐가는비 2022. 10. 4. 10:16

거품정렬

  • 두개의 인접한 원소를 비교하는 정렬이다.
  • 선택정렬과 유사하나, 다른점은 앞에서부터 차례로 정렬하기때문에 '안정정렬' 이라고도 한다.
  • 정렬 방식중 가장 간단하며 쉬우나, 다른 정렬보다 원소의 교환이 많아, 소요되는 시간이 길다.

원소들의 정렬과정에서 원소의 이동이 거품처럼 수면위로 올라온다는 뜻으로 거품 정렬이라는 이름이 붙기도하였다.

 

거품정렬 알고리즘

  • 앞에서부터 현재 원소와 바로 다음 원소를 비교한다.
  • 현재 원소가 바로 다음의 원소보다 클 경우 교환한다. (오름차순 기준)
  • 그 이후 바로 다음 원소로 이동하여, 해당 원소와 다음 원소를 비교한다.

 

예시

출처 - https://runestone.academy/ns/books/published/pythonds/SortSearch/TheBubbleSort.html

 

원소들 정렬 흐름은 아래 gif 파일로 확인할 수 있다.

https://ko.wikipedia.org/wiki/%EA%B1%B0%ED%92%88_%EC%A0%95%EB%A0%AC

 

거품정렬 특징

  • 추가적인 메모리 소비가 적다.
  • 구현이 매우 간단하다.
  • 안정정렬이 가능하다.
  • 다른 정렬과는 다르게 교환 횟수가 많아 소비 시간이 크다. 그래서 실제로 많이 사용하지 않는 정렬이다.

 

시간복잡도

거품 정렬의 평균 시간 복잡도는 O(n^2)의 시간 복잡도를 갖는다.

  • 비교 횟수

- 두개의 for루프의 실행 횟수

- 외부 루프 : (n - 1) 번

- 내부 루프 : n-1, n-2, n-3 .... 2, 1번 (최솟값 찾기)

 

일반화 시킨 공식은 다음과 같다.

즉, T(N) 시간복잡도 또한 도출이 가능하다.

관련 알고리즘 문제

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

 

1838번: 버블 정렬

버블 정렬이란 배열에서 서로 인접해 있는 값을 비교해서 작은 값이 더 뒤에 있을 때 두 값을 바꾸어 주는 과정을 계속 반복하는 정렬 방법이다. N개의 서로 다른 정수가 A[0], A[1], ..., A[N-1]의 정

www.acmicpc.net

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

 

2750번: 수 정렬하기

첫째 줄에 수의 개수 N(1 ≤ N ≤ 1,000)이 주어진다. 둘째 줄부터 N개의 줄에는 수 주어진다. 이 수는 절댓값이 1,000보다 작거나 같은 정수이다. 수는 중복되지 않는다.

www.acmicpc.net

 

Code

이미 정렬이 된 원소의 집합이여도, 그 이후 원소들까지 전부 정렬을 하기 때문에 시간복잡도가 O(n^2)가 되지만,

O(n)까지 개선시킬 수 있다.

 

우선 아래 코드는 일반적인 거품 정렬이다.

#include <iostream>

using namespace std;

int main() {

	int arr[] = { 54, 26, 93, 17, 77, 31, 44, 55, 20 };
	int arrSize = 9;
	for (int i = 0; i < arrSize - 1; i++) {
		for (int j = 0; j < arrSize - 1 - i; j++) {
			if (arr[j] > arr[j + 1]) { 
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
		for (int a = 0; a < arrSize; a++)
			cout << arr[a] << ' ';
		cout << i + 1 << " Exchange\n";
	}

}

해당 원소와 다음 원소를 비교하였을때, 교환이 이루어지는 해당 반복문 내에서 원소가 교환되지 않는다면,

즉, swap이 발생하지 않는다면 이미 정렬된 데이터로 가정하고, 종료할 수 있다.

이 경우 Best Case로 시간복잡도가 O(n) 가 될 수 있다.

 

아래 코드를 한번 살펴 보자.

 

#include <iostream>

using namespace std;

int main() {

	/* Worst Case
	int arr[] = { 54, 26, 93, 17, 77, 31, 44, 55, 20 };
	int arrSize = 9;
	for (int i = 0; i < arrSize - 1; i++) {
		for (int j = 0; j < arrSize - 1 - i; j++) {
			if (arr[j] > arr[j + 1]) { 
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
		for (int a = 0; a < arrSize; a++)
			cout << arr[a] << ' ';
		cout << i + 1 << " Exchange\n";
	}
	*/

	/* Best Case*/

	int arr[] = { 54, 26, 93, 17, 77, 31, 44, 55, 20 };
	int arrSize = 9;
	for (int i = 0; i < arrSize - 1; i++) {

		bool isSwap = false;
		for (int j = 0; j < arrSize - 1 - i; j++) {
			if (arr[j] > arr[j + 1]) 
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				isSwap = true;
			}
		}

		if (isSwap == false)
			break;

		for (int a = 0; a < arrSize; a++)
			cout << arr[a] << ' ';
		cout << i + 1 << " Exchange\n";
	}
}

 

이 경우, 교환이 이미 이루어져 있거나, 정렬이 된 경우 바로 종료 하게 된다.