답변:
이 논문 에는 몇 가지 분석이 있습니다.
또한 Wikipedia에서 :
quicksort의 가장 직접적인 경쟁자는 heapsort입니다. 힙 정렬은 일반적으로 빠른 정렬보다 다소 느리지 만 최악의 경우 실행 시간은 항상 Θ (nlogn)입니다. Quicksort는 일반적으로 더 빠르지 만 잘못된 경우가 감지되면 heapsort로 전환되는 introsort 변형을 제외하고는 최악의 성능 가능성이 남아 있습니다. 힙 정렬이 필요하다는 것을 미리 알고있는 경우 직접 사용하는 것이 introsort가 전환 될 때까지 기다리는 것보다 빠릅니다.
힙 정렬은 O (N log N) 보장되며, Quicksort의 최악의 경우보다 훨씬 낫습니다. Heapsort는 Mergesort에 필요한대로 정렬 된 데이터를 넣는 데 다른 배열에 대해 더 많은 메모리가 필요하지 않습니다. 그렇다면 상용 애플리케이션이 Quicksort를 고수하는 이유는 무엇입니까? 다른 구현에 비해 특별한 Quicksort는 무엇입니까?
나는 알고리즘을 직접 테스트했으며 Quicksort에 실제로 특별한 것이 있음을 보았습니다. 힙 및 병합 알고리즘보다 훨씬 빠르게 실행됩니다.
Quicksort의 비결은 : 불필요한 요소 교체를 거의 수행하지 않습니다. 스왑은 시간이 많이 걸립니다.
Heapsort를 사용하면 모든 데이터가 이미 정렬 된 경우에도 배열을 정렬하기 위해 100 % 요소를 교체 할 것입니다.
Mergesort를 사용하면 더 나빠집니다. 데이터가 이미 정렬 된 경우에도 다른 배열에 100 % 요소를 쓰고 원래 배열에 다시 쓸 것입니다.
Quicksort를 사용하면 이미 주문한 것을 교체 할 수 없습니다. 데이터가 완전히 주문되면 거의 스왑이 없습니다! 최악의 경우에 대해 많은 소란이 있지만, 배열의 첫 번째 또는 마지막 요소를 얻는 것 외에 피벗 선택을 약간 개선하면 피벗을 피할 수 있습니다. 첫 번째, 마지막 및 중간 요소 사이의 중간 요소에서 피벗을 얻으면 최악의 경우를 피하는 것으로 충분합니다.
Quicksort에서 우월한 것은 최악의 경우가 아니라 최상의 경우입니다! 가장 좋은 경우에는 동일한 수의 비교를 수행하지만 거의 아무것도 바꾸지 않습니다. 평균적으로 Heapsort 및 Mergesort에서와 같이 모든 요소가 아닌 요소의 일부를 교체합니다. 이것이 Quicksort에 최고의 시간을 제공하는 것입니다. 더 적은 스왑, 더 빠른 속도.
릴리스 모드에서 실행되는 내 컴퓨터의 C #에서 아래 구현은 Array.Sort를 중간 피벗으로 3 초, 개선 된 피벗으로 2 초 더 낫습니다 (예, 좋은 피벗을 얻기위한 오버 헤드가 있음).
static void Main(string[] args)
{
int[] arrToSort = new int[100000000];
var r = new Random();
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
Console.WriteLine("Press q to quick sort, s to Array.Sort");
while (true)
{
var k = Console.ReadKey(true);
if (k.KeyChar == 'q')
{
// quick sort
Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
QuickSort(arrToSort, 0, arrToSort.Length - 1);
Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
else if (k.KeyChar == 's')
{
Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
Array.Sort(arrToSort);
Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
}
}
static public void QuickSort(int[] arr, int left, int right)
{
int begin = left
, end = right
, pivot
// get middle element pivot
//= arr[(left + right) / 2]
;
//improved pivot
int middle = (left + right) / 2;
int
LM = arr[left].CompareTo(arr[middle])
, MR = arr[middle].CompareTo(arr[right])
, LR = arr[left].CompareTo(arr[right])
;
if (-1 * LM == LR)
pivot = arr[left];
else
if (MR == -1 * LR)
pivot = arr[right];
else
pivot = arr[middle];
do
{
while (arr[left] < pivot) left++;
while (arr[right] > pivot) right--;
if(left <= right)
{
int temp = arr[right];
arr[right] = arr[left];
arr[left] = temp;
left++;
right--;
}
} while (left <= right);
if (left < end) QuickSort(arr, left, end);
if (begin < right) QuickSort(arr, begin, right);
}
대부분의 상황에서 빠른 속도와 약간 빠른 속도는 관련이 없습니다. 때때로 속도가 느려지는 것을 결코 원하지 않습니다. 느린 상황을 피하기 위해 QuickSort를 조정할 수 있지만 기본 QuickSort의 우아함을 잃게됩니다. 따라서 대부분의 경우 실제로 HeapSort를 선호합니다. 완전히 단순하면서도 우아하게 구현할 수 있으며 결코 느린 정렬을 얻을 수 없습니다.
대부분의 경우 최대 속도를 원할 경우 QuickSort가 HeapSort보다 선호 될 수 있지만 둘 다 정답이 아닐 수 있습니다. 속도가 중요한 상황의 경우 상황의 세부 사항을 면밀히 검토하는 것이 좋습니다. 예를 들어, 속도가 중요한 일부 코드에서 데이터가 이미 정렬되었거나 거의 정렬되어있는 것이 매우 일반적입니다 (종종 함께 위아래로 이동하거나 서로 반대 방향으로 위아래로 이동하는 여러 관련 필드를 인덱싱하고 있습니다. 따라서 하나를 기준으로 정렬하면 다른 항목이 정렬되거나 반대로 정렬되거나 닫힙니다. 둘 중 하나가 QuickSort를 종료 할 수 있습니다. 이 경우 둘 다 구현하지 않았습니다 ... 대신 Dijkstra의 SmoothSort를 구현했습니다 ... 이미 정렬되었거나 거의 정렬되었을 때 O (N) 인 HeapSort 변형 ... 그다지 우아하지 않고 이해하기 쉽지 않습니다. 하지만 빨리 ... 읽기http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF 코드에 좀 더 어려운 것을 원한다면.
Quicksort-Heapsort 인플레 이스 하이브리드도 정말 흥미 롭습니다. 대부분의 경우 최악의 경우 n * log n 비교 만 필요하기 때문입니다 (무증상의 첫 번째 항과 관련하여 최적이므로 최악의 시나리오를 피할 수 있습니다). of Quicksort), O (log n) 추가 공간이 있으며 이미 주문 된 데이터 세트와 관련하여 Quicksort의 양호한 동작의 "반"이상을 보존합니다. 매우 흥미로운 알고리즘은 http://arxiv.org/pdf/1209.4214v1.pdf 에 Dikert와 Weiss가 제공합니다 .
Comp. 사이 quick sort
와 merge sort
두 이후는 빠른 정렬을위한 시간을 실행하는 wrost 케이스의 실행 시간을 wrost 경우 사이에 차이가 정렬 장소에서의 유형 O(n^2)
분류는 여전히 힙에 대한 O(n*log(n))
데이터의 평균 금액에 대한 빠른 정렬 더 유용 할 것이다. 무작위 알고리즘이므로 정확한 ans를 얻을 확률이 있습니다. 선택한 피벗 요소의 위치에 따라 더 짧은 시간에 달라집니다.
그래서
좋은 전화 : L과 G의 크기는 각각 3s / 4 미만입니다.
잘못된 호출 : L과 G 중 하나의 크기가 3s / 4보다 큽니다.
소량의 경우 삽입 정렬로 이동하고 매우 많은 양의 데이터의 경우 힙 정렬로 이동할 수 있습니다.
힙 정렬은 O (n * log (n)) 의 최악의 실행 사례를 갖는 이점이 있으므로 퀵 정렬이 제대로 수행되지 않을 가능성이있는 경우 (일반적으로 정렬 된 데이터 세트) 힙 정렬이 훨씬 선호됩니다.
Heapsort 는 힙을 빌드 한 다음 최대 항목을 반복적으로 추출합니다. 최악의 경우는 O (n log n)입니다.
그러나 O (n2) 인 빠른 정렬 의 최악의 경우를 보게된다면 빠른 정렬이 대용량 데이터에 적합하지 않다는 것을 깨달았을 것입니다.
그래서 이것은 정렬을 흥미롭게 만듭니다. 오늘날 많은 정렬 알고리즘이 사용되는 이유는 모두 최고의 장소에서 '최고'이기 때문이라고 생각합니다. 예를 들어, 거품 정렬은 데이터가 정렬되면 빠른 정렬을 수행 할 수 있습니다. 또는 정렬 할 항목에 대해 알고 있으면 더 잘할 수 있습니다.
이것은 귀하의 질문에 직접 답변하지 않을 수 있습니다.
힙 정렬은 매우 큰 입력을 처리 할 때 안전한 방법입니다. 점근 분석에 따르면 최악의 경우 Heapsort의 성장 순서는이며 최악의 경우 Big-O(n logn)
Quicksort보다 낫습니다 Big-O(n^2)
. 그러나 Heapsort 는 잘 구현 된 빠른 정렬보다 실제로 대부분의 컴퓨터에서 다소 느립니다. Heapsort는 안정적인 정렬 알고리즘도 아닙니다.
heapsort가 실제로 quicksort보다 느린 이유는 데이터 요소가 상대적으로 가까운 저장 위치에있는 quicksort 의 더 나은 참조 지역성 ( " https://en.wikipedia.org/wiki/Locality_of_reference ") 때문입니다. 강력한 참조 지역성을 나타내는 시스템은 성능 최적화를위한 훌륭한 후보입니다. 그러나 힙 정렬은 더 큰 도약을 처리합니다. 이것은 더 작은 입력에 대해 퀵 정렬을 더 유리하게 만듭니다.
나에게는 heapsort와 quicksort 사이에 매우 근본적인 차이점이 있습니다. 후자는 재귀를 사용합니다. 재귀 알고리즘에서 힙은 재귀 횟수에 따라 증가합니다. n 이 작은 경우에는 중요하지 않지만 지금은 n = 10 ^ 9 !!로 두 개의 행렬을 정렬하고 있습니다. 이 프로그램은 거의 10GB의 램을 사용하며 추가 메모리가 있으면 내 컴퓨터가 가상 디스크 메모리로 스와핑을 시작합니다. 내 디스크는 RAM 디스크이지만 여전히 교체하면 속도에 큰 차이가 있습니다. 따라서 프로그래머에게 미리 알려지지 않은 크기와 비모수 적 통계 정렬 유형을 포함하는 조정 가능한 차원 매트릭스를 포함하는 C ++로 코딩 된 statpack에서 매우 큰 데이터 매트릭스와 함께 사용하는 지연을 피하기 위해 힙 정렬을 선호합니다.
원래 질문에 답하고 다른 의견을 여기에서 해결하려면 :
방금 선택, 퀵, 병합 및 힙 정렬의 구현을 비교하여 서로 겹쳐지는 방식을 확인했습니다. 대답은 모두 단점이 있다는 것입니다.
요약 : Quick은 최고의 범용 정렬 (합리적으로 빠르고 안정적이며 대부분 제자리에 있음)입니다. 개인적으로 안정적인 정렬이 필요하지 않는 한 힙 정렬을 선호합니다.
선택-N ^ 2-20 개 미만의 요소에만 적합하며 성능이 뛰어납니다. 데이터가 이미 정렬되어 있지 않거나 거의 거의 정렬되지 않은 경우. N ^ 2는 정말 빠르게 느려집니다.
내 경험상 빠르다 는 것은 실제로 항상 그렇게 빠르지 는 않습니다 . 빠른 정렬을 일반 정렬로 사용하는 경우 보너스는 합리적으로 빠르고 안정적이라는 것입니다. 또한 내부 알고리즘이지만 일반적으로 재귀 적으로 구현되므로 추가 스택 공간을 차지합니다. 또한 O (n log n)과 O (n ^ 2) 사이에 있습니다. 어떤 종류의 타이밍은 특히 값이 좁은 범위 내에있을 때이를 확인하는 것 같습니다. 10,000,000 개 항목에 대한 선택 정렬보다 빠르지 만 병합 또는 힙보다 느립니다.
병합 정렬은 데이터에 종속되지 않으므로 O (n log n)가 보장됩니다. 당신이 어떤 가치를 부여했는지에 상관없이 그것은 단지 그것이하는 일을합니다. 또한 안정적이지만 구현에주의하지 않으면 매우 큰 정렬이 스택을 날려 버릴 수 있습니다. 복잡한 내부 병합 정렬 구현이 있지만 일반적으로 값을 병합하려면 각 수준에 다른 배열이 필요합니다. 이러한 어레이가 스택에 있으면 문제가 발생할 수 있습니다.
힙 정렬은 최대 O (n log n)이지만 대부분의 경우 값을 log n 딥 힙 위로 얼마나 멀리 이동해야하는지에 따라 더 빠릅니다. 힙은 원래 배열에서 쉽게 제자리에 구현할 수 있으므로 추가 메모리가 필요하지 않고 반복적이므로 반복되는 동안 스택 오버플로에 대해 걱정할 필요가 없습니다. 힙 정렬 의 큰 단점은 안정적인 정렬이 아니라는 것입니다. 즉, 필요한 경우 바로 사용할 수 있습니다.