일곱 가지 정렬 알고리즘 요약

1.1 개념

정렬: 레코드 집합을 특정 키 값의 크기를 기준으로 오름차순 또는 내림차순으로 재배열하는 작업

안정성: 동일 키 값을 가진 레코드들이 정렬 후에도 원본 순서를 유지하면 안정적, 그렇지 않으면 불안정

1.2 일반적인 응용

  • 대학 순위 산정
  • 상품 정렬 시스템

2.1 삽입 정렬

두 번째 요소부터 기준 설정, 이전 요소들과 비교하며 적절한 위치에 삽입

public static void 삽입정렬(int[] 배열) {
    for (int i = 1; i < 배열.length; i++) {
        int 현재값 = 배열[i];
        int j = i - 1;
        while (j >= 0 && 배열[j] > 현재값) {
            배열[j + 1] = 배열[j];
            j--;
        }
        배열[j + 1] = 현재값;
    }
}

특징:

  • 데이터 유사 정렬 상태에서 효율적
  • 시간 복잡도: O(n²)
  • 공간 복잡도: O(1)
  • 안정적

2.2 셸 정렬

삽입 정렬 개선, 간격(gap) 설정하여 부분 배열 정렬 후 간격 축소

public static void 셸정렬(int[] 배열) {
    int 간격 = 배열.length / 2;
    while (간격 > 0) {
        for (int i = 간격; i < 배열.length; i++) {
            int 임시 = 배열[i];
            int j = i;
            while (j >= 간격 && 배열[j - 간격] > 임시) {
                배열[j] = 배열[j - 간격];
                j -= 간격;
            }
            배열[j] = 임시;
        }
        간격 /= 2;
    }
}

특징:

  • 간격 1에서 삽입 정렬로 전환
  • 시간 복잡도: O(n¹·²⁵ ~ n¹·⁶)
  • 불안정

2.3 선택 정렬

최소값 탐색 후 현재 위치와 교환

public static void 선택정렬(int[] 배열) {
    for (int i = 0; i < 배열.length - 1; i++) {
        int 최소위치 = i;
        for (int j = i + 1; j < 배열.length; j++) {
            if (배열[j] < 배열[최소위치]) 
                최소위치 = j;
        }
        int 임시 = 배열[최소위치];
        배열[최소위치] = 배열[i];
        배열[i] = 임시;
    }
}

특징:

  • 시간 복잡도: O(n²)
  • 실무에서 비효율적

2.4 버블 정렬

인접 요소 비교하며 교환, 라운드마다 최대값이 끝으로 이동

public static void 버블정렬(int[] 배열) {
    for (int i = 0; i < 배열.length - 1; i++) {
        boolean 교환발생 = false;
        for (int j = 0; j < 배열.length - i - 1; j++) {
            if (배열[j] > 배열[j + 1]) {
                int 임시 = 배열[j];
                배열[j] = 배열[j + 1];
                배열[j + 1] = 임시;
                교환발생 = true;
            }
        }
        if (!교환발생) break;
    }
}

특징:

  • 안정적
  • 간단하지만 대규모 데이터에 비효율적

2.5 힙 정렬

최대 힙 구성 후 루트와 마지막 요소 교환, 힙 크기 축소하며 반복

public static void 힙정렬(int[] 배열) {
    // 최대 힙 생성
    for (int i = 배열.length / 2 - 1; i >= 0; i--)
        힙조정(배열, 배열.length, i);
    
    // 요소 추출 및 힙 조정
    for (int i = 배열.length - 1; i > 0; i--) {
        int 임시 = 배열[0];
        배열[0] = 배열[i];
        배열[i] = 임시;
        힙조정(배열, i, 0);
    }
}

private static void 힙조정(int[] 배열, int 힙크기, int 부모) {
    int 최대 = 부모;
    int 왼쪽 = 2 * 부모 + 1;
    int 오른쪽 = 2 * 부모 + 2;
    
    if (왼쪽 < 힙크기 && 배열[왼쪽] > 배열[최대]) 
        최대 = 왼쪽;
    if (오른쪽 < 힙크기 && 배열[오른쪽] > 배열[최대]) 
        최대 = 오른쪽;
        
    if (최대 != 부모) {
        int 교환 = 배열[부모];
        배열[부모] = 배열[최대];
        배열[최대] = 교환;
        힙조정(배열, 힙크기, 최대);
    }
}

특징:

  • 대용량 데이터에 적합
  • 시간 복잡도: O(n log n)
  • 불안정

2.6 퀵 정렬

피벗 선택 후 분할 정복, 좌측엔 작은 값, 우측엔 큰 값 배치

2.6.1 호어 분할

private static int 분할(int[] 배열, int 시작, int 끝) {
    int 피벗 = 배열[시작];
    int i = 시작 + 1;
    int j = 끝;
    while (i <= j) {
        while (i <= 끝 && 배열[i] <= 피벗) i++;
        while (j > 시작 && 배열[j] >= 피벗) j--;
        if (i < j) {
            int 임시 = 배열[i];
            배열[i] = 배열[j];
            배열[j] = 임시;
        }
    }
    배열[시작] = 배열[j];
    배열[j] = 피벗;
    return j;
}

2.6.2 삼중 분할

public static void 삼중분할(int[] 배열, int 왼쪽, int 오른쪽) {
    if (왼쪽 >= 오른쪽) return;
    int 피벗 = 배열[new Random().nextInt(오른쪽 - 왼쪽) + 왼쪽];
    int l = 왼쪽 - 1;
    int r = 오른쪽 + 1;
    int i = 왼쪽;
    while (i < r) {
        if (배열[i] < 피벗) {
            l++;
            int 임시 = 배열[l];
            배열[l] = 배열[i];
            배열[i] = 임시;
            i++;
        } else if (배열[i] > 피벗) {
            r--;
            int 임시 = 배열[r];
            배열[r] = 배열[i];
            배열[i] = 임시;
        } else {
            i++;
        }
    }
    삼중분할(배열, 왼쪽, l);
    삼중분할(배열, r, 오른쪽);
}

특징:

  • 피벗 선택 최적화 필요
  • 평균 시간 복잡도: O(n log n)

2.7 병합 정렬

배열 분할 후 정렬하며 병합

public static void 병합정렬(int[] 배열) {
    if (배열.length < 2) return;
    int 중간 = 배열.length / 2;
    int[] 왼쪽 = Arrays.copyOfRange(배열, 0, 중간);
    int[] 오른쪽 = Arrays.copyOfRange(배열, 중간, 배열.length);
    
    병합정렬(왼쪽);
    병합정렬(오른쪽);
    병합(배열, 왼쪽, 오른쪽);
}

private static void 병합(int[] 결과, int[] 왼쪽, int[] 오른쪽) {
    int i = 0, j = 0, k = 0;
    while (i < 왼쪽.length && j < 오른쪽.length) {
        if (왼쪽[i] <= 오른쪽[j]) 
            결과[k++] = 왼쪽[i++];
        else 
            결과[k++] = 오른쪽[j++];
    }
    while (i < 왼쪽.length) 결과[k++] = 왼쪽[i++];
    while (j < 오른쪽.length) 결과[k++] = 오른쪽[j++];
}

특징:

  • 안정적
  • 시간 복잡도: O(n log n)
  • 공간 복잡도: O(n)

태그: 삽입정렬 셸정렬 선택정렬 버블정렬 힙정렬

5월 26일 11:08에 게시됨