안녕하세요 CSDN 여러분, 오늘 Xiaoyalan의 내용은 여전히 데이터 구조 및 알고리즘 열의 정렬입니다.다음으로 병합 정렬의 세계로 들어가 봅시다! ! !
병합 정렬
병합 정렬(MERGE-SORT)은 분할 및 정복(Divide and Conquer)의 매우 일반적인 응용 프로그램인 병합 연산을 기반으로 한 효과적인 정렬 알고리즘입니다. 정렬된 하위 시퀀스를 결합하여 완전히 정렬된 시퀀스를 얻습니다. 즉, 먼저 각 하위 시퀀스를 순서대로 만든 다음 하위 시퀀스 세그먼트를 순서대로 만듭니다. 두 개의 정렬된 목록을 하나의 정렬된 목록으로 병합하는 것을 양방향 병합이라고 합니다. 병합 정렬 핵심 단계:
void _MergeSort(int* a, int begin, int end, int* tmp) { if (begin >= end) { return; } int mid = (begin + end) / 2; //[begin,mid] [mid+1,end] _MergeSort(a, begin, mid, tmp); _MergeSort(a, mid + 1, end, tmp); //归并两个区间 int begin1 = begin; int begin2 = mid + 1; int end1 = mid; int end2 = end; int i = begin; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[i++] = a[begin1++]; } else { tmp[i++] = a[begin2++]; } } while (begin1 <= end1) { tmp[i++] = a[begin1++]; } while (begin2 <= end2) { tmp[i++] = a[begin2++]; } memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1)); } //归并排序 void MergeSort(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); _MergeSort(a, 0, n - 1, tmp); free(tmp); }
테스트 병합 정렬:
무효 TestMergeSort()
{ int a[] = { 2,1,4,3,6,5,7,9,8,10 }; PrintArray(a, sizeof(a) / sizeof(a[0])); MergeSort(a, sizeof(a) / sizeof(a[0])); PrintArray(a, sizeof(a) / sizeof(a[0])); }
병합 정렬의 특성 요약:
- 머징의 단점은 O(N) 공간복잡도가 필요하다는 점이며, 머징과 정렬에 대한 생각은 디스크에서 외부 정렬 문제를 해결하는 데 더 가깝습니다.
- 시간 복잡도: O(N*logN)
- 공간 복잡도: O(N)
- 안정성: 안정적
병합 정렬 비재귀
void MergeSortNonR(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc失败!!!"); return; } int gap = 1; while (gap < n) { int j = 0; for (int i = 0; i < n; i += gap) { //每组的合并数据 int begin1 = i; int end1 = i + gap - 1; int begin2 = i + gap; int end2 = i + 2 * gap - 1; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[j++] = a[begin1++]; } else { tmp[j++] = a[begin2++]; } } while (begin1 <= end1) { tmp[j++] = a[begin1++]; } while (begin2 <= end2) { tmp[j++] = a[begin2++]; } } memcpy(a, tmp, sizeof(int) * n); gap *= 2; } free(tmp); }
하지만 이 코드는 매우 심각한 아웃오브바운드 문제가 있는데, 데이터가 2의 제곱인 경우에만 아웃오브바운드가 되지 않습니다! ! !
Xiao Yalan은 더 명확하게 보기 위해 여기에 여러 데이터 세트를 인쇄합니다.
void MergeSortNonR(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc失败!!!"); return; } // 1 2 4 .... int gap = 1; while (gap < n) { int j = 0; for (int i = 0; i < n; i += 2 * gap) { // 每组的合并数据 int begin1 = i; int end1 = i + gap - 1; int begin2 = i + gap; int end2 = i + 2 * gap - 1; printf("[%d,%d][%d,%d]\n", begin1, end1, begin2, end2); if (end1 >= n || begin2 >= n) { break; } // 修正 if (end2 >= n) { end2 = n - 1; } while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[j++] = a[begin1++]; } else { tmp[j++] = a[begin2++]; } } while (begin1 <= end1) { tmp[j++] = a[begin1++]; } while (begin2 <= end2) { tmp[j++] = a[begin2++]; } // 归并一组,拷贝一组 memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1)); } printf("\n"); gap *= 2; } free(tmp); }
이렇게 수정하면 됩니다! ! !
이 범위를 벗어난 문제에 대한 두 번째 솔루션이 있습니다.
void MergeSortNonR(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); // 1 2 4 .... int gap = 1; while (gap < n) { int j = 0; for (int i = 0; i < n; i += 2 * gap) { // 每组的合并数据 int begin1 = i, end1 = i + gap - 1; int begin2 = i + gap, end2 = i + 2 * gap - 1; printf("修正前:[%d,%d][%d,%d]\n", begin1, end1, begin2, end2); if (end1 >= n) { end1 = n - 1; // 不存在区间 begin2 = n; end2 = n - 1; } else if (begin2 >= n) { // 不存在区间 begin2 = n; end2 = n - 1; } else if(end2 >= n) { end2 = n - 1; } printf("修正后:[%d,%d][%d,%d]\n", begin1, end1, begin2, end2); while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] <= a[begin2]) { tmp[j++] = a[begin1++]; } else { tmp[j++] = a[begin2++]; } } while (begin1 <= end1) { tmp[j++] = a[begin1++]; } while (begin2 <= end2) { tmp[j++] = a[begin2++]; } } printf("\n"); memcpy(a, tmp, sizeof(int) * n); gap *= 2; } free(tmp); }
다양한 종류의 테스트
// 测试排序的性能对比 void TestOP() { srand(time(0)); const int N = 1000000; int* a1 = (int*)malloc(sizeof(int) * N); int* a2 = (int*)malloc(sizeof(int) * N); int* a3 = (int*)malloc(sizeof(int) * N); int* a4 = (int*)malloc(sizeof(int) * N); int* a5 = (int*)malloc(sizeof(int) * N); int* a6 = (int*)malloc(sizeof(int) * N); int* a7 = (int*)malloc(sizeof(int) * N); for (int i = 0; i < N; ++i) { a1[i] = rand(); a2[i] = a1[i]; a3[i] = a1[i]; a4[i] = a1[i]; a5[i] = a1[i]; a6[i] = a1[i]; a7[i] = a1[i]; } int begin1 = clock(); InsertSort(a1, N); int end1 = clock(); int begin2 = clock(); ShellSort(a2, N); int end2 = clock(); int begin3 = clock(); SelectSort(a3, N); int end3 = clock(); int begin4 = clock(); HeapSort(a4, N); int end4 = clock(); int begin5 = clock(); QuickSort(a5, 0, N - 1); int end5 = clock(); int begin6 = clock(); MergeSort(a6, N); int end6 = clock(); int begin7 = clock(); BubbleSort(a7, N); int end7 = clock(); printf("InsertSort:%d\n", end1 - begin1); printf("ShellSort:%d\n", end2 - begin2); printf("SelectSort:%d\n", end3 - begin3); printf("HeapSort:%d\n", end4 - begin4); printf("QuickSort:%d\n", end5 - begin5); printf("MergeSort:%d\n", end6 - begin6); printf("BubbleSort:%d\n", end7 - begin7); free(a1); free(a2); free(a3); free(a4); free(a5); free(a6); free(a7); }
모든 정렬 소스 코드:
Sort.h의 내용:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>
#include<string.h>
무효 PrintArray(int* a, int n);
// 직접 삽입 정렬
void InsertSort(int* a, int n);// 쉘 정렬
void ShellSort(int* a, int n);// 직접 선택 정렬
void SelectSort(int* a, int n);// 堆排序
void AdjustDown(int* a, int n, int root);
무효 HeapSort(int* a, int n);// 버블 정렬
void BubbleSort(int* a, int n);//
int PartSort1(int* a, int left, int right);
int PartSort2(int* a, int 왼쪽, int 오른쪽);
int PartSort3(int* a, int 왼쪽, int 오른쪽);
void QuickSort(int* a, int begin, int end);무효 QuickSortNonR(int* a, int 시작, int 끝);
// 병합 정렬
void MergeSort(int* a, int n);무효 MergeSortNonR(int* a, int n);
Sort.c의 내용:
#include"Sort.h"
#include"Stack.h"
void PrintArray(int* a, int n)
{ int i = 0; for (i = 0; i < n; i++) { printf("%d ", a[i]); } printf("\n"); }
//직접 삽입 정렬
void InsertSort(int* a, int n)
{ int i = 0; for (i = 1; i < n; i++) { int end = i - 1; int tmp = a[i]; while (end >= 0) { //삽입된 데이터가 원본 데이터보다 작은 경우 if (a[end] > tmp) { a[end + 1] = a[end]; --end; } else { break; } } a[종료 + 1] = tmp; } }
//쉘 정렬
void ShellSort(int* a, int n)
{ //1.gap>1, 사전 정렬 //2.gap==1, 직접 삽입 정렬 int gap = n; while (gap > 1) { gap = gap / 3 + 1; //+1은 마지막 시간이 1이어야 함을 보장할 수 있습니다. for (int i = 0; i < n - gap; i++) { int end = i; int tmp = a[end + gap ]; while (end >= 0) { if (a[end] > tmp) { a[end + gap] = a[end]; end = end - gap; } else {
부서지다;
}
}
a[끝 + 간격] = tmp;
} }
}
//
무효 BubbleSort(int* a, int n)
{ for (int j = 0; j < n; j++) { bool exchange = false; for (int i = 1; i < n - j; i++) { if (a[i - 1] > a[i]) { int tmp = a[i]; a[i] = a[i - 1]; a[i - 1] = tmp; 교환 = 참; } } if (교환 == 거짓) { 중단; } } }
무효 스왑(int* a1, int* a2)
{ int tmp = *a1; *a1 = *a2; *a2 = tmp; }//직접 선택 정렬
void SelectSort(int* a, int n)
{ int begin = 0; int end = n - 1; while (begin < end) { int maxi = 시작; int mini = 시작; for (int i = i <= end; i++) { if (a[i] > a[maxi]) { maxi = i; } if (a[i] < a[mini]) { mini = i; } } Swap(&a [begin], &a[mini]); //maxi와 begin이 겹치는 경우 그냥 수정 if (begin ==maxi) {
맥시 = 미니;
}
Swap(&a[end], &a[maxi]);
++시작;
--끝;
}
}//아래로 조정 알고리즘
void AdjustDown(int* a, int n, int parent)
{ //기본 왼쪽 자식은 작음 int child = parent * 2 + 1; while (child < n)//자식은 범위 내에 있음 of the array { //왼쪽 자식과 오른쪽 자식 중 큰 것을 선택 // 왼쪽 자식 이 없다고 가정하는 것은 잘못일 수 있습니다. , 오른쪽 자식이 없을 수 있습니다 if (child + 1 < n && a[child + 1] > a[child]) // 오른쪽 자식이 존재합니다 right child > left child // 이렇게 쓸 수 없습니다 if (a[child] + 1] > a[chid] && child + 1 < n ) // 배열의 요소에 먼저 액세스한 다음 올바른 자식이 있는지 확인하기 위해 비교하기 때문에 이런 방식으로 작성할 때 범위를 벗어날 위험이 있습니다. { ++자식; } //자식이 나이가 많은 자식 //왼쪽 자식이든 오른쪽 자식이든 상관하지 않습니다.
if (a[child] > a[parent])
{ Swap(&a[child], &a[parent]); parent = child; child = parent * 2 + 1;//기본값은 왼쪽 자식입니다 } else { break ; }
}
}
//HeapSort
void HeapSort(int* a, int n)
{ //힙 생성——힙을 아래쪽으로 조정 int i = 0; for (i = (n - 1 - 1) / 2; i >= 0 ; i--) { AdjustDown(a, n, i); } //오름차순 순서 - 큰 더미 만들기 int end = n - 1; while (end > 0) { Swap(&a[0], &a[end] ); AdjustDown(a, end, 0); --end; } }
//三数取中
int GetMidIndex(int* a, int left, int right)
{ int mid = (left + right) / 2; if (a[left] < a[mid]) { if (a[mid] < a[right]) { return mid; } else if (a[왼쪽] < a[오른쪽]) { 오른쪽으로 돌아가기; } else { 왼쪽으로 돌아가기; } } else // a[왼쪽] > a[mid] { if (a[mid] > a[right]) { return mid; }
else if (a[왼쪽] > a[오른쪽])
{ 오른쪽으로 돌아가기; } else { 왼쪽으로 돌아가기; } } } // 호어 // [left, right] int PartSort1(int* a, int left, int right) { int midi = GetMidIndex(a, left, right); Swap(&a[왼쪽], &a[midi]);
int keyi = 왼쪽;
while (left < right)
{ // 右边找小 while (left < right && a[right] >= a[keyi]) { --right; }
// 左边找大
while (left < right && a[left] <= a[keyi])
{ ++left; }Swap(&a[왼쪽], &a[오른쪽]);
}Swap(&a[keyi], &a[left]);
왼쪽으로 돌아갑니다.
}
挖坑法
[left, right]
//int PartSort2(int* a, int left, int right)
//{ // int midi = GetMidIndex(a, left, right); // 교환(&a[왼쪽], &a[midi]); // // int 키 = a[왼쪽]; // 정수 구멍 = 왼쪽; // while (left < right) // { // // 右边找小// while (left < right && a[right] >= key) // { // --right; // } // // a[구멍] = a[오른쪽]; // 구멍 = 오른쪽; // // // 左边找大// while (left < right && a[left] <= key) // { // ++left;
// }
//
// a[구멍] = a[왼쪽];
// 구멍 = 왼쪽;
// }
//
// a[구멍] = 키;
//
// 반환 구멍;
//}
//
前后指针法
[left, right]
//int PartSort3(int* a, int left, int right)
//{ // int midi = GetMidIndex(a, left, right); // 교환(&a[왼쪽], &a[midi]); // // int prev = 왼쪽; // int cur = 왼쪽 + 1; // int keyi = 왼쪽; // while (cur <= right) // { // if (a[cur] < a[keyi] && ++prev != cur) // { // Swap(&a[prev], &a[cur]) ;
// }
//
// ++cur;
// }
//
// Swap(&a[prev], &a[keyi]);
// keyi = 이전;
// 키를 반환합니다.
//}
//
무효 QuickSort(int* a, int begin, int end)
{ if (begin >= end) { return; } int keyi = PartSort1(a, 시작, 종료); //[begin,keyi-1] keyi [keyi+1,end] QuickSort(a, begin, keyi - 1); 퀵소트(a, keyi + 1, end); }
//빠른 정렬 비재귀
void QuickSortNonR(int* a, int begin, int end)
{ Stack st; StackInit(&st); StackPush(&st, end); StackPush(&st, begin);while (!StackEmpty(&st))
{ int left = StackTop(&st); StackPop(&st);오른쪽 정수 = StackTop(&st);
StackPop(&st);int keyi = PartSort1(a, 왼쪽, 오른쪽);
// [왼쪽, keyi-1] keyi [keyi+1, 오른쪽]
if (keyi + 1 < right)
{ StackPush(&st, right); StackPush(&st, keyi + 1); }if (left < keyi - 1)
{ StackPush(&st, keyi - 1); StackPush(&st, 왼쪽); } }StackDestroy(&st);
}
무효 _MergeSort(int* a, int 시작, int 끝, int* tmp)
{ if (시작 >= 끝) { 반환; } int mid = (시작 + 끝) / 2; //[시작,중간] [중간+1,종료] _MergeSort(a, 시작, 중간, tmp); _MergeSort(a, 중간 + 1, 끝, tmp); // 归并两个区间 int begin1 = 시작; int 시작2 = 중간 + 1; int end1 = 중간; 정수 end2 = 끝; 정수 i = 시작; while (시작1 <= 끝1 && 시작2 <= 끝2) { if (a[begin1] < a[begin2]) { tmp[i++] = a[begin1++]; } 다른
{ tmp[i++] = a[begin2++]; } } while (begin1 <= end1) { tmp[i++] = a[begin1++]; } while (begin2 <= end2) { tmp[i++] = a[begin2++]; } memcpy(a + 시작, tmp + 시작, sizeof(int) * (끝 - 시작 + 1)); } // 무효 MergeSort(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); if(tmp == NULL) { perror("malloc이 취소되었습니다!!!"); 반품; } _MergeSort(a, 0, n - 1, tmp); 무료(tmp); }
// 병합 정렬 비재귀
void MergeSortNonR(int* a, int n)
{ int* tmp = (int*)malloc(sizeof(int) * n);// 1 2 4 ....
int gap = 1;
while (gap < n)
{ int j = 0; for (int i = 0; i < n; i += 2 * gap) { // 각 그룹에 대해 데이터 병합 int begin1 = i, end1 = i + gap - 1; int begin2 = i + gap, end2 = i + 2 * gap - 1;
printf("수정 전: [%d,%d][%d,%d]\n", begin1, end1, begin2, end2);
if(end1 >= n)
{ end1 = n - 1;// 간격이 없습니다.
begin2 = n;
end2 = n - 1;
}
else if (begin2 >= n)
{ // 간격이 없습니다. begin2 = n; end2 = n - 1; } else if(end2 >= n ) { 끝2 = n - 1; }
printf("수정 후: [%d,%d][%d,%d]\n", begin1, end1, begin2, end2);
동안 (시작1 <= 끝1 && 시작2 <= 끝2)
{ if (a[begin1] <= a[begin2]) { tmp[j++] = a[begin1++]; } else { tmp[j++] = a[begin2++]; } }
while (begin1 <= end1)
{ tmp[j++] = a[begin1++]; }while (begin2 <= end2)
{ tmp[j++] = a[begin2++]; } } printf("\n");memcpy(a, tmp, sizeof(int) * n);
갭 *= 2;
}무료(tmp);
}
// 무효 MergeSortNonR(int* a, int n)
//{ // int* tmp = (int*)malloc(sizeof(int) * n); // if(tmp == NULL) // { // perror("malloc失败!!!"); // 반품; // } // // 1 2 4 .... // 정수 간격 = 1; // while (gap < n) // { // int j = 0; // for (int i = 0; i < n; i += 2 * gap) // { // // 每组的合并数据// int begin1 = i; // int end1 = i + 간격 - 1; // 정수 시작2 = i + 간격; // int end2 = i + 2 * 간격 - 1; //
// printf("[%d,%d][%d,%d]\n", 시작1, 끝1, 시작2, 끝2);
//
// if (end1 >= n || begin2 >= n)
// { // 중단; // } // // // 修正// if (end2 >= n) // { // end2 = n - 1; // } // // while (begin1 <= end1 && begin2 <= end2) // { // if (a[begin1] < a[begin2]) // { // tmp[j++] = a[begin1++] ; // } // 그렇지 않으면 // {
// tmp[j++] = a[begin2++];
// }
// }
//
// while (begin1 <= end1)
// { // tmp[j++] = a[begin1++]; // } // / / while (begin2 <= end2) // { // tmp[j++] = a[begin2++]; // } // // // 그룹 병합, 그룹 복사 // memcpy(a + i, tmp + i , sizeof(int) * (end2 - i + 1)); // } // printf("\n"); // 간격 *= 2; // } // free(tmp); //}
Leetcode 일일 질문 - "912. 배열 정렬"
leetcode에 대한 질문이 있습니다. 통과할 수 있는지 테스트하기 위해 다양한 종류를 사용할 수 있습니다.
Xiao Yalan은 여기에서 병합 정렬을 시도했고 쉽게 통과했습니다! ! !
void _MergeSort(int* a, int begin, int end, int* tmp) { if (begin >= end) { return; } int mid = (begin + end) / 2; //[begin,mid] [mid+1,end] _MergeSort(a, begin, mid, tmp); _MergeSort(a, mid + 1, end, tmp); //归并两个区间 int begin1 = begin; int begin2 = mid + 1; int end1 = mid; int end2 = end; int i = begin; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[i++] = a[begin1++]; } else { tmp[i++] = a[begin2++]; } } while (begin1 <= end1) { tmp[i++] = a[begin1++]; } while (begin2 <= end2) { tmp[i++] = a[begin2++]; } memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1)); } //归并排序 void MergeSort(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc失败!!!"); return; } _MergeSort(a, 0, n - 1, tmp); free(tmp); } int* sortArray(int* nums, int numsSize, int* returnSize){ MergeSort(nums, numsSize); *returnSize = numsSize; return nums; }
다음과 같이 작성할 수도 있습니다. 상대적으로 더 나은 셀 사이에 최적화된 버전이지만 이 효과는 leetcode에서 테스트할 수 없습니다.
//直接插入排序 void InsertSort(int* a, int n) { int i = 0; for (i = 1; i < n; i++) { int end = i - 1; int tmp = a[i]; while (end >= 0) { //插入的数据比原来的数据小 if (a[end] > tmp) { a[end + 1] = a[end]; --end; } else { break; } } a[end + 1] = tmp; } } void _MergeSort(int* a, int begin, int end, int* tmp) { if (begin >= end) { return; } //小区间优化 if(end-begin+1<10) { InsertSort(a+begin,end-begin+1); return; } int mid = (begin + end) / 2; //[begin,mid] [mid+1,end] _MergeSort(a, begin, mid, tmp); _MergeSort(a, mid + 1, end, tmp); //归并两个区间 int begin1 = begin; int begin2 = mid + 1; int end1 = mid; int end2 = end; int i = begin; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] < a[begin2]) { tmp[i++] = a[begin1++]; } else { tmp[i++] = a[begin2++]; } } while (begin1 <= end1) { tmp[i++] = a[begin1++]; } while (begin2 <= end2) { tmp[i++] = a[begin2++]; } memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1)); } //归并排序 void MergeSort(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc失败!!!"); return; } _MergeSort(a, 0, n - 1, tmp); free(tmp); } int* sortArray(int* nums, int numsSize, int* returnSize){ MergeSort(nums,numsSize); *returnSize = numsSize; return nums; }
그러나이 질문에 대해서는 직접 삽입 정렬 및 거품 정렬을 통한 정렬을 통과할 수 없으며 다음과 같은 메시지가 표시됩니다. 시간 제한 초과
안타깝게도 퀵 정렬은 통과하지 못했고 Xiao Yalan은 여러 번 반복해서 테스트했습니다.
자, 이것으로 오늘의 Xiao Yalan 콘텐츠 병합 및 정렬이 끝났습니다. 계속 진행하겠습니다! ! !