320 likes | 1.08k Views
6. 합병 정렬. 합병 정렬 (Merge Sorting) 각 단계별로 두 그룹의 정렬된 데이터를 서로 합쳐서 정렬된 더 큰 그룹으로 만들어 나아가는 정렬. 6. 합병 정렬. 합병 정렬 (Merge Sorting). 두 그룹을 합친 새로운 그룹을 위한 임시 저장공간이 필요함 각 그룹당 포인터 한 개씩 필요 각 포인터가 가리키는 데이터 두 개를 비교하여 더 작은 것에 대해 임시 저장공간에 복사 복사한 것에 대해서는 포인터를 다음으로 움직임 두 그룹 중 하나는 먼저 처리가 완료되어 포인터가 밖으로 나감
E N D
6. 합병 정렬 • 합병 정렬 (Merge Sorting) • 각 단계별로 두 그룹의 정렬된 데이터를 서로 합쳐서 정렬된 더 큰 그룹으로 만들어 나아가는 정렬 Data Structure
6. 합병 정렬 • 합병 정렬 (Merge Sorting) • 두 그룹을 합친 새로운 그룹을 위한 임시 저장공간이 필요함 • 각 그룹당 포인터 한 개씩 필요 • 각 포인터가 가리키는 데이터 두 개를 비교하여 더 작은 것에 대해 임시 저장공간에 복사 • 복사한 것에 대해서는 포인터를 다음으로 움직임 • 두 그룹 중 하나는 먼저 처리가 완료되어 포인터가 밖으로 나감 • 남은 그룹의 데이터를 순서대로 임시 저장공간으로 그대로 복사 • 위와 같은 작업이 끝나면 임시 저장공간에 있는 데이터를 원래의 배열에 다시 옮긴다. Data Structure
6. 합병 정렬 • 합병 정렬 (Merge Sorting) 배열 A int Temp[MAX_TEMP_DATA_SIZE_FOR_MERGE]; void Merge_Sub (int A[ ], int F, int Mid, int L) { int First1 = F; int Last1 = Mid; int First2 = Mid + 1; int Last2 = L; int Index = First1; for (; (First1 <= Last1) && (First2 <= Last2); ++Index) { if (A[First1] < A[First2]) { Temp[Index] = A[First1]; ++First1; } else { Temp[Index] = A[First2]; ++First2; } } for (; First1 <= Last1; ++First1, ++Index) Temp[Index] = A[First1]; for (; First2 <= Last2; ++First2, ++Index) Temp[Index] = A[First2]; for (Index = F; Index <= L; ++Index) A[Index] = Temp[Index]; } First1 Last1 First2 Last2 Data Structure
6. 합병 정렬 • 합병 정렬 (Merge Sorting) • 재귀호출을 이용 • 원 데이터를 반 잘라서 왼쪽재귀 호출, 오른쪽 재귀 호출. 그 두 결과를 합병 • 베이스 케이스로부터 빠져 나와 호출 함수로 되돌아오는 과정에서Merge(A, First, Middle, Last)를 수행. 즉, 합병에 의해 정렬 배열 A void MergeR(int A[ ], int First, int Last) { if (First < Last) { int Middle = (First + Last) / 2; MergeR(A, First, Middle); MergeR(A, Middle+1, Last); Merge_Sub(A, First, Middle, Last); //Merge 작업 – 실제 정렬이 수행됨 } } void Merge(int A[ ], int N) { MergeR(A, 0, N-1); } First Middle Middle+1 Last N-1 0 Data Structure
6. 합병 정렬 • 합병 정렬 (Merge Sorting) Data Structure
6. 합병 정렬 • 합병 정렬 (Merge Sorting) • 복잡도: 수학적 증명에 의하여 O(NlnN)로 밝혀짐 • 호출의 단계수가 lnN • 각 단계별로 합병을 하는 연산의 총합 복잡도 O(N) • 합병정렬은 안정정렬 • 합병과정에서 임시 저장공간에 데이터를 복사할 때, 원래 순서를 유지하면서 복사함 Data Structure
6. 합병 정렬 • 삽입 정렬 (Insertion Sorting) vs. 합병 정렬 (Merge Sorting) • O(N2) vs. O(NlnN) • 삽입 정렬 • 합병 정렬 Data Structure
7. 퀵 정렬 • 퀵 정렬 (Quick Sorting) • 1960년 C.A.R. Hoare 에 의하여 개발된 알고리즘 • 재귀 호출의 순서에 유의 • 실제 정렬 작업이 어디서 일어나는지 유의 • 퀵 정렬은 실제 정렬작업이 재귀 함수의 처음에 Partition 작업에 의하여 수행된다. • 합병 정렬 – Divide and Conquer • 쾌속 정렬 – Conquer and Divide Data Structure
7. 퀵 정렬 • 퀵 정렬 (Quick Sorting) high low int Quick_Sub(int A[ ], int First, int Last) { int low, high, p; p = A[Last]; low = First; high = Last-1; while (low < high) { while (p > A[low]) low++; while (p < A[high]) high--; if (low < high) { int Temp = A[low]; A[low] = A[high]; A[high] = Temp; } } int Temp = A[low]; A[low] = A[Last]; A[Last] = Temp; return low; } Data Structure
7. 퀵 정렬 • 퀵 정렬 (Quick Sorting) • Quick_Sub는 파티션 함수 • 피벗을 중심으로 그보다 작은 것은 왼쪽, 그보다 큰 것은 오른쪽으로 몰아넣음 • 피벗이 그 사이에 끼어 들어감 • 피벗의 위치를 돌려줌 • 이후 피벗의 왼쪽, 오른쪽 데이터가 각각 정렬되더라도 피벗의 위치는 불변, 즉 최종정렬까지 그 위치가 고정됨 void QuickR(int A[ ], int First, int Last) { if (First < Last) { int PivotIndex= Quick_Sub(A, First, Last); //Partition 작업 – 실제 정렬이 수행됨 QuickR(A, First, PivotIndex-1); QuickR(A, PivotIndex+1, Last); } } void Quick(int A[ ], int N) { QuickR(A, 0, N-1); } Data Structure
7. 퀵 정렬 • 퀵 정렬 (Quick Sorting) Data Structure
7. 퀵 정렬 • 퀵 정렬 (Quick Sorting) • 복잡도: 수학적 증명에 의하여 O(NlnN)로 밝혀짐 • 재귀 호출 단계 수는 일반적으로 lnN • 각 단계별로 비교와 스왑을 하는 연산의 총합 복잡도 O(N) • 일반 퀵 정렬 의 단점 • 피벗을 중심으로 데이터가 정확하게 반으로 갈린다는 보장이 없음 • 파티션의 결과 두 그룹이 균형(Balanced)이 잡히는 것은 퀵 정렬 성능에 중요한 영향을 미침 • 만약 균형이 잘 안 잡히면 단계의 수가 lnN 보다 많아짐 • 그래서, 거의 정렬이 되어진 데이터 집합에 대하여 퀵 정렬은 최악의 성능을 보임 완벽하게 정렬된 데이터 집합에 대하여퀵정렬의 복잡도는 O(N2) Data Structure
7. 퀵 정렬 • 퀵 정렬 (Quick Sorting) 향상법 • 향상된 파티션 방법 (더 나은 균형을 위하여) • Case I: Median-of-Three 파티션 • 임의로 세 개의 데이터 값을 골라서 그 중 중간 값에 해당하는 것을 키로 정함 • Case II: Sample 정렬 • 데이터 배열에서 임의로 많은 샘플을 끄집어 내어 일단 정렬한 후, 그 중에 중간 값을 피벗으로 사용 • Case III: Bentley-Mcllroy 방식 • 한 번에 세 개의 데이터 값을 고르고, 이를 세 번 반복하여 메디안 값에 해당하는 것인 세 개의 값 중의 메디안 값을 피벗으로 사용 • 작은 사이즈의 파티션 처리 방법 • 파티션 데이터가 열 개 미만이면 더 이상 파티션 하여 재귀호출 부르지 않고 바로 삽입 정렬들을 불러서 정렬을 끝냄 Data Structure
7. 퀵 정렬 • 합병 정렬 (Merge Sort) vs. 퀵 정렬 (Quick Sorting) • 최악의 경우 비교 • 합병 정렬은 매 단계 마다 완벽한 균형 잡힌 분할을 함 • 합병 정렬은 최악의 경우에도 O(NlgN)을 보장 • 퀵 정렬은최악의경우O(N2) • 추가 메모리 필요 여부 • 퀵 정렬은임시저장공간이불필요한제자리연산(In-Place Computation) • 합병정렬은임시저장공간에옮겨가고옮겨오는시간이필요 • 평균적(일반적)으로는 퀵 정렬이 더 빠름 • 합병 정렬 • 퀵 정렬 Data Structure
7. 퀵 정렬 • 삽입 정렬 (Insertion Sort) vs. 퀵 정렬 (Quick Sorting) vs. 합병 정렬 (Merge Sort) Data Structure
8. 외부 정렬 • 외부 정렬 • 한꺼번에 모든 데이터를 메모리에 올리지 못할 때 수행. • Problem: sort 1Gb of data with 1Mb of RAM!!! • 기본적으로 합병 정렬 방식이 사용됨 • 초기 수행 방법 • 메인 메모리의 크기가 Maximum 3 개의 Record만 수용할 수 있다고 가정 • 입력 파일(하드디스크) • D A T A S T R U C T U R E A N D A L G O R I T H M • 초기 단계 • 3개의 Record 단위로 메인 메모리로 읽어 들임 • 읽어 드린 3개의 Record를 메모리에서 정렬 • 다시 하드 디스크의 파일로 저장 • 이 때 여러 파일로 나누어서 저장 (*은 블록 나눔을 의미): 파일 1: ADT * RTU * GOR * 파일 2: AST * AEN * HIT * 파일 3: CRU * ADL * M * 파일 1: ADT * CRU * AEN * GOR * M 파일 2: AST * RTU * ADL * HIT * Data Structure
8. 외부 정렬 • 3-WAY Merge Example (3개 단위의 합병) • 초기 단계 • 파일 1: ADT * RTU * GOR * • 파일 2: AST * AEN * HIT * • 파일 3: CRU * ADL * M * • Procedure • 개념적 포인터 세 개를 각 파일의 첫 번째 Record에 위치시킴 • 포인터가 가리키는 세 개의 Record를 메모리로 올림 • 비교 결과 가장 작은 것을 파일 4에 저장하고 포인터를 움직임 • 해당 포인터에 있는 Record를 다시 읽어 들임 • 다시 세 개의 Record를 비교하여 가장 작은 것을 파일 4에 저장 • 결과적으로 파일 4에는 기존 파일 1,2,3의 첫 블록들이 정렬되어 기록 • 2단계 • 파일 4: AACDRSTTU * • 파일 5: AADELNRTU * • 파일 6: GHIMORT * • 3단계 • 파일 1: AAAACDDEGHILMNORRSTTTTUU * 반복 Data Structure
8. 외부 정렬 • 2-WAY Merge Example (2개 단위의 합병) • 초기 단계 • 파일 1: ADT * CRU * AEN * GOR * M • 파일 2: AST * RTU * ADL * HIT * • Procedure • 2단계 • 파일 3: AADSTT * AADELN * M • 파일 4: CRRTUU * GHIORT * • 3단계 • 파일 1: AACDRRSTTTUU * M • 파일 2: AADEGHILNORT * • 4단계 • 파일 3: AAAACDDEGHILNORRSTTTTUU * • 파일 4: M • 5단계 • 파일 1: AAAACDDEGHILMNORRSTTTTUU * Data Structure
8. 외부 정렬 • 효율 • CPU 연산시간 << 입출력 시간 • 외부정렬의 효율은 입출력시간에 좌우됨 • Dominant Factor: 입출력 시간 • 몇 단계에 걸쳐 파일 출력이 일어나는가가 관건 • p개 단위의 합병 (p-way external merge sort) • 초기 단계 실행결과 정렬된 블록의 개수는 N/M 개 • N: 주어진 데이터 사이즈 (Ex. 1000MBytes = 1GBytes) • M: 허용된 메모리 사이즈 (Ex. 100MBytes) • 정렬의 효율은 대략 logp(N/M) Data Structure
9. 최선의 정렬 효율 • 결정트리 (Decision Tree) • 3개의 키에 대해서 크기 순서의 조합은 3! = 6 • 불필요한 비교도 감안하여 일반적으로 리프 노드의 수는 N! 개 이상 • Sorting algorithm’s behavior corresponds to some path on a Decisiontree. • 트리의 높이는 최소한 log2(N!) 보다 크고 최소 비교의 회수는 log2(N!). • 스털링(Stirling)의 공식: log2(N!) ≥ log2(N/e)N = Nlog2N - Nlog2e. • 즉, log2(N!) O(Nlog2N). • 따라서 임의의 정렬 알고리즘의 복잡도는 최소한 O(Nlog2N) Data Structure
10. 버킷 정렬과 셈 정렬 • 버킷 정렬 (Bucket Sorting) • 방법 • 정렬의 대상으로 주어진 Record들의 Key Field 값을 전부 살펴본다. • 가장 큰 Key Field 값과 같거나 큰 값을 최대 Index로 지니는 Bucket Array를 만든다. • 다시 한번 주어진 Record들을 앞에서 부터 읽어 가며, 해당 Key Field값을 Array의 Index로 매칭하여 Bucket Array에 Record를 삽입한다. • 효율 • O(N).키 비교에 의한 정렬이 아니므로 O(NlgN)을 능가할 수 있음 • 단점 • 버켓 배열의 크기는 최대 Key Field 값으로 설정. 메모리 공간낭비 가능성 • 이를 개선한 것이 셈 정렬 (계수 정렬) Data Structure
10. 버킷 정렬과 셈 정렬 • 버킷 정렬 (Bucket Sorting) • 같은 배열 인덱스로 분류되는 Record의 처리방법 Data Structure
10. 버킷 정렬과 셈 정렬 • 셈 정렬 (CountingSorting or Distribution Sorting, 계수 정렬) • Key Field의 값 자체의 크기와 무관하게 입력 레코드의 수와 같은 크기의 공간만으로 정렬 • 방법 • 키 빈도를 Count[ ] 배열에 넣음. Count[1]= 4, Count[2] = 2, Count[3] = 1 • 누계를 구하면 Count[1] = 4, Count[2] = 4 + 2 = 6, Count[3] = 6 + 1 = 7 • 다시 한번 오른쪽에서 왼쪽으로 스캔 해 가면서 버켓에 삽입 • 삽입 위치는 현재의 카운트 값. 삽입 직후에는 카운트 값을 하나씩 줄임 • 효율 • O(N+N) = O(2N) = O(N) • 특징 • 버킷 정렬과 셈 정렬 모두 안정정렬이며 Key Field 비교 없이 정렬 수행 • 단점: 메모리사용 큼, Bucket 생성 방법 및 데이터 분배 방법이 쉽지 않음 1 1 2 2 Data Structure
10. 버킷 정렬과 셈 정렬 • 셈 정렬 (CountingSorting or Distribution Sorting, 계수 정렬) Data Structure
10. 버킷 정렬과 셈 정렬 • 셈 정렬 (CountingSorting or Distribution Sorting, 계수 정렬) • 셈 정렬에서 추가적으로 사용하는 메모리 사용량에 대한 분석 • 아래 TempC의 사이즈는 키필드 값에 대하여 Max – MIN + 1 이면 됨 int TempC[MAX_TEMP_DATA_SIZE_FOR_COUNT]; void Count(int A[ ], int N) { for (int i = 0 ; i < MAX_TEMP_DATA_SIZE_FOR_COUNT ; i++) TempC[i] = 0; for (int j = 0 ; j < N ; j++) TempC[A[j]-1]++; for (int k = 0 ; k < MAX_TEMP_DATA_SIZE_FOR_COUNT ; k++) { TempC[k+1] += TempC[k]; } if (TempC[0] != 0) { for (int t = TempC[0]-1 ; t >= 0 ; --t) A[t] = 1; } for (int s = 1 ; s < MAX_TEMP_DATA_SIZE_FOR_COUNT ; s++) { if (TempC[s] == TempC[s-1]) continue; for (int t = TempC[s]-1 ; t >= TempC[s-1] ; --t) { A[t] = s+1; } } } Data Structure
11. 기수 정렬 • 기수 정렬 (Radix Sort) – 문자열 정렬로 사용 • Radix • 뿌리(Root)에 그 어원을 가짐 • 키의 구성요소. 분해된 문자열, 분해된 문자 • 문자열 “KEY”의 뿌리는 각 문자 ‘K’, ‘E’, ‘Y’ • 문자의 뿌리는 비트 (bit) • Basic Policy • 문자열 단위로 비교하는 대신 문자열을 잘라서 문자 단위로 비교 • 더 나아가서 문자 단위로 비교하는 대신 비트 단위로 비교도 가능 int strcmp ( const char * string1, const char * string2 ); [Parameters] string1: Null-terminated string to compare. string2: Null-terminated string to compare. [Results] Data Structure
11. 기수 정렬 • LSD 기수 정렬 (Radix Sort) • 잘 사용 안됨 • MSD 기수 정렬 (Radix Sort) • 왼쪽 (MSD: Most Significant Digit) 에서 오른쪽으로 진행 • 셈 정렬(Distribution Counting)을 사용. O(WN) = O(N) • W: 각 문자열의 길이 • 이전의 모든 문자가 일치하는 것들만 다음 문자를 비교 • 앞부분이 유일(Uniqueness) 해지면 더 이상 비교 대상에서 제외 Data Structure
11. 기수 정렬 • 기수교환 정렬(Radix Exchange Sort) • 문자 단위의 정렬에 셈 정렬 대신 퀵정렬 사용 • 별도 메모리 대신 스와핑이 이용됨 • 3-way 파티션 • 피벗 b와 일치하지 않는 키(add, ace 그룹, dad, fee, cab 그룹)는 다시 첫 문자를 기본으로 재귀적으로 정렬해야 함. • 피벗 b와 일치하는 키(bed, bad, bee)는 두번째 문자를 대상으로 정렬을 진행 Data Structure
11. 기수 정렬 • 비트 단위의 기수 교환 정렬 (Radix Exchange Sort) • 파티션 결과 0인 그룹과 1인 그룹으로 양분 • 비트단위의 쾌속정렬 • 비트 열이 유일(Uniqueness)해지면 더 이상 비교대상에서 제외시킴 Data Structure
11. 기수 정렬 • Comparison Sort • Quick sort • Merge sort • Shell sort • Insertion sort • Selection sort • Bubble sort • Heap sort • Non-comparison Sort • Bucket sort • Counting sort • Radix sort Data Structure