3. 정렬 알고리즘
3.1 버블 정렬
버블 정렬(Bubble Sort)
버블 정렬은 1956년에 제어 장치 프로그래머 존 코너(John von Neumann)에 의해 처음 소개된 단순한 정렬 알고리즘입니다. 버블 정렬은 인접한 두 요소를 비교하여 정렬하는 방식으로, 가장 큰 요소가 매 반복(iteration)마다 배열의 끝으로 "거품처럼" 떠오르는 방식입니다.
버블 정렬의 시간 복잡도:
- 최악의 경우: \(O(n^2)\)
배열이 역순으로 정렬된 경우, 모든 요소를 반복적으로 비교해야 하기 때문에 최악의 경우 \(O(n^2)\)의 시간이 소요됩니다.
- 최선의 경우: \(O(n)\)
배열이 이미 정렬된 경우, 한 번의 패스만 필요하므로 \(O(n)\)의 시간이 소요됩니다.
// 버블 정렬 예시
public class BubbleSort {
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) { // 배열의 모든 요소를 반복
for (int j = 0; j < n - i - 1; j++) { // 인접한 두 요소를 비교
if (arr[j] > arr[j + 1]) { // 앞 요소가 뒤 요소보다 크면 교환
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
bubbleSort(arr); // 배열 정렬
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
3.2 선택 정렬
선택 정렬(Selection Sort)
선택 정렬은 1955년 CSIRAC 컴퓨터에서 최초로 구현된 정렬 알고리즘입니다. 선택 정렬은 매번 가장 작은(또는 가장 큰) 요소를 선택하여 배열의 앞쪽부터 채워 나가는 방식입니다.
선택 정렬의 시간 복잡도:
- 모든 경우: \(O(n^2)\)
매번 최소값을 찾기 위해 남은 요소들을 모두 비교해야 하므로, 모든 경우에 \(O(n^2)\)의 시간이 소요됩니다.
// 선택 정렬 예시
public class SelectionSort {
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int min_idx = i; // 최소값을 찾기 위한 인덱스 초기화
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[min_idx]) {
min_idx = j; // 최소값 인덱스 업데이트
}
}
// 최소값을 현재 위치로 이동
int temp = arr[min_idx];
arr[min_idx] = arr[i];
arr[i] = temp;
}
}
public static void main(String[] args) {
int[] arr = {64, 25, 12, 22, 11};
selectionSort(arr); // 배열 정렬
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
3.3 삽입 정렬
삽입 정렬(Insertion Sort)
삽입 정렬은 1946년에 최초로 개발된 컴퓨터인 ENIAC에서 사용된 알고리즘 중 하나로, 간단하면서도 효율적인 정렬 방법입니다. 삽입 정렬은 배열의 요소를 하나씩 선택하여 이미 정렬된 부분에 삽입하는 방식입니다.
삽입 정렬의 시간 복잡도:
- 최악의 경우: \(O(n^2)\)
배열이 역순으로 정렬된 경우, 각 요소를 삽입할 때마다 모든 이전 요소들을 비교해야 하므로 \(O(n^2)\)의 시간이 소요됩니다.
- 최선의 경우: \(O(n)\)
배열이 이미 정렬된 경우, 각 요소는 한 번의 비교만으로 제자리에 삽입되므로 \(O(n)\)의 시간이 소요됩니다.
// 삽입 정렬 예시
public class InsertionSort {
public static void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i]; // 현재 요소를 key로 설정
int j = i - 1;
// 현재 요소를 이미 정렬된 부분과 비교하여 삽입 위치를 찾음
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j]; // 요소를 오른쪽으로 이동
j = j - 1;
}
arr[j + 1] = key; // key를 올바른 위치에 삽입
}
}
public static void main(String[] args) {
int[] arr = {12, 11, 13, 5, 6};
insertionSort(arr); // 배열 정렬
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
3.4 병합 정렬
병합 정렬(Merge Sort)
병합 정렬은 존 폰 노이만(John von Neumann)이 1945년에 제안한 알고리즘입니다. 병합 정렬은 분할 정복(Divide and Conquer) 기법을 사용하여 배열을 재귀적으로 분할하고, 정렬된 부분 배열을 병합하여 전체를 정렬합니다.
병합 정렬의 시간 복잡도:
- 모든 경우: \(O(n \log n)\)
배열을 반씩 분할하고 병합하는 과정이 각각 \(O(\log n)\)과 \(O(n)\)의 시간이 소요되므로 전체 시간 복잡도는 \(O(n \log n)\)입니다.
// 병합 정렬 예시
import java.util.Arrays;
public class MergeSort {
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int middle = (left + right) / 2; // 중간 인덱스를 계산하여 배열을 분할
mergeSort(arr, left, middle); // 왼쪽 절반을 재귀적으로 정렬
mergeSort(arr, middle + 1, right); // 오른쪽 절반을 재귀적으로 정렬
merge(arr, left, middle, right); // 정렬된 두 부분을 병합
}
}
public static void merge(int[] arr, int left, int middle, int right) {
int n1 = middle - left + 1;
int n2 = right - middle;
int[] leftArray = new int[n1];
int[] rightArray = new int[n2];
// 임시 배열에 데이터를 복사
for (int i = 0; i < n1; i++)
leftArray[i] = arr[left + i];
for (int j = 0; j < n2; j++)
rightArray[j] = arr[middle + 1 + j];
int i = 0, j = 0;
int k = left;
// 두 배열을 병합
while (i < n1 && j < n2) {
if (leftArray[i] <= rightArray[j]) {
arr[k] = leftArray[i];
i++;
} else {
arr[k] = rightArray[j];
j++;
}
k++;
}
// 남은 요소들을 복사
while (i < n1) {
arr[k] = leftArray[i];
i++;
k++;
}
while (j < n2) {
arr[k] = rightArray[j];
j++;
k++;
}
}
public static void main(String[] args) {
int[] arr = {12, 11, 13, 5, 6, 7};
System.out.println("Given Array");
System.out.println(Arrays.toString(arr));
mergeSort(arr
, 0, arr.length - 1); // 배열 정렬
System.out.println("\nSorted array");
System.out.println(Arrays.toString(arr));
}
}
3.5 퀵 정렬
퀵 정렬(Quick Sort)
퀵 정렬은 1960년에 토니 호어(Tony Hoare)가 개발한 알고리즘입니다. 퀵 정렬은 분할 정복 기법을 사용하여 피벗을 기준으로 배열을 두 부분으로 나눈 후, 각각을 정렬하는 알고리즘입니다.
퀵 정렬의 시간 복잡도:
- 평균: \(O(n \log n)\)
피벗이 항상 중앙에 위치할 경우, 분할 과정이 \(O(\log n)\) 번 발생하고 각 분할마다 \(O(n)\) 시간이 소요됩니다.
- 최악의 경우: \(O(n^2)\)
피벗이 항상 가장 작은 또는 가장 큰 값을 선택할 경우, 분할 과정이 \(O(n)\) 번 발생하여 각 분할마다 \(O(n)\) 시간이 소요됩니다.
// 퀵 정렬 예시
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high); // 피벗을 기준으로 배열을 분할
quickSort(arr, low, pi - 1); // 피벗 왼쪽 부분을 재귀적으로 정렬
quickSort(arr, pi + 1, high); // 피벗 오른쪽 부분을 재귀적으로 정렬
}
}
public static int partition(int[] arr, int low, int high) {
int pivot = arr[high]; // 마지막 요소를 피벗으로 선택
int i = (low - 1);
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
// arr[i]와 arr[j]를 교환
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 피벗을 올바른 위치로 이동
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
public static void main(String[] args) {
int[] arr = {10, 7, 8, 9, 1, 5};
quickSort(arr, 0, arr.length - 1); // 배열 정렬
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
3.6 힙 정렬
힙 정렬(Heap Sort)
힙 정렬은 1964년에 제이 윌리엄스(J. W. J. Williams)가 개발한 알고리즘입니다. 힙 정렬은 힙 데이터 구조를 이용하여 정렬하는 알고리즘으로, 힙은 최대값 또는 최소값을 빠르게 찾을 수 있는 완전 이진 트리입니다.
힙 정렬의 시간 복잡도:
- 모든 경우: \(O(n \log n)\)
힙을 구성하는 과정과 최대값을 제거하여 정렬하는 과정이 각각 \(O(n \log n)\)의 시간이 소요됩니다.
// 힙 정렬 예시
public class HeapSort {
public static void heapSort(int arr[]) {
int n = arr.length;
// 힙을 구성
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 하나씩 요소를 힙에서 제거하여 정렬
for (int i = n - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
}
public static void heapify(int arr[], int n, int i) {
int largest = i; // 현재 노드를 루트로
int left = 2 * i + 1; // 왼쪽 자식
int right = 2 * i + 2; // 오른쪽 자식
// 왼쪽 자식이 더 크면 largest 업데이트
if (left < n && arr[left] > arr[largest])
largest = left;
// 오른쪽 자식이 더 크면 largest 업데이트
if (right < n && arr[right] > arr[largest])
largest = right;
// largest가 루트가 아니면 교환하고 재귀적으로 힙을 구성
if (largest != i) {
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
heapify(arr, n, largest);
}
}
public static void main(String[] args) {
int[] arr = {12, 11, 13, 5, 6, 7};
heapSort(arr); // 배열 정렬
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
3.7 기수 정렬
기수 정렬(Radix Sort)
기수 정렬은 1887년에 Herman Hollerith에 의해 개발된 알고리즘입니다. 기수 정렬은 숫자의 자릿수를 기준으로 정렬하는 알고리즘입니다. 각 자릿수에 대해 안정적인 정렬 알고리즘을 반복적으로 적용하여 전체 숫자를 정렬합니다.
기수 정렬의 시간 복잡도:
- 모든 경우: \(O(d \cdot (n + k))\)
여기서 \(d\)는 자릿수의 개수, \(k\)는 자릿수의 가능한 값의 범위입니다. 각 자릿수에 대해 안정적인 정렬 알고리즘을 적용하는 데 \(O(n + k)\)의 시간이 소요됩니다.
// 기수 정렬 예시
import java.util.Arrays;
public class RadixSort {
public static void radixSort(int[] arr) {
int max = Arrays.stream(arr).max().getAsInt(); // 배열에서 최대값 찾기
for (int exp = 1; max / exp > 0; exp *= 10)
countingSort(arr, exp); // 각 자릿수에 대해 카운팅 정렬 적용
}
public static void countingSort(int[] arr, int exp) {
int n = arr.length;
int[] output = new int[n]; // 정렬된 배열을 저장할 임시 배열
int[] count = new int[10];
Arrays.fill(count, 0);
// 현재 자릿수의 숫자 개수 세기
for (int i = 0; i < n; i++)
count[(arr[i] / exp) % 10]++;
// 누적 합 계산
for (int i = 1; i < 10; i++)
count[i] += count[i - 1];
// 임시 배열에 정렬된 값 저장
for (int i = n - 1; i >= 0; i--) {
output[count[(arr[i] / exp) % 10] - 1] = arr[i];
count[(arr[i] / exp) % 10]--;
}
// 원본 배열에 정렬된 값 복사
for (int i = 0; i < n; i++)
arr[i] = output[i];
}
public static void main(String[] args) {
int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};
radixSort(arr); // 배열 정렬
System.out.println("Sorted array: ");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
'IT 강좌(IT Lectures) > 알고리즘(Algorithm)' 카테고리의 다른 글
5강. 고급 데이터 구조 (0) | 2024.07.08 |
---|---|
4강. 검색알고리즘 (0) | 2024.07.07 |
2강. 기본 데이터 구조 (0) | 2024.07.05 |
1강. 알고리즘 소개 (0) | 2024.07.04 |
[알고리즘] 알고리즘이란 (0) | 2018.07.03 |