본문 바로가기
Java/Java이론

리스트를 사용하는 다양한 알고리즘 구현

by P_eli 2024. 10. 17.
728x90
반응형

리스트는 알고리즘 구현에서 가장 기본적이고 중요한 데이터 구조 중 하나입니다. 특히, 정렬 알고리즘검색 알고리즘은 리스트를 기반으로 효율적인 처리를 위해 사용됩니다. ArrayList를 활용해 대표적인 정렬 알고리즘인 QuickSortMergeSort를 구현하고, 두 알고리즘의 성능 및 시간 복잡도를 비교해 보겠습니다.

 

1. 정렬 알고리즘 개요

1.1 QuickSort (퀵 정렬)

QuickSort는 분할 정복 방법을 사용한 효율적인 정렬 알고리즘입니다. 리스트에서 기준이 되는 **피벗(Pivot)**을 선택하고, 피벗을 기준으로 작은 값들은 왼쪽, 큰 값들은 오른쪽에 배치한 후 재귀적으로 정렬합니다.

  • 시간 복잡도
    • 평균: O(n log n)
    • 최악: O(n²) (이미 정렬된 경우 피벗이 항상 가장 큰 값이나 작은 값일 때)

1.2 MergeSort (병합 정렬)

MergeSort는 리스트를 반으로 나누어 각각 정렬한 후, 두 정렬된 리스트를 병합하는 방식으로 작동합니다. 역시 분할 정복 기법을 사용하며, 안정적인 정렬 알고리즘입니다.

  • 시간 복잡도
    • 최선/최악/평균: O(n log n) (항상 동일)

QuickSort와 MergeSort는 모두 효율적인 정렬 알고리즘이지만, 각 알고리즘은 상황에 따라 성능 차이가 있을 수 있습니다. 이에 대해서는 뒤에서 자세히 살펴보겠습니다.

2. QuickSort 구현

ArrayList를 사용하여 QuickSort를 구현해 보겠습니다.

2.1 QuickSort 코드 예제

import java.util.ArrayList;
import java.util.Collections;

public class QuickSortExample {
    public static void quickSort(ArrayList<Integer> list, int low, int high) {
        if (low < high) {
            int pivotIndex = partition(list, low, high);
            quickSort(list, low, pivotIndex - 1);
            quickSort(list, pivotIndex + 1, high);
        }
    }

    private static int partition(ArrayList<Integer> list, int low, int high) {
        int pivot = list.get(high); // 마지막 요소를 피벗으로 선택
        int i = low - 1;

        for (int j = low; j < high; j++) {
            if (list.get(j) <= pivot) {
                i++;
                Collections.swap(list, i, j); // 작은 요소를 왼쪽으로 이동
            }
        }
        Collections.swap(list, i + 1, high); // 피벗을 올바른 위치로 이동
        return i + 1; // 피벗의 위치 반환
    }

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 34, 7, 23, 32, 5, 62);

        System.out.println("Before QuickSort: " + list);
        quickSort(list, 0, list.size() - 1);
        System.out.println("After QuickSort: " + list);
    }
}

 

2.2 QuickSort 설명

  • 피벗을 기준으로 리스트를 두 부분으로 나누고, 피벗보다 작은 값은 왼쪽, 큰 값은 오른쪽으로 배치한 후 재귀적으로 정렬합니다.
  • partition 함수는 리스트를 나누고 피벗의 최종 위치를 반환합니다.

3. MergeSort 구현

다음으로 ArrayList를 사용한 MergeSort 구현을 보겠습니다.

3.1 MergeSort 코드 예제

import java.util.ArrayList;
import java.util.Arrays;

public class MergeSortExample {
    public static void mergeSort(ArrayList<Integer> list) {
        if (list.size() < 2) {
            return; // 리스트 크기가 1 이하일 때는 이미 정렬된 상태
        }

        int mid = list.size() / 2;
        ArrayList<Integer> left = new ArrayList<>(list.subList(0, mid));
        ArrayList<Integer> right = new ArrayList<>(list.subList(mid, list.size()));

        // 재귀적으로 분할
        mergeSort(left);
        mergeSort(right);

        // 병합
        merge(list, left, right);
    }

    private static void merge(ArrayList<Integer> list, ArrayList<Integer> left, ArrayList<Integer> right) {
        int i = 0, j = 0, k = 0;

        while (i < left.size() && j < right.size()) {
            if (left.get(i) <= right.get(j)) {
                list.set(k++, left.get(i++));
            } else {
                list.set(k++, right.get(j++));
            }
        }

        // 남은 요소들 처리
        while (i < left.size()) {
            list.set(k++, left.get(i++));
        }

        while (j < right.size()) {
            list.set(k++, right.get(j++));
        }
    }

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(34, 7, 23, 32, 5, 62));

        System.out.println("Before MergeSort: " + list);
        mergeSort(list);
        System.out.println("After MergeSort: " + list);
    }
}

3.2 MergeSort 설명

  • 리스트를 절반으로 나누어 각각 정렬한 후, 병합하는 방식으로 동작합니다.
  • merge 함수는 두 개의 정렬된 리스트를 하나로 합치는 역할을 합니다.

4. QuickSort와 MergeSort 성능 분석

4.1 시간 복잡도

  • QuickSort
    • 평균적으로 O(n log n)의 시간 복잡도를 가집니다. 하지만, 최악의 경우 O(n²)가 될 수 있습니다. 이는 리스트가 이미 정렬된 경우 피벗 선택에 따라 발생할 수 있습니다.
  • MergeSort
    • 항상 O(n log n)의 시간 복잡도를 보장합니다. 리스트가 어떠한 상태이든 간에, 동일한 시간 복잡도를 유지합니다.

4.2 공간 복잡도

  • QuickSort: 추가적인 배열을 사용하지 않기 때문에 공간 복잡도는 O(log n)입니다. 하지만 재귀 호출로 인해 스택 공간을 사용합니다.
  • MergeSort: 추가적인 배열을 사용하여 정렬된 리스트를 병합하므로, O(n)의 공간 복잡도를 가집니다.

4.3 사용 상황에 따른 선택

  • QuickSort는 평균적으로 매우 빠르며, 추가 메모리가 거의 필요하지 않기 때문에 메모리 제한이 있는 환경에서 적합합니다. 다만, 최악의 경우 O(n²)이 발생할 수 있기 때문에 이에 대한 대비가 필요합니다.
  • MergeSort는 안정적인 정렬이 필요하거나, 시간 복잡도가 중요한 경우에 적합합니다. 특히 큰 데이터를 다룰 때, MergeSort의 안정성은 매우 유리합니다. 하지만 O(n)의 추가 메모리를 사용해야 하므로, 메모리 사용에 제약이 있는 환경에서는 부적합할 수 있습니다.

5. 결론

  • QuickSort는 평균적으로 빠르고 메모리 사용이 적어 대부분의 상황에서 좋은 성능을 발휘하지만, 최악의 경우를 대비해야 합니다.
  • MergeSort는 일관된 성능을 보장하며 안정적인 정렬을 제공하지만, 추가 메모리가 필요합니다.

상황에 따라 이 두 알고리즘을 적절히 선택하여 사용할 수 있으며, 이를 통해 효율적인 정렬 알고리즘을 구현할 수 있습니다.

728x90
반응형