目录
思路
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列。即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
演示
实现它走的是一个递归,先让一个序列不断对半拆分(递推),拆到只有一个数据的序列时(递推结束条件),开始回归排序(回归)。具体如下图。
代码
在排序时不能在原数组上进行排序,解决这个问题其实只需要开辟一个与原数组大小一样的空间,在排完一个后把该数据存入开辟的数组中,当这次序排完后把数据拷贝到原数组中即可。
void _MergeSort(int* arr, int* temp, int left, int right)
{
if (left == right)
return;
int mid = (left + right) / 2;
_MergeSort(arr, temp, left, mid);
_MergeSort(arr, temp, mid + 1, right);
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = left;//这里的i = left,我是让原数组的起始位置与开辟的数组的该位置对齐
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])
{
temp[i++] = arr[begin1++];
}
else
{
temp[i++] = arr[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = arr[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = arr[begin2++];
}
memcpy(arr + left, temp + left, (right - left + 1) * sizeof(int));//拷贝到原数组
}
//归并排序
void MergeSort1(int* arr, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc");
return;
}
_MergeSort(arr, temp, 0, n - 1);//这是一个左闭右闭
free(temp);
temp = NULL;
}
补充
非递归实现
我们用下面的这组数据来讲解怎么实现。
回顾上面递归实现,可以发现是先对下标为0~1排序在对下标为2~3排序...这次排完后对下标0~3排序以此类推。所以我们是不是可以创建一个变量gap用来控制上面下标变化。
下面的代码主框架其实没变,就是递归改成循环了。
//归并排序(非递归)
void MergeSort2(int* arr, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc");
return;
}
int gap = 1;
while (gap < n)
{
for (int j = 0; j < n; j += 2 * gap)
{
int begin1 = j, end1 = j + gap - 1;
int begin2 = j + gap, end2 = j + 2 * gap - 1;
int i = j;
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])
{
temp[i++] = arr[begin1++];
}
else
{
temp[i++] = arr[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = arr[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = arr[begin2++];
}
memcpy(arr + j, temp + j, (end2 - j + 1) * sizeof(int));
}
gap *= 2;
}
free(temp);
temp = NULL;
}
上面的代码就只能对2的倍数进行排序如果不是就无法排序,下面的图片是对一个只有10个数据的序列下标访问情况。
划红线的都是越界访问的,可以发现第二行和第三行有个共同特征都是begin2大于等于n遇到这种情况直接跳出循环即可。如果end2大于等于n让他等于n-1即可。
//归并排序(非递归)
void MergeSort2(int* arr, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc");
return;
}
int gap = 1;
while (gap < n)
{
for (int j = 0; j < n; j += 2 * gap)
{
int begin1 = j, end1 = j + gap - 1;
int begin2 = j + gap, end2 = j + 2 * gap - 1;
int i = j;
if (begin2 >= n)
break;
if (end2 >= n)
end2 = n - 1;
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])
{
temp[i++] = arr[begin1++];
}
else
{
temp[i++] = arr[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = arr[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = arr[begin2++];
}
memcpy(arr + j, temp + j, (end2 - j + 1) * sizeof(int));
}
gap *= 2;
}
free(temp);
temp = NULL;
}
例题
题目
该题链接:https://leetcode.cn/problems/sort-list/description/
思路
我第一次看到这道题时,想的是用插入排序来解决但是没有实现,因为有一个测试用例很长很长。所以有解决这道题就要用比较快的排序,考虑到该题的特殊性用归并排序解决是最合理的。
这道题的解决思路和上面基本一样,也是不断对半拆分。递推结束条件会有所变化。当头指针指向的下一个是尾指针时,即开始返回并把头指针的指向置为空。然后调用合并两个有序链表的函数即可实现。
代码
//合并两个有序链表的函数
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
if(list1 == NULL)
return list2;
if(list2 == NULL)
return list1;
struct ListNode* n1 = list1, *n2 = list2;
struct ListNode* newhead = NULL, *newend = NULL;
newhead = newend = (struct ListNode*)malloc(sizeof(struct ListNode));
while(n1 && n2)
{
if(n1->val < n2->val)
{
newend->next = n1;
newend = newend->next;
n1 = n1->next;
}
else
{
newend->next = n2;
newend = newend->next;
n2 = n2->next;
}
}
if(n1 == NULL)
{
newend->next = n2;
}
if(n2 == NULL)
{
newend->next = n1;
}
struct ListNode* ret = newhead->next;
free(newhead);
newhead = NULL;
return ret;
}
struct ListNode* MergeSort(struct ListNode* phead, struct ListNode* ptail)
{
if(phead->next == ptail)
{
phead->next = NULL;
return phead;
}
struct ListNode* slow = phead, *fast = phead;
while(fast != ptail)
{
slow = slow->next;
fast = fast->next;
if(fast != ptail)
fast = fast->next;
}
struct ListNode* mid = slow;
struct ListNode* l1 = MergeSort(phead, mid);
struct ListNode* l2 = MergeSort(mid, ptail);
return mergeTwoLists(l1, l2);
}
struct ListNode* sortList(struct ListNode* head) {
return head == NULL ? NULL : MergeSort(head, NULL);
}