병합 정렬
Merge sort학급 | 정렬 알고리즘 |
---|---|
data 구조 | 어레이 |
최악의 경우 성능 | |
베스트 케이스 성능 | ( n n ) { ( \log n ) ,,, ( ) \ \ n )자연 변종 |
평균 성능 | |
최악의 경우 공간의 복잡성 | ( )\ O ()\ O ( ) ( 1 O (1)보조, 링크[1] 리스트포함 |
컴퓨터 공학에서 병합 정렬(일반적으로 mergesort라고도 함)은 효율적인 범용 비교 기반 정렬 알고리즘입니다.대부분의 구현은 안정적인 정렬을 생성합니다. 즉, 동일한 요소의 순서가 입력과 출력에서 동일함을 의미합니다.Marge Sort는 [2]1945년 John von Neumann에 의해 발명된 분할 정복 알고리즘입니다.보텀업 머지 정렬에 대한 자세한 설명과 분석은 1948년 Goldstine과 von Neumann의 보고서에 [3]실렸다.
알고리즘.
개념적으로 머지 정렬은 다음과 같이 동작합니다.
- 정렬되지 않은 목록을 n개의 서브리스트로 분할합니다.각 서브리스트에는 1개의 요소가 포함됩니다(한 요소의 리스트는 정렬된 것으로 간주됩니다).
- 하위 목록을 반복적으로 병합하여 정렬된 하위 목록을 새로 생성합니다. 하위 목록이 하나만 남아 있을 때까지.이것이 정렬된 목록이 됩니다.
톱다운 구현
예: 하위 목록 크기가 1이 될 때까지 목록(이 예에서는 실행)을 하위 목록으로 반복적으로 분할한 다음 하위 목록을 병합하여 정렬된 목록을 생성하는 하향식 병합 알고리즘에 인덱스를 사용하는 C-라이크 코드입니다.카피백 스텝은, 머지 방향을 각 재귀 레벨과 바꾸어 회피합니다(초기 1회 카피는 제외합니다만, 피할 수 있습니다).이를 이해하기 위해 2개의 요소가 있는 어레이를 살펴보겠습니다.요소는 B[]에 복사된 후 A[]에 병합됩니다.4개의 요소가 있는 경우 재귀 레벨의 하부에 도달하면 A[]로부터의 단일 요소 실행이 B[]로 Marge되고 다음으로 높은 재귀 레벨에서는 이들 2개의 요소 실행이 A[]로 Marge됩니다.이 패턴은 각 재귀 수준에서 계속됩니다.
// 어레이 A[]에는 정렬할 항목이 있습니다. 어레이 B[]는 작업용 어레이입니다. 무효 Top Down Merge Sort(톱다운 머지 정렬)(A[], B[], n) { 카피 어레이(A, 0, n, B); // A[]를 B[]에 1회 복사 톱다운 스플릿 머지(B, 0, n, A); // B[]에서 A[]로 데이터 정렬 } // A[]를 2개의 실행으로 분할하고, 두 실행 모두를 B[]로 정렬하고, 두 실행 모두를 B[]에서 A[]로 병합합니다. // iBegin은 포함, iEnd는 제외입니다(A[iEnd]는 세트에 없습니다). 무효 톱다운 스플릿 머지(B[], 아이비긴, iEnd, A[]) { 한다면 (iEnd - 아이비긴 <=> 1) // 실행 크기 == 1인 경우 돌아가다; // 정렬된 것으로 간주 // 1개보다 긴 실행 항목을 반으로 나눕니다. i미들 = (iEnd + 아이비긴) / 2; // i중간 = 중간점 // 어레이 A[]에서 B[]로 양쪽 실행을 재귀적으로 정렬합니다. 톱다운 스플릿 머지(A, 아이비긴, i미들, B); // 왼쪽 실행 정렬 톱다운 스플릿 머지(A, i미들, iEnd, B); // 올바른 실행 정렬 // 어레이 B[]의 결과 실행을 A[]에 병합합니다. 톱다운 머지(B, 아이비긴, i미들, iEnd, A); } // 왼쪽 소스의 절반은 A[iBegin:iMiddle-1]입니다. // 오른쪽 소스의 절반은 A[iMiddle:iEnd-1]입니다. // 결과는 B [ iBegin : iEnd-1 ]입니다. 무효 톱다운 머지(A[], 아이비긴, i미들, iEnd, B[]) { i = 아이비긴, j = i미들; // 왼쪽 또는 오른쪽 실행에 요소가 있는 동안... 위해서 (k = 아이비긴; k < > iEnd; k++) { // 좌측 런 헤드가 존재하고 기존 우측 런 헤드가 <=인 경우. 한다면 (i < > i미들 & & (j >= iEnd A[i] <=> A[j])) { B[k] = A[i]; i = i + 1; } 또 다른 { B[k] = A[j]; j = j + 1; } } } 무효 카피 어레이(A[], 아이비긴, iEnd, B[]) { 위해서 (k = 아이비긴; k < > iEnd; k++) B[k] = A[k]; }
어레이 전체의 정렬은 다음과 같이 하십시오.Top Down Merge Sort(A, B, 길이(A))
상향식 구현
예를 들어 목록을 크기1의 n개의 서브리스트 배열(이 예에서는 실행)로 취급하고 서브리스트를 2개의 버퍼 간에 반복적으로 Marge하는 상향식 Marge Sort 알고리즘에 인덱스를 사용하는 C라이크 코드입니다.
// 어레이 A[]에는 정렬할 항목이 있습니다. 어레이 B[]는 작업용 어레이입니다. 무효 Bottom Up Merge Sort(보텀 업 머지 정렬)(A[], B[], n) { // A에서 실행되는 각 1-element는 이미 "정렬"되어 있습니다. // 길이 2, 4, 8, 16으로 정렬된 계단진행을 연속적으로 길게 만듭니다...전체 배열이 정렬될 때까지. 위해서 (폭 = 1; 폭 < > n; 폭 = 2 * 폭) { // 어레이 A는 길이 폭의 실행으로 가득 차 있습니다. 위해서 (i = 0; i < > n; i = i + 2 * 폭) { // 두 실행 병합:A[i:i+width-1] 및 A[i+width:i+2*width-1] ~ B[] // 또는 A[i:n-1]를 B[]에 복사합니다(i+width >= n인 경우). 보텀업머지(A, i, 분(i+폭, n), 분(i+2*폭, n), B); } // 현재 워크 어레이 B에는 길이 2*폭의 실행이 가득합니다. // 다음 반복을 위해 어레이 B를 어레이 A에 복사합니다. // 좀 더 효율적인 구현은 A와 B의 역할을 바꿉니다. 카피 어레이(B, A, n); // 현재 어레이 A에는 길이 2*폭의 실행이 가득합니다. } } // 왼쪽 실행이 A[iLeft:iRight-1]입니다. // 오른쪽 실행은 A[iRight:iEnd-1]입니다. 무효 보텀업머지(A[], 왼쪽, 아 맞다, iEnd, B[]) { i = 왼쪽, j = 아 맞다; // 왼쪽 또는 오른쪽 실행에 요소가 있는 동안... 위해서 (k = 왼쪽; k < > iEnd; k++) { // 좌측 런 헤드가 존재하고 기존 우측 런 헤드가 <=인 경우. 한다면 (i < > 아 맞다 & & (j >= iEnd A[i] <=> A[j])) { B[k] = A[i]; i = i + 1; } 또 다른 { B[k] = A[j]; j = j + 1; } } } 무효 카피 어레이(B[], A[], n) { 위해서 (i = 0; i < > n; i++) A[i] = B[i]; }
목록을 사용한 하향식 구현
서브리스트가 3차적으로 정렬될 때까지 입력 리스트를 작은 서브리스트로 재귀적으로 분할한 후 콜체인을 되돌리는 동안 서브리스트를 Marge하는 탑다운형 머지 정렬 알고리즘의 의사 코드.
function merge_sort(list m)는 // 베이스 케이스입니다. 0 또는 1개의 요소의 목록이 정의에 따라 정렬됩니다. 길이가 m 1 1이면 m // 재귀 대소문자를 반환합니다.첫째, //equal-sized sublists이 전반전 목록의 후반전으로 구성된로. 끓여이 목록 인덱스 0에 시작한다고 가정한다. strobilacea:= 바로 만약 내 < 나는 m에;(m의 길이)/2 다음 다른 추가에서 왼쪽으로 말야를 끓여 x를 빈 목록 var 각 x를 지수와 = 빈 목록을 떠나 목록을 나눈다.리두 하위 목록을 필기체로 정렬합니다.left : = merge_sublists(왼쪽) right : = merge_sublists(오른쪽) // 그런 다음 현재 삭제된 하위 목록을 병합합니다.return merge(왼쪽, 오른쪽)
이 예에서는 Marge 함수가 왼쪽 서브리스트와 오른쪽 서브리스트를 Marge합니다.
함수 merge(왼쪽, 오른쪽)는 var result: = 빈 목록(왼쪽)이 비어 있지 않고 오른쪽이 비어 있지 않은 경우 먼저(오른쪽) 결과: = rest(왼쪽) first 첫 번째(왼쪽) 결과: = rest(오른쪽) // 왼쪽 또는 오른쪽 중 하나에 elemen이 있을 수 있습니다.ts left; 소비합니다. // (다음 루프 중 하나만 실제로 입력됩니다.) 왼쪽이 비어 있지 않은 상태에서 왼쪽:= rest(왼쪽) 결과:= rest(오른쪽)에 우선 추가:= rest(오른쪽) 결과:= rest(오른쪽) 반환 결과:= rest(오른쪽)
목록을 사용한 상향식 구현
노드에 대한 참조의 작은 고정 크기 배열을 사용하는 상향식 병합 정렬 알고리즘의 의사 코드. 여기서 array [i]는 크기가 2 또는 0인i 목록을 참조합니다.node는 노드에 대한 참조 또는 포인터입니다.merge() 함수는 톱다운마지 리스트의 예와 비슷하며 이미 정렬된2개의 목록을 Marge하여 빈 목록을 처리합니다.이 경우 merge()는 입력 파라미터에 노드를 사용하여 값을 반환합니다.
기능 merge_sort(노드 머리)은// 돌아가면 빈 목록을 보유한 머리)전무한 다음 돌아오nil을 만든다고 노드 array[32], 초기에 모든 것은 아무 것도 바 노드 결과 무효 전력 노드 다음을 의미함을 native나는 기인한다:= 머리//병합 노드로 배열하는 동안 결과≠ 전무하니 다음:=result.next, result.next:)영(나는 정도 0;(나는 <을 말한다. 32)&&(≠ 영 array[나는]), 나는 1+=)것을 하면 결과:= merge(array[나는], 결과)array[나는]:나는 i1array[나는]-= 32정도씩)전무 // 배열의 끝 지난:)결과 결과:)다음 // 단일 목록에 결과 배열이 하나로 가지 않습니다:)영에(나는 0;나는 <, 32;나는 1+=원)resul 한다.t:합병 =(array[i], 결과) 반환 결과
내추럴 머지 정렬
자연 병합 정렬은 입력에서 자연적으로 발생하는 모든 실행( 정렬된 시퀀스)이 이용된다는 점을 제외하고는 상향식 병합 정렬과 유사합니다.목록(또는 동등한 테이프 또는 파일)은 편리한 데이터 구조(FIFO 큐 또는 LIFO [4]스택으로 사용)로 단조로운 실행과 비트닉 실행(업/다운 번갈아 실행)을 모두 이용할 수 있습니다.상향식 병합 정렬에서 시작점은 각 런의 길이가 한 항목이라고 가정합니다.실제로 랜덤 입력 데이터에는 짧은 런이 많이 있으며, 이러한 런이 정렬됩니다.일반적인 경우 병합할 실행이 적기 때문에 자연 병합 정렬에는 패스가 많이 필요하지 않을 수 있습니다.최선의 경우 입력은 이미 정렬되어 있기 때문에(즉, 1회 실행), 자연스러운 병합 정렬은 1회만 데이터를 통과시킬 필요가 있습니다.많은 실제 사례에서 긴 자연적 실행이 존재하며, 이러한 이유로 자연적 병합 정렬이 Timsort의 주요 구성 요소로 이용됩니다.예:
시작 : 3 4 2 1 7 5 8 9 0 6 선택 실행 : (3 4) (2 7) (5 8) (0 6) Merge : (2 3 4) (1 5 7 9) (0 6) Merge : (1 2 3 4 5 7 8 ) (0 6 ) Merge : (1 2 3 4 5 7 8 8 )
형식적으로는 자연 병합 정렬은 Runs-optimal이라고 합니다. ns ( ){ ( ) 、 L( 1 。
토너먼트 대체 선택 정렬은 외부 정렬 알고리즘의 초기 실행을 수집하는 데 사용됩니다.
분석.
n개의 오브젝트를 정렬할 때 Marge sort의 평균 및 최악의 퍼포먼스는 O(n log n)입니다.길이 n의 리스트에 대한 결합 정렬 실행 시간이 T(n)인 경우 알고리즘의 정의에서 반복 관계 T(n) = 2T(n/2) + n이 뒤따릅니다(알고리즘을 원래 목록의 절반 크기의 두 목록에 추가하고, 결과 [5]2개의 목록을 병합하기 위해 수행된 n개의 단계를 추가합니다).닫힌 형식은 분할 및 정복 반복에 대한 마스터 정리로부터 파생됩니다.
최악의 경우 병합 정렬에 의해 수행된 비교 수는 정렬 번호로 지정됩니다.이러한 수치는 (n lg n - n + 1) ~ (n lg n + n + O (lg n)[6] 사이의 (n lg lg n - - 2⌈lg n⌉ + 1)과 같거나 약간 작다.Marge sort의 최적의 경우 최악의 경우보다 [7]약 절반의 반복 시간이 소요됩니다.
큰 n과 무작위로 정렬된 입력 목록의 경우, 병합 정렬의 예상(평균) 비교 횟수는 최악의 경우보다 α·n에 접근합니다. 서 - + 0 0. \alpha =- _{k} {k} {k} {k} {k+} {k} {k} {k} {\frac1} {k} {k}
최악의 경우, 머지 정렬은 평균적인 경우보다 약 39% 적은 비교를 사용하며, 이동의 경우 머지 정렬의 최악의 경우 O(n log n)는 Quicksort의 최선의 [7]경우와 같은 복잡도입니다.
정렬할 데이터가 순차적으로만 효율적으로 액세스할 수 있는 경우 일부 유형의 목록에서는 병합 정렬이 빠른 정렬보다 효율적이며, 따라서 순차적으로 액세스하는 데이터 구조가 매우 일반적인 Lisp와 같은 언어에서 널리 사용됩니다.일부(효율적인) QuickSort 구현과 달리 병합 정렬은 안정적인 정렬입니다.
Merge sort의 가장 일반적인 구현에서는 [8]정렬이 이루어지지 않으므로 정렬된 출력을 저장할 때 입력의 메모리 크기를 할당해야 합니다(n/2개의 추가 공간만 필요한 변형에 대해서는 아래 참조).
변종
머지 정렬의 변형은 주로 공간의 복잡성과 복사 비용을 줄이는 것과 관련이 있습니다.
공간 오버헤드를 n/2로 줄이기 위한 간단한 대안은 왼쪽과 오른쪽을 결합구조로 유지하고 m의 왼쪽 부분만 임시공간에 복사하고 m으로 결합출력을 배치하도록 merge 루틴을 지시하는 것이다.이 버전에서는 하나의 할당만 필요하도록 병합 루틴 외부에 임시 공간을 할당하는 것이 좋습니다.리턴 결과문(위의 의사 코드로 함수가 Marge) 앞의 마지막 행 쌍이 불필요해지기 때문에 앞에서 설명한 과도한 복사도 완화됩니다.
어레이에 실장되어 있는 경우, Marge Sort의 한 가지 단점은 O(n) 작업 메모리 요건입니다.몇 가지 내부 변형이 제안되었습니다.
- Katajainen 등은 일정한 양의 작업 메모리를 필요로 하는 알고리즘을 제시한다.입력 배열의 1개의 요소를 저장하기에 충분한 저장 공간과 입력 배열에 O(1) 포인터를 저장하기 위한 추가 공간이다.작은 상수로 O(n log n) 시간을 제한하지만 알고리즘이 [9]안정적이지 않습니다.
- 표준(하향 또는 하향) 병합 정렬과 조합하여 인플레이스 병합 정렬을 생성할 수 있는 인플레이스 병합 알고리즘을 생성하려고 여러 번 시도했습니다.이 경우 "in-place"라는 개념은 "대수 스택 공간 사용"을 의미하도록 완화될 수 있습니다. 표준 병합 정렬에는 자체 스택 사용을 위한 공간이 필요하기 때문입니다.Geffert 등에서는 일정한 스크래치 공간을 사용하여 O(n log n) 시간 내에 안정적인 병합이 가능하지만 알고리즘이 복잡하고 상수 요인이 높아 길이 n과 m의 병합 배열은 5n + 12m + o(m)[10] 이동이 가능하다는 것을 보여주었다.이 높은 상수 계수와 복잡한 임플레이스 알고리즘은 보다 단순하고 이해하기 쉽게 만들었습니다.빙차오황과 마이클 에이.Langston은[11] 소트된 목록을 추가 공간을 사용하여 병합하기 위한 간단한 선형 시간 알고리즘을 in-place merge를 제공했습니다.그들은 둘 다 크론로드와 다른 사람들의 작품을 사용했다.선형 시간과 일정한 추가 공간에서 병합됩니다.이 알고리즘은 O(n)의 임시 메모리 셀을 자유롭게 이용할 수 있는 표준 머지 정렬 알고리즘보다 평균 시간이 2배 미만 소요됩니다.알고리즘이 실용적으로는 훨씬 빠르지만 리스트에 따라서는 불안정합니다.하지만 비슷한 개념을 사용하여 그들은 이 문제를 해결할 수 있었다.그 외의 임플레이스 알고리즘에는 SymMerge가 있습니다.SymMerge는 O(n + m) 로그(n + m)의 시간이 걸리고 [12]안정적입니다.이러한 알고리즘을 병합 정렬에 연결하면 비선형 광학이지만 여전히 준선형인 O(n(log n)2에 대한 복잡성이 증가합니다.
- 현대적인 안정적인 선형 및 내부 병합은 블록 병합 정렬입니다.
복수의 리스트에의 카피를 줄이는 대체 수단으로서 새로운 정보 필드를 각 키에 관련짓는 것이 있습니다(m 의 요소는 키라고 불립니다).이 필드는 키 및 관련 정보를 정렬된 목록(키 및 관련 정보를 레코드라고 함)으로 연결하기 위해 사용됩니다.그런 다음 정렬된 목록의 병합은 링크 값을 변경하여 진행됩니다. 레코드를 이동할 필요가 없습니다.링크만 포함된 필드는 일반적으로 레코드 전체보다 작기 때문에 사용되는 공간도 줄어듭니다.이것은 표준 정렬 기법이며 병합 정렬로 제한되지 않습니다.
테이프 드라이브와 함께 사용
외부 머지 정렬은 정렬할 데이터가 너무 커서 메모리에 들어가지 않을 때 디스크 또는 테이프 드라이브를 사용하여 실행할 수 있습니다.외부 정렬은 디스크 드라이브에서 병합 정렬을 구현하는 방법을 설명합니다.일반적인 테이프 드라이브 정렬에는 4개의 테이프 드라이브가 사용됩니다.모든 I/O는 순차적입니다(각 패스의 끝에 있는 되감기는 제외).최소한의 구현으로도 2개의 레코드 버퍼와 몇 개의 프로그램 변수만으로 그럭저럭 버틸 수 있습니다.
4개의 테이프 드라이브의 이름을 A, B, C, D로 지정하고 두 개의 레코드 버퍼만 사용하는 알고리즘은 메모리에 있는 어레이 대신 테이프 드라이브 쌍을 사용하는 상향식 구현과 유사합니다.기본 알고리즘은 다음과 같이 설명할 수 있습니다.
- A에서 레코드 쌍을 병합하고 2개의 레코드 하위 목록을 번갈아 C와 D로 작성합니다.
- C와 D의 2개의 레코드 서브리스트를 4개의 레코드 서브리스트로 병합합니다.이것들을 A와 B에 번갈아 씁니다.
- A와 B의 4개의 레코드 서브리스트를 8개의 레코드 서브리스트로 병합합니다.이것들을 C와 D에 번갈아 씁니다.
- log(n) pass로 정렬된2 모든 데이터를 포함하는 하나의 목록이 될 때까지 반복합니다.
매우 짧은 실행으로 시작하는 대신 일반적으로 하이브리드 알고리즘이 사용됩니다. 여기서 초기 패스는 많은 레코드를 메모리에 읽어들이고 내부 정렬을 수행하여 장기 실행을 만든 다음 출력 세트에 이러한 장기 실행을 배포합니다.이 스텝은 많은 얼리 패스를 회피합니다.예를 들어, 내부 종류의 1024 레코드는 9개의 패스를 저장합니다.내부 분류는 종종 큰 편익을 가지고 있기 때문에 크다.실제로 초기 실행을 사용 가능한 내장 메모리보다 길게 할 수 있는 기술이 있습니다.그 중 하나인 Knuth의 '스노우'(이진수 Min-heap 기준)는 [13]사용된 메모리 크기보다 2배(평균) 긴 런을 생성합니다.
약간의 오버헤드를 수반해, 상기의 알고리즘을 3개의 테이프를 사용하도록 변경할 수 있습니다.O(n log n) 실행 시간은 2개의 큐 또는1개의 스택과 큐 또는3개의 스택을 사용하여 달성할 수도 있습니다.한편, k > 2 개의 테이프(및 메모리내의 O(k) 아이템)를 사용하면, k/2 웨이 머지를 사용하는 것으로, O(log k) 회수의 테이프 조작수를 줄일 수 있습니다.
테이프(및 디스크) 드라이브 사용을 최적화하는 보다 정교한 병합 정렬은 다상 병합 정렬입니다.
병합 정렬 최적화
최신 컴퓨터에서는 다단계 메모리 계층이 사용되기 때문에 소프트웨어 최적화에 있어 참조 인접성이 가장 중요할 수 있습니다.머신의 메모리 캐시에서 페이지 이동을 최소화하기 위해 특별히 선택된 머지 정렬 알고리즘의 캐시 인식 버전이 제안되었습니다.예를 들어 Tiled Merge Sort 알고리즘은 크기 S의 서브어레이에 도달하면 서브어레이 분할을 정지한다.여기서 S는 CPU의 캐시에 들어가는 데이터 항목의 수이다.이들 서브어레이 각각은 삽입 정렬 등의 인플레이스 정렬 알고리즘에 의해 정렬되어 메모리 스왑을 억제하고 표준 재귀적인 방법으로 일반 머지 정렬이 완료된다.이 알고리즘에 의해 캐시 최적화의 이점을 얻을 수 있는 머신에서의 퍼포먼스가[example needed] 향상되었습니다.(LaMarca & Ladner 1997)
Kronrod(1969)는 일정한 추가 공간을 사용하는 병합 정렬의 대체 버전을 제안했다.이 알고리즘은 나중에 개량되었다.(Katajainen, Pasanen, Teuhola 1996)
또, 외부 정렬의 많은 애플리케이션은, 입력이 보다 많은 수의 서브 리스트로 분할되는, 이상적으로는 그것들을 합치면, 현재 처리되고 있는 페이지 세트가 메인 메모리에 들어가도록 하는, 머지 정렬의 형태를 사용합니다.
병렬 병합 정렬
병합 정렬은 분할 및 정복 방식을 사용하여 잘 병렬화됩니다.알고리즘의 몇 가지 다른 병렬 변형이 수년간 개발되어 왔다.일부 병렬 병합 정렬 알고리즘은 순차적 하향식 병합 알고리즘과 강한 관련이 있는 반면, 다른 일반 구조를 가지고 K-way 병합 방법을 사용합니다.
병렬 재귀와 정렬 병합
순차적 병합 정렬 절차는 분할 단계와 병합 단계의 두 단계로 설명할 수 있습니다.첫 번째 콜은 여러 개의 재귀 콜로 구성되어 있습니다.이 콜은 같은 분할 프로세스를 반복하여 수행합니다.이 콜은 후속 콜이 개별적으로 정렬될 때까지(하나의 요소가 포함되거나 요소가 포함되지 않습니다.직관적인 접근법은 이러한 재귀 [14]콜을 병렬화하는 것입니다.다음 의사 코드는 fork 키워드 및 join 키워드를 사용하여 병렬 재귀에 의한 머지 정렬을 설명합니다.
// 배열 A의 mergesort(A, lo, hi)에서 hi(hi)까지의 소트 요소 lo. 알고리즘은 lo+1 < hi이면 // 두 개 이상의 요소. mid : = ( ( lo + hi) // fork mergesort(A, lo, mid) mergesort(A, mid, mid, hi) 결합(A, merge)
이 알고리즘은 시퀀셜버전의 간단한 변경으로 잘 병렬화되지 않습니다.따라서 속도 향상은 그다지 인상적이지 않다.of ()\ \ )스팬은 순차 버전보다 n) \ \ ( \ n )의 개량일 뿐입니다(알고리즘의 개요 참조).이는 병렬 실행의 병목현상이기 때문에 순차적 병합 방식에 주로 기인합니다.
병렬 병합과 정렬 병합
병렬 병합 알고리즘을 사용하면 더 나은 병렬 처리를 달성할 수 있습니다.Cormen 등은 2개의 정렬된 서브시퀀스를 하나의 정렬된 [14]출력시퀀스로 병합하는 바이너리 변형을 제시한다.
시퀀스 중 하나(길이가 다른 경우 긴 시퀀스)에서 중간 인덱스의 요소가 선택됩니다.다른 시퀀스에서의 위치는 이 요소가 이 위치에 삽입된 경우 이 시퀀스가 정렬된 상태로 유지되도록 결정됩니다.따라서 두 시퀀스의 다른 요소가 얼마나 더 작고 출력 시퀀스에서 선택한 요소의 위치를 계산할 수 있는지 알 수 있습니다.이렇게 생성된 더 작고 큰 요소의 부분 시퀀스에 대해 재귀의 베이스 케이스에 도달할 때까지 병합 알고리즘이 다시 병렬로 실행된다.
다음 의사코드는 병렬마지 알고리즘(Cormen 등)을 사용하여 변경된 병렬마지 정렬 방식을 사용합니다.
/** * A: 입력 배열 * B: 출력 배열 * lo: 하한 * hi: 상한 * off: off: 오프셋 */ 알고리즘 parallelMergesort(A, lo, hi, B, off)는 len : == lo + 1이면 B[off] : A[lo] elther T[1]입니다.len]이 새 배열 중간이 됩니다. = ( ( lo + hi ) / 2 mid' mid ' : = mid - lo + 1 fork parallel Mergesort ( A , lo , mid , T , hi , mid ' + 1 ) parallel Mergesort ( A , mergesort ( A , mid + 1, hi , mid , mid , mid , mid , mid , 1 ) 、 men ) 、 marallel ) 。
최악의 경우 범위의 반복 관계를 분석하려면 parallel Mergesort의 재귀 호출은 병렬 실행으로 인해 한 번만 통합해야 합니다.
병렬 병합 절차의 복잡성에 대한 자세한 내용은 병합 알고리즘을 참조하십시오.
이 재발의 해결 방법은 다음과 같습니다.
이 병렬병합 알고리즘은 ( logn )( \ \ \ left ( { \ { n} { ( \ n )^{ \right )의 병렬에 도달합니다이것은 이전 알고리즘의 병렬보다 훨씬 높은 수치입니다.이러한 정렬은 삽입 정렬과 같은 빠르고 안정적인 시퀀셜 정렬과 [15]소형 어레이를 병합하는 기본 케이스로서의 고속 시퀀셜 병합과 결합하면 실제로 잘 수행될 수 있습니다.
병렬 다중 방향 병합 정렬
일반적으로 사용 가능한 프로세서는 p>2이므로 머지 정렬 알고리즘을 바이너리 머지 방식으로 제한하는 것은 임의인 것 같습니다.더 나은 접근법은 k\ k개의 정렬된 시퀀스를 하는 바이너리 병합의 일반화인 K-way 병합 방법을 사용하는 것입니다.이 Marge Variant는 [16][17]PRAM에서의 정렬 알고리즘을 기술하는 데 매우 적합합니다.
기본 개념
n개의가 정렬되지 않은 경우 사용 가능한 프로세서를 하여 시퀀스를 정렬하는 것이 목표입니다이러한 요소는 모든 프로세서에 균등하게 분포되며 순차 정렬 알고리즘을 사용하여 로컬로 정렬됩니다.따라서 시퀀스는 길이 p { \\{ } \ 의 정렬된 1, 로 구성됩니다. 단순화를 위해 n n은 p의 라고 하면 p 는 과 같습니다. ,., \ i , ... , ..., 의 } \ right \ = frac { } { } 。
이 시퀀스는 멀티시퀀스 선택/스플리터 선택을 수행하는 데 사용됩니다. ,.. ,p { \ j =, . } , { \ _ { } rank determ determ determ k k k p { k { { p}. globalterterterterterterterterterterterterterterterterterterterter splitter v_pv v v v v v v v v vj p v v v v v v v v v v v v v v v vAystyle S_{나는}}2진 검색이고 따라서 S나는{\displaystyle S_{나는}}와 더 p로 분할된다{p\displaystyle}결정된다 subsequences S나는, 1,..., S나는,{\displaystyle S_{i,1},...,S_{i,p}}과 Sij:={)∈ S나이었고 넌 결코 모르네 오빠 k(vj− 1)<>r nk())≤ r오빠 k(v j (v_)\j
또한 i i, {1의 는 {\ i에 할당됩니다.이것은랭크 {\ {와 i} 사이의 모든 요소를 의미합니다.따라서 각 프로세서는 정렬된 시퀀스를 수신합니다.스플리터 의 순위k(\ k가 글로벌하게 선택되었다는 사실은 다음 두 가지 중요한 속성을 제공합니다.한편 각 프로세서가 할당 후에도 n\ n에서 동작할 수 있도록k\k가 되었습니다.알고리즘은 완벽하게 부하 분산되어 있습니다.한편, 의 모든 요소는 i +의 모든 요소보다 작거나 따라서 각 프로세서는 로컬에서 p-way 마지를 실행하고, 그 서브시퀀스로부터 정렬된 시퀀스를 얻는다.두 번째 속성으로 인해 더 이상의 p-way-merge를 수행할 필요가 없습니다.프로세서 번호의 순서로 결과를 정리하기만 하면 됩니다.
다중 시퀀스 선택
가장 간단한 형태에서는 p p프로세서와 k k에 균등하게 분포된 1, p {\}, 를 하면 t {\ k에서 글로벌의 x {\x}를 찾는 것이 과제입니다.배열의 결합입니다.따라서 각 S_})를 splitter 스타일 })로 두 부분으로 분할할 수 있습니다.여기서 하단에는 xx보다 작은 요소만 포함되고 상단에는 xx)보다 큰 요소가 배치됩니다.
제시된 시퀀셜 알고리즘은 각 시퀀스의 분할 인덱스를 반환합니다. 예를 들어 Si의 li})은}[의 글로벌 랭크가 k 및 보다 작습니다.+ k\ style \ } \ ( S { } [ _ { } + ] \ )\ k[18]}
를 나는 1이 할 정도 있었는데 나는:l_i<>을 위해 존재하는 것 나는 1이 항목을 만약 m_1 m_i 순차적으로 binarySearch(vS_i[l_i, r_i])을 끓여= 하원 알고리즘 msSelect(S:정렬된 Sequences[S_1,..,S_p]의 정열은, k:int)(l_i, r_i)(0, S_i))=, r_i // 피벗 요소 S_j[l_j]를 걷어들이다.., S_j[r_j], 무작위 j한결같이 v:=pickPivot(S, l, r)를 골랐다는 점.+... + m_p > = k 다음 // m_1+...+ m_p는 v r : = m // 벡터 할당의 글로벌 순위입니다.그렇지 않으면 l : = m return l
복잡도 분석의 경우 PRAM 모델이 선택됩니다.데이터가 모든 p에 균등하게 분포되어 있는 경우 binarySearch 메서드의 p폴드 실행 시간은 log ( /) \ { O \ ( p \log ( n / p \ right ) 。예상되는 재귀 깊이는 O ( i) ( )\ \ { ( \ \ ( \ \ _ { } i } \ right ) = mathcal { O} ()입니다.따라서 전체 예상 실행 시간은 ( log" ( / ) log"( { left \ ( / )\ ( )\ right 입니다.
병렬 멀티웨이 병합 정렬에 적용되는 이 알고리즘은 i ,. ,p {\ i, 에 순위 p {\의 모든 스플리터 요소가 동시에 검색되도록 병렬로 호출해야 합니다.이러한 스플리터 요소를 사용하여 각 시퀀스를 pp 로 분할할 수 있습니다. ( / ) log ()\ \ { }\ ( , \ / p ) \ ( ) \ right。
유사 코드
다음으로 병렬 멀티웨이 머지 정렬 알고리즘의 완전한 의사 코드를 나타냅니다.모든 프로세서가 분할 요소 및 시퀀스 파티션을 올바르게 결정할 수 있도록 멀티시퀀스 선택 전후에 장벽 동기화가 있다고 가정합니다.
/** * d: 정렬되지 않은 요소 배열 * n: 요소 수 * p: 프로세서 수 * 정렬된 배열 */ 알고리즘 parallelMultiwayMergesort(d: Array, n: int, p: int)는 o:= 새 배열[0, n] // 각 프로세서에서 병렬로 p를 수행하는 데 필요한 출력 배열입니다. //=i-1) * n/p, i * n/p] // 길이 n/p 정렬(S_i) // 로컬로 정렬 v_i := msSelect([S_1, ...S_p], i * n/p) // 요소가 글로벌 순위 i * n/p 동기화(S_i, ..., S_p) 시퀀스:n/p] := kWayMerge(s_1,i, ..., s_p,i) // 병합하여 출력 배열 반환에 할당
분석.
우선 각 프로세서는 O log ( /style {O의 정렬 알고리즘을 사용하여 n 요소를 로컬로 정렬합니다.그 후 스플리터 요소는O log 로 계산해야 합니다 ){ { } \ ( , \ ( / )\ ()\ right마지막으로 p p 의 각 그룹은 각 프로세서에 의해 이O ( log ( ) n / { } ( p / p/ n ) ( / n / )\ right )\ display styledisplay styleft ;따라서 전체 실행 시간은 다음과 같습니다.
( p ( ) + ( ) + (p )\ {{} \ left ( { \ {} { } } \ \ lef ( { \ {} { p} \ ) +\ \ left ( { \ } } } { p } } { } { p } { right } }
실용적 어댑테이션 및 응용
멀티웨이 머지 정렬 알고리즘은 다수의 프로세서를 사용할 수 있는 높은 병렬화 기능을 통해 매우 확장성이 있습니다.따라서 알고리즘은 컴퓨터 클러스터에서 처리되는 것과 같은 대량의 데이터를 정렬할 수 있습니다.또, 이러한 시스템에서는, 메모리는 통상, 제한 자원이 아니기 때문에, 머지 정렬의 공간 복잡성의 단점은 무시할 수 있다.그러나 이러한 시스템에서는 PRAM을 모델링할 때 고려되지 않는 다른 요인이 중요해진다.여기서는 다음 측면을 고려해야 합니다.데이터가 프로세서 캐시에 들어가지 않는 메모리 계층 또는 프로세서 간에 데이터를 교환하는 통신 오버헤드가 공유 메모리를 통해 데이터에 더 이상 액세스할 수 없게 되면 병목 현상이 발생할 수 있습니다.
샌더스 등님은 이 논문에서 멀티레벨 멀티웨이 머지소트의 벌크 동기 병렬 알고리즘을 제시했습니다.이 알고리즘은p\ p p의 r r으로 .모든 프로세서는 로컬로 정렬됩니다.싱글 레벨 멀티웨이 머지소트와는 달리 이들 시퀀스는r\r 으로 분할되어 적절한 프로세서 그룹에 할당됩니다.이러한 단계는 이러한 그룹에서 반복적으로 반복됩니다.이것에 의해, 통신이 삭감되어, 특히 많은 작은 메세지에 관한 문제를 회피할 수 있습니다.기반이 되는 실제 네트워크의 계층 구조를 사용하여 프로세서 그룹(랙, 클러스터 [17]등)을 정의할 수 있습니다.
기타 변종
Marge Sort는 최적의 속도 향상을 실현한 최초의 정렬 알고리즘 중 하나이며, Richard Cole은 O(1)의 [19]Marge를 확실하게 하기 위해 영리한 서브샘플링 알고리즘을 사용했습니다.다른 정교한 병렬 정렬 알고리즘은 더 낮은 상수로 같거나 더 나은 시간 범위를 달성할 수 있습니다.예를 들어 1991년에 David Powers는 파티셔닝을 [20]암묵적으로 실행함으로써 n개의 프로세서를 탑재한 CRCW 병렬랜덤액세스 머신(PRAM) 상에서 O(log n) 타임으로 동작할 수 있는 병렬화된 퀵소트(및 관련 기수 정렬)에 대해 설명했습니다.또한 Powers는 버터플라이 정렬 네트워크상의 O(log n)2 시간에 Batcher의 Bitonic Mergesort 파이프라인 버전이 실제로 PRAM 상의 O(log n) 정렬보다 빠르다는 것을 보여주고 비교, 기수 및 병렬 [21]정렬에서 숨겨진 오버헤드에 대해 자세히 설명합니다.
다른 정렬 알고리즘과의 비교
힙소트는 병합 정렬과 동일한 시간 범위를 가지지만 병합 정렬의 '(n) 대신 θ(1) 보조 공간만 필요합니다.일반적인 최신 아키텍처에서는 효율적인 QuickSort 구현이 일반적으로 RAM [citation needed]기반 어레이를 정렬하기 위한 병합 정렬보다 성능이 우수합니다.한편, 머지 정렬은 안정된 정렬이며 접근이 느린 시퀀셜 미디어를 처리하는 데 더 효율적입니다.링크 리스트의 정렬에는 머지 정렬이 최적입니다.이 상황에서는 여분의 공간밖에 필요로 하지 않는 방법으로 머지 정렬을 실장하는 것이 비교적 용이하며 링크 리스트의 랜덤액세스 퍼포먼스가 느리기 때문에 다른 알고리즘(퀵소트 등)의 퍼포먼스가 저하되고 다른 알고리즘(히프소트 등)의 퍼포먼스가 완전히 저하됩니다.sible.
Perl 5.8에서는 Marge sort가 기본 정렬 알고리즘입니다(이전 버전의 [22]Perl에서는 빠른 정렬이었습니다).Java에서는 Arrays.sort() 메서드는 데이터 유형에 따라 결합 정렬 또는 조정된 빠른 정렬을 사용하며 7개 미만의 배열 요소가 [23]정렬될 때 구현 효율성 스위치를 사용하여 삽입 정렬합니다.Linux 커널은 링크된 [24]목록에 병합 정렬을 사용합니다.Python은 Java SE 7, Android 플랫폼 [25][26]및 GNU [27]옥타브에서 표준 정렬 알고리즘이 된 병합 정렬과 삽입 정렬의 또 다른 튜닝된 하이브리드인 Timsort를 사용합니다.
메모들
- ^ Skiena (2008, 페이지 122)
- ^ Knuth (1998, 페이지 158
- ^ Katajainen, Jyrki; Träff, Jesper Larsson (March 1997). "Algorithms and Complexity". Proceedings of the 3rd Italian Conference on Algorithms and Complexity. Italian Conference on Algorithms and Complexity. Lecture Notes in Computer Science. Vol. 1203. Rome. pp. 217–228. CiteSeerX 10.1.1.86.3154. doi:10.1007/3-540-62592-5_74. ISBN 978-3-540-62592-6.
- ^ Powers, David M. W.; McMahon, Graham B. (1983). "A compendium of interesting prolog programs". DCS Technical Report 8313 (Report). Department of Computer Science, University of New South Wales.
- ^ 코멘 등 (2009, 페이지 36) :
- ^ 여기에 제시된 최악의 경우 숫자는 Knuth의 컴퓨터 프로그래밍 기술 제3권에 제시된 수치와 일치하지 않습니다.이 불일치는 Knuth가 약간 차선인 병합 정렬의 변형 구현을 분석하기 때문입니다.
- ^ a b Jayalakshmi, N. (2007). Data structure using C++. ISBN 978-81-318-0020-1. OCLC 849900742.
- ^ 코멘 등 (2009, 페이지 151) :
- ^ 카타자이넨, 파사넨 & 테우홀라(1996년)
- ^ Geffert, Viliam; Katajainen, Jyrki; Pasanen, Tomi (2000). "Asymptotically efficient in-place merging". Theoretical Computer Science. 237 (1–2): 159–181. doi:10.1016/S0304-3975(98)00162-5.
- ^ Huang, Bing-Chao; Langston, Michael A. (March 1988). "Practical In-Place Merging". Communications of the ACM. 31 (3): 348–352. doi:10.1145/42392.42403. S2CID 4841909.
- ^ Kim, Pok-Son; Kutzner, Arne (2004). "Stable Minimum Storage Merging by Symmetric Comparisons". Algorithms – ESA 2004. European Symp. Algorithms. Lecture Notes in Computer Science. Vol. 3221. pp. 714–723. CiteSeerX 10.1.1.102.4612. doi:10.1007/978-3-540-30140-0_63. ISBN 978-3-540-23025-0.
- ^ Ferragina, Paolo (2009–2019), "5. Sorting Atomic Items" (PDF), The magic of Algorithms!, p. 5-4, archived (PDF) from the original on 2021-05-12
- ^ a b 코멘 등 (2009, 페이지 797–805) :
- ^ Victor J. Duvanko "Parallel Merge Sort" Dobb 박사의 저널&블로그 [1] 및 GitHub repo C++ 구현 [2]
- ^ Peter Sanders; Johannes Singler (2008). "Lecture Parallel algorithms" (PDF). Retrieved 2020-05-02.
- ^ a b Axtmann, Michael; Bingmann, Timo; Sanders, Peter; Schulz, Christian (2015). "Practical Massively Parallel Sorting". Proceedings of the 27th ACM Symposium on Parallelism in Algorithms and Architectures: 13–23. doi:10.1145/2755573.2755595. ISBN 9781450335881. S2CID 18249978.
- ^ Peter Sanders (2019). "Lecture Parallel algorithms" (PDF). Retrieved 2020-05-02.
- ^ Cole, Richard (August 1988). "Parallel merge sort". SIAM J. Comput. 17 (4): 770–785. CiteSeerX 10.1.1.464.7118. doi:10.1137/0217049. S2CID 2416667.
- ^ Powers, David M. W. (1991). "Parallelized Quicksort and Radixsort with Optimal Speedup". Proceedings of International Conference on Parallel Computing Technologies, Novosibirsk. Archived from the original on 2007-05-25.
- ^ Powers, David M. W. (January 1995). Parallel Unification: Practical Complexity (PDF). Australasian Computer Architecture Workshop Flinders University.
- ^ "Sort – Perl 5 version 8.8 documentation". Retrieved 2020-08-23.
- ^ coleenp (22 Feb 2019). "src/java.base/share/classes/java/util/Arrays.java @ 53904:9c3fe09f69bc". OpenJDK.
- ^ Linux 커널 /lib/list_list.c
- ^ jjb (29 Jul 2009). "Commit 6804124: Replace "modified mergesort" in java.util.Arrays.sort with timsort". Java Development Kit 7 Hg repo. Archived from the original on 2018-01-26. Retrieved 24 Feb 2011.
- ^ "Class: java.util.TimSort<T>". Android JDK Documentation. Archived from the original on January 20, 2015. Retrieved 19 Jan 2015.
- ^ "liboctave/util/oct-sort.cc". Mercurial repository of Octave source code. Lines 23-25 of the initial comment block. Retrieved 18 Feb 2013.
Code stolen in large part from Python's, listobject.c, which itself had no license header. However, thanks to Tim Peters for the parts of the code I ripped-off.
레퍼런스
- Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford (2009) [1990]. Introduction to Algorithms (3rd ed.). MIT Press and McGraw-Hill. ISBN 0-262-03384-4.
- 실용적인 임플레이스 머지소트Katajainen, Jyrki; Pasanen, Tomi; Teuhola, Jukka (1996). "Practical in-place mergesort". Nordic Journal of Computing. 3 (1): 27–40. CiteSeerX 10.1.1.22.8523. ISSN 1236-6064. Archived from the original on 2011-08-07. Retrieved 2009-04-04..또 [3]
- Knuth, Donald (1998). "Section 5.2.4: Sorting by Merging". Sorting and Searching. The Art of Computer Programming. Vol. 3 (2nd ed.). Addison-Wesley. pp. 158–168. ISBN 0-201-89685-0.
- Kronrod, M. A. (1969). "Optimal ordering algorithm without operational field". Soviet Mathematics - Doklady. 10: 744.
- LaMarca, A.; Ladner, R. E. (1997). "The influence of caches on the performance of sorting". Proc. 8th Ann. ACM-SIAM Symp. On Discrete Algorithms (SODA97): 370–379. CiteSeerX 10.1.1.31.1153.
- Skiena, Steven S. (2008). "4.5: Mergesort: Sorting by Divide-and-Conquer". The Algorithm Design Manual (2nd ed.). Springer. pp. 120–125. ISBN 978-1-84800-069-8.
- Sun Microsystems. "Arrays API (Java SE 6)". Retrieved 2007-11-19.
- Oracle Corp. "Arrays (Java SE 10 & JDK 10)". Retrieved 2018-07-23.