정렬 알고리즘 종합 정리: 동작 원리와 구현 예시

1. 버블 정렬 (Bubble Sort)

이 알고리즘은 반복적으로 인접한 요소를 비교하고, 필요 시 교환하여 최댓값(또는 최솟값)을 배열의 끝으로 이동시킨다. 각 단계마다 하나의 정렬된 요소가 확정되며, 전체적으로 정렬된 상태가 되기까지 O(n²)의 시간 복잡도를 가진다. 최적화된 경우, 이미 정렬된 배열에 대해 O(n)까지 가능하지만, 추가적인 플래그 변수를 사용해야 한다.

- (void)bubbleSort:(NSMutableArray *)arr {
    for (int i = 0; i < arr.count - 1; i++) {
        BOOL swapped = NO;
        for (int j = 0; j < arr.count - 1 - i; j++) {
            if ([arr[j + 1] intValue] < [arr[j] intValue]) {
                [arr exchangeObjectAtIndex:j withObjectAtIndex:j + 1];
                swapped = YES;
            }
        }
        if (!swapped) break;
    }
}

2. 선택 정렬 (Selection Sort)

정렬되지 않은 범위에서 가장 작은 값을 찾아 첫 번째 위치와 교환한다. 이 과정을 반복하며, 매 단계마다 하나의 최소값이 고정된다. 완전히 정렬되기 전까지 모든 요소를 검사하므로 시간 복잡도는 항상 O(n²).

- (void)selectionSort:(NSMutableArray *)arr {
    for (int i = 0; i < arr.count - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < arr.count; j++) {
            if ([arr[j] intValue] < [arr[minIndex] intValue]) {
                minIndex = j;
            }
        }
        if (minIndex != i) {
            [arr exchangeObjectAtIndex:i withObjectAtIndex:minIndex];
        }
    }
}

3. 삽입 정렬 (Insertion Sort)

정렬된 부분과 아직 정렬되지 않은 부분을 구분하여, 새로운 요소를 적절한 위치에 삽입하는 방식이다. 이미 정렬된 배열에서는 매우 효율적이며, 최선의 경우 O(n)의 성능을 보인다. 그러나 최악의 경우 O(n²)의 시간이 소요된다.

- (void)insertionSort:(NSMutableArray *)arr {
    for (int i = 1; i < arr.count; i++) {
        int key = [arr[i] intValue];
        int j = i - 1;
        while (j >= 0 && [arr[j] intValue] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = @key;
    }
}

4. 쉘 정렬 (Shell Sort)

삽입 정렬의 개선형으로, 간격을 점차 줄이며 여러 그룹 내에서 삽입 정렬을 수행한다. 초기에는 큰 간격으로 분할하여 요소들을 대략적으로 정렬한 후, 점점 더 좁은 간격으로 조정하며 최종적으로 전체 배열을 정렬한다. 평균적으로 O(n log n)의 성능을 기대할 수 있다.

- (void)shellSort:(NSMutableArray *)arr {
    int gap = arr.count / 2;
    while (gap > 0) {
        for (int i = gap; i < arr.count; i++) {
            int temp = [arr[i] intValue];
            int j = i;
            while (j >= gap && [arr[j - gap] intValue] > temp) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = @temp;
        }
        gap /= 2;
    }
}

5. 힙 정렬 (Heap Sort)

완전 이진 트리 구조를 활용해 최댓값(또는 최솟값)을 루트 노드로 유지하면서 정렬한다. 먼저 주어진 배열을 대규모 힙(최대 힙)으로 변환한 후, 루트와 마지막 요소를 교환하고, 힙 크기를 줄여가며 다시 정렬한다. 이 과정을 반복하여 전체 배열을 오름차순으로 정렬한다. 시간 복잡도는 O(n log n).

- (void)heapSort:(NSMutableArray *)arr {
    NSInteger size = arr.count;
    // 최대 힙 구성
    for (NSInteger i = size / 2 - 1; i >= 0; i--) {
        [self heapify:arr size:size index:i];
    }
    // 힙 정렬
    while (size > 0) {
        [arr exchangeObjectAtIndex:0 withObjectAtIndex:size - 1];
        size--;
        [self heapify:arr size:size index:0];
    }
}

- (void)heapify:(NSMutableArray *)arr size:(NSInteger)size index:(NSInteger)root {
    NSInteger left = 2 * root + 1;
    NSInteger right = left + 1;
    NSInteger largest = root;

    if (left < size && [arr[left] intValue] > [arr[largest] intValue]) {
        largest = left;
    }
    if (right < size && [arr[right] intValue] > [arr[largest] intValue]) {
        largest = right;
    }

    if (largest != root) {
        [arr exchangeObjectAtIndex:root withObjectAtIndex:largest];
        [self heapify:arr size:size index:largest];
    }
}

6. 합병 정렬 (Merge Sort)

분할 정복 전략을 기반으로 하며, 배열을 두 부분으로 나누고, 각 부분을 재귀적으로 정렬한 후, 두 정렬된 배열을 합쳐서 하나의 정렬된 배열을 만든다. 모든 비교 연산에서 안정성(같은 값의 순서 유지)을 보장하며, 항상 O(n log n)의 시간 복잡도를 가진다.

- (NSArray *)mergeSort:(NSArray *)arr {
    if (arr.count <= 1) return arr;

    NSInteger mid = arr.count / 2;
    NSArray *left = [arr subarrayWithRange:NSMakeRange(0, mid)];
    NSArray *right = [arr subarrayWithRange:NSMakeRange(mid, arr.count - mid)];

    left = [self mergeSort:left];
    right = [self mergeSort:right];

    return [self merge:left right:right];
}

- (NSArray *)merge:(NSArray *)left right:(NSArray *)right {
    NSMutableArray *result = [NSMutableArray array];
    NSInteger i = 0, j = 0;

    while (i < left.count && j < right.count) {
        if ([left[i] floatValue] <= [right[j] floatValue]) {
            [result addObject:left[i++]];
        } else {
            [result addObject:right[j++]];
        }
    }

    while (i < left.count) [result addObject:left[i++]];
    while (j < right.count) [result addObject:right[j++]];

    return result;
}

7. 퀵 정렬 (Quick Sort)

피벗 값을 기준으로 배열을 두 부분으로 나누고, 왼쪽은 피벗보다 작고 오른쪽은 피벗보다 큰 요소들로 구성한다. 이후 각 부분에 대해 동일한 과정을 재귀적으로 반복하여 전체 배열을 정렬한다. 평균적인 성능은 O(n log n)이나, 최악의 경우 O(n²)가 될 수 있다.

- (void)quickSort:(NSMutableArray *)arr low:(int)low high:(int)high {
    if (low < high) {
        int pivotIndex = [self partition:arr low:low high:high];
        [self quickSort:arr low:low high:pivotIndex - 1];
        [self quickSort:arr low:pivotIndex + 1 high:high];
    }
}

- (int)partition:(NSMutableArray *)arr low:(int)low high:(int)high {
    int pivot = [arr[low] intValue];
    int left = low + 1;
    int right = high;

    while (YES) {
        while (left <= right && [arr[left] intValue] <= pivot) left++;
        while (left <= right && [arr[right] intValue] > pivot) right--;
        if (left > right) break;
        [arr exchangeObjectAtIndex:left withObjectAtIndex:right];
    }
    [arr exchangeObjectAtIndex:low withObjectAtIndex:right];
    return right;
}

태그: 정렬 알고리즘 버블 정렬 선택 정렬 삽입 정렬 쉘 정렬

10월 8일 13:10에 게시됨