목차
3.2.1 잘못된 구분: [begin, mid-1], [mid, end]
3.2.2 올바른 구분: [begin, mid], [mid+1, end]
1. 병합의 아이디어
머징의 개념을 이번이 두 번째로 이해하게 되었습니다. 이전 연결 리스트 oj 문제에서 처음으로 순서가 있는 두 개의 링크드 리스트를 병합했습니다. 당시 문제를 해결하려는 우리의 아이디어는 병합.
이번에는 체계적으로 병합하는 아이디어를 연구해 봅시다( 이 기사에서는 오름차순을 예로 들었습니다 ).
두 개의 배열(linked list)을 병합할 때 두 개의 포인터를 사용하여 서로 다른 배열의 첫 번째 요소를 가리키고 두 배열을 제어 및 순회하며 두 포인터가 가리키는 값을 비교합니다. 임시 배열을 만든 다음 포인터를 작은 값의 포인터가 한 단계 뒤로 이동하고 비교가 계속됩니다. 계속 비교해 보면 정렬된 배열이기 때문에 항상 먼저 순회 하는 배열이 있고 순회하지 않은 다른 배열은 임시 배열의 뒷면에 직접 연결하여 정렬을 완료합니다.
이 아이디어를 이해하기 위해 그림을 그려 봅시다.
2. 병합 정렬의 아이디어
2.1 기본 아이디어
병합 정렬은 병합 작업을 기반으로 하는 효율적인 정렬 알고리즘입니다.
1. 병합과 정렬의 개념은 하나의 배열을 좌우 두 개의 배열로 나눈 다음 분할된 하위 범위가 하나의 데이터가 될 때까지 계속해서 왼쪽과 오른쪽 배열을 나누고 데이터가 순서대로 정렬되어야 한다는 것입니다. 이분법.
2. 다음으로 왼쪽 및 오른쪽 하위 간격을 정렬 및 병합하고 연속적으로 정렬 및 병합하여 최종적으로 전체 배열의 정렬을 실현합니다.
2.2 그래픽 분석
병합할 때 원본 어레이에서 직접 교환하지 않고 원본 어레이에서 직접 작업하면 덮어쓰기 및 오류가 발생합니다 . 따라서 원래 배열과 같은 크기의 공간 tmp를 malloc하고 각 병합 후 값을 tmp로 복사하고 병합 및 복사를 한 번 수행하면 중간 tmp 배열이 순서대로 된 다음 memcpy를 사용하여 tmp를 원래 배열로 다시 복사합니다. 그런 다음 free(tmp)(메모리 누수를 방지하기 위해) 전체 정렬이 완료됩니다.
3. 병합 정렬 재귀 버전 코드 구현
// 归并排序递归实现
// 时间复杂度:O(N*logN)
// 空间复杂度:O(N + logN)
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, end1 = mid;
int begin2 = mid + 1, 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 (NULL == tmp)
{
perror("malloc fail:");
return;
}
_MergeSort(a, 0, n-1, tmp);
free(tmp);
}
3.1 코드 분석
우리는 이 절에서 재귀를 사용했는데 재귀의 개념은 배열을 좌우 간격으로 나눈 다음 계속해서 왼쪽과 오른쪽 간격을 나누는 것입니다. 정렬+병합을 진행합니다.
이 섹션은 정렬 + 병합입니다. 우리는 두 개의 하위 간격을 정렬 + 병합합니다. 아이디어는 병합에 대한 우리의 아이디어입니다. 여기서 주의할 점은 우리 tmp의 첨자가 begin이라는 점에 유의해야 하는데, 이는 각 레이어의 병합된 값을 먼저 tmp에 넣은 후 원래 배열에 복사해야 하기 때문이며, 0이면 다시 복사할 때 오류가 발생합니다 . .
3.2 주의사항
좌우간격을 나눌때 반드시 [begin, mid], [mid+1, end],
[begin, mid-1], [mid, end]가 될 수 없습니다! ! !
여기서 실수하기 쉬우니 논리상으로는 문제가 없을 것 같지만 정렬할 때 문제가 있을 것입니다.
3.2.1 오류 구분: [begin, mid-1], [mid, end]
3.2.2 올바른 구분: [begin, mid], [mid+1, end]
요약: 무한 루프의 첫 번째 나눗셈에 대한 이유는 mid = begin+end/2일 때 계산기가 내림되기 때문에 내림되면 간격에 중간 값이 포함되어야 합니다. 내림으로.
4. 병합 정렬 테스트
void Print(int* a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
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, end1 = mid;
int begin2 = mid + 1, 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 (NULL == tmp)
{
perror("malloc fail:");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
void test()
{
int a[] = { 6,3,2,1,5,7,9 };
Print(&a, sizeof(a) / sizeof(int));
MergeSort(&a, sizeof(a) / sizeof(int) - 1);
Print(&a, sizeof(a) / sizeof(int));
}
int main()
{
test();
return 0;
}
시험 결과:
5. 시간복잡도와 공간복잡도 분석
5.1 시간 복잡도
머지와 소트 구간을 연속적으로 나누어 시간복잡도는 O(logN), 다시 머지하면 시간복잡도는 O(N)이다.
전체 시간 복잡도는 o(N*logN)입니다.
5.2 공간 복잡도
정렬을 위한 임시 공간을 열었기 때문에 공간 복잡도는 O(N)입니다.