본문 바로가기
Development/Baekjoon

[C#] 2751번: 수 정렬하기 2

by Mobics 2025. 10. 18.

목차


    백준 단계별로 풀어보기

    25.10.18

    13단계: 정렬


    2751번: 수 정렬하기 2

    문제 링크 : https://www.acmicpc.net/problem/2751

     

    문제 풀이

    >> 문제를 풀기 위해 알아야 할 개념

    - 퀵 정렬(Quick Sort)

    : 분할 정복 방법을 통해 정렬하는 알고리즘으로 다음과 같은 과정을 통해 정렬한다.

    1. 원소 하나를 기준(피벗, pivot)으로 삼는다.
    2. 피벗보다 작은 것을 앞으로 빼내고 그 뒤에 피벗을 옮겨 피벗보다 작은 것, 큰 것으로 나눈다. --> 이를 'partition step'이라 한다.
    3. 나누어진 각각에서 다시 피벗을 잡고 정렬해서 각각의 크기가 0이나 1이 될 때까지 정렬한다.

    → 퀵 정렬의 핵심은 어떻게 pivot을 선정하는가?

    : 피벗을 최솟값이나 최댓값으로 계속해서 잡게 되는 최악의 경우 O(n²)의 시간복잡도를 갖게 된다.

     

    >> pivot을 선정하는 여러가지 방식

    1. 첫번째 값이나 마지막 값을 pivot으로 선정한다. --> 퀵 정렬의 원리와 과정을 이해하기 가장 용이한 방법
    2. 중간값을 pivot으로 선정한다.
    3. 무작위 값을 pivot으로 선정한다.
    4. Median of Three : 첫번째 값, 중간값, 마지막 값을 먼저 정렬하고 그 중 중간값을 pivot으로 선정한다. --> 성능적으로 더욱 향상된 방식이다.

     

    >> Partition Step 방식

    1. Hoare Partition : 주로 첫번째 값을 pivot으로 선정하고, 양쪽 끝에서 포인터를 시작해 pivot보다 작은 값을 앞으로, pivot보다 큰 값을 뒤로 모은 뒤, 마지막에 pivot을 정확한 자리에 이동시킨다.
      •     구현이 좀 복잡하지만 성능이 더 우수하다.
    2. Lomuto Partition : 마지막 값을 pivot으로 고정하고, 하나의 포인터를 사용해서 pivot보다 작은 값들을 앞으로 모은 뒤, 마지막에 pivot을 정확한 자리에 이동시킨다.
      •     비효율적이라 큰 배열이나 중복이 많은 데이터에서 성능이 저하되지만 간단하고 직관적이다.

     

    ex) 첫번째 값을 pivot으로 선정한 방식 (Hoare Partition)

     

    - 코드

    private int Partition(int[] arr, int l, int h)
    {
        int pivot = arr[l]; // pivot 설정
        int low = l + 1;    // low는 pivot의 바로 다음 위치
        int high = h;
    
        while (low <= high)
        {
        	if (arr[low] < pivot)	// pivot보다 큰 값이 나올 때까지 이동
            {
                low++;
                continue;
            }
    
            if (arr[high] > pivot)  // pivot보다 작은 값이 나올 때까지 이동
            {
                high--;
                continue;
            }
            
            if (low < high)    // low와 high가 멈춘 지점이 서로 역전되지 않았다면
                (arr[low], arr[high]) = (arr[high], arr[low]);  // swap
        }
        (arr[l], arr[high]) = (arr[high], arr[l]);  // pivot과 high swap
        return high;    // pivot 위치 반환
    }
    
    public void MyQuickSort(int[] arr, int l, int h)
    {
        if (l < h)
        {
            int pivot = Partition(arr, l, h);
            MyQuickSort(arr, l, pivot - 1);
            MyQuickSort(arr, pivot + 1, h);
        }
    }

     

    ※ 이해를 위한 영상

    https://youtu.be/3San3uKKHgg

     

    └ 풀이 및 오답노트

    : 시간 초과로 자꾸 실패하였다.

     

    >> 첫 오답

    - 작성한 코드

    <hide/>
    class Backjoon
    {
        static void Main(string[] args)
        {
            int count = int.Parse(Console.ReadLine());
            int[] arr = new int[count];
    
            for (int i = 0; i < count; i++)
            {
                arr[i] = int.Parse(Console.ReadLine());
            }
    
            MyQuickSort(arr, 0, count - 1);
    
            foreach (var t in arr)
            {
                Console.WriteLine(t);
            }
        }
    
        private static int Partition(int[] arr, int l, int h)
        {
            int pivot = arr[l];
            int low = l + 1;
            int high = h;
    
            while (low <= high)
            {
                if (arr[low] < pivot)
                {
                    low++;
                    continue;
                }
    
                if (arr[high] > pivot)
                {
                    high--;
                    continue;
                }
    
                if (low < high)
                    (arr[low], arr[high]) = (arr[high], arr[low]);
            }
    
            (arr[l], arr[high]) = (arr[high], arr[l]);
            return high;
        }
    
        public static void MyQuickSort(int[] arr, int l, int h)
        {
            if (l < h)
            {
                int pivot = Partition(arr, l, h);
                MyQuickSort(arr, l, pivot - 1);
                MyQuickSort(arr, pivot + 1, h);
            }
        }
    }

     

     

    >> 두 번째 오답

    : Console.WriteLine()이 생각보다 시간을 많이 잡아먹기 때문에 StringBuilder를 활용하여 출력하도록 변경

     

    - 작성한 코드

    <hide/>
    using System.Text;
    
    class Backjoon
    {
        static void Main(string[] args)
        {
            StringBuilder sb = new StringBuilder();
            int count = int.Parse(Console.ReadLine());
            int[] arr = new int[count];
    
            for (int i = 0; i < count; i++)
            {
                arr[i] = int.Parse(Console.ReadLine());
            }
    
            MyQuickSort(arr, 0, count - 1);
    
            foreach (var t in arr)
            {
                sb.AppendLine($"{t}");
            }
            Console.Write(sb.ToString().TrimEnd());
        }
    
        private static int Partition(int[] arr, int l, int h)
        {
            int pivot = arr[l];
            int low = l + 1;
            int high = h;
    
            while (low <= high)
            {
                if (arr[low] < pivot)
                {
                    low++;
                    continue;
                }
    
                if (arr[high] > pivot)
                {
                    high--;
                    continue;
                }
    
                if (low < high)
                    (arr[low], arr[high]) = (arr[high], arr[low]);
            }
    
            (arr[l], arr[high]) = (arr[high], arr[l]);
            return high;
        }
    
        public static void MyQuickSort(int[] arr, int l, int h)
        {
            if (l < h)
            {
                int pivot = Partition(arr, l, h);
                MyQuickSort(arr, l, pivot - 1);
                MyQuickSort(arr, pivot + 1, h);
            }
        }
    }

     

    >> 최종 수정

    : StringBuilder를 사용해도 시간 초과가 발생했다. 이는 현재 작성한 정렬 알고리즘으로는 이미 정렬된 입력(예: {5, 4, 3, 2, 1}, {1, 2, 3, 4, 5})과 같이 최악의 경우 O(n²)의 시간복잡도를 갖게 될 수도 있기 때문이다.

    --> 처음에 pivot을 설정할 때, 랜덤으로 정하여 분할의 편향을 줄인다. 이후, 랜덤으로 정한 pivot을 첫번째 값과 swap하여 기존의 Quick Sort 방식대로 정렬한다.

     

    >> 풀이

    : 랜덤으로 pivot을 선정하고 이를 첫번째 값과 swap하여 Hoare 방식으로 Quick Sort

    1. 랜덤으로 pivot 값을 뽑기 위해 Random을 선언한다. --> Partition 함수에서 사용할 것이기 때문에 Main 밖에 static으로 선언한다.
    2. StringBuilder를 선언하고, 입력값을 받아 int값으로 변환하여 수의 개수를 담을 변수 count 에 담고, 수를 담을 int형 배열 arr 를 초기화한다.
    3. for 반복문을 통해 입력 받은 수를 int값으로 변환하여 arr 에 담는다.
    4. 만든 MyQuickSort 함수를 통해 배열 arr 를 정렬한다.
    5. foreach 반복문을 통해 배열 arr 에 있는 값을 StringBuilder에 담는다.
    6. StringBuilder에 있는 값을 출력한다.

     

    정답 코드

    using System.Text;
    
    class Backjoon
    {
        static Random rand = new Random();
        static void Main(string[] args)
        {
            StringBuilder sb = new StringBuilder();
            int count = int.Parse(Console.ReadLine());
            int[] arr = new int[count];
    
            for (int i = 0; i < count; i++)
            {
                arr[i] = int.Parse(Console.ReadLine());
            }
    
            MyQuickSort(arr, 0, count - 1);
    
            foreach (var t in arr)
                sb.AppendLine($"{t}");
            Console.Write(sb.ToString().TrimEnd());
        }
    
        private static int Partition(int[] arr, int l, int h)
        {
            int randomPivot = rand.Next(l, h + 1);
            (arr[l], arr[randomPivot]) = (arr[randomPivot], arr[l]);
    
            int pivot = arr[l];
            int low = l + 1;
            int high = h;
    
            while (low <= high)
            {
                if (arr[low] < pivot)
                {
                    low++;
                    continue;
                }
    
                if (arr[high] > pivot)
                {
                    high--;
                    continue;
                }
    
                if (low < high)
                    (arr[low], arr[high]) = (arr[high], arr[low]);
            }
    
            (arr[l], arr[high]) = (arr[high], arr[l]);
            return high;
        }
    
        public static void MyQuickSort(int[] arr, int l, int h)
        {
            if (l < h)
            {
                int pivot = Partition(arr, l, h);
                MyQuickSort(arr, l, pivot - 1);
                MyQuickSort(arr, pivot + 1, h);
            }
        }
    }

    'Development > Baekjoon' 카테고리의 다른 글

    [C#] 1427번: 소트인사이드  (0) 2025.10.20
    [C#] 10989번: 수 정렬하기 3  (0) 2025.10.19
    [C#] 25305번: 커트라인  (0) 2025.10.17
    [C#] 2587번: 대표값2  (0) 2025.10.16
    [C#] 2750번: 수 정렬하기  (0) 2025.10.15