归并排序很基础,通常的实现需要O(n)的空间复杂度,思路简单,不赘述。
今年的百度笔试题考了一个原地归并排序的归并算法:
要求用O(1)的空间复杂度,合并2个已经有序的子数组a[0,mid-1] 和 a[mid,num-1]。按照升序排列。
手摇算法:使用3次reverse操作实现相邻区间的交换位置
void exchange(int a[], int size, int n) {
reverse(a, n);
reverse(a + n, size - n);
reverse(a, size);
}
原理很简单,不多说了。
假设数组a[p...m]和a[m+1...r]是两个相邻的有序数组。
对于原地归并,我们定义两个下标i和j,保持下列循环不变式:
a[p...i-1],a[i...j-1],a[j...r]都是有序
i和j所指向的元素就是待归并的两个数组的第一个元素。
我们比较a[i]和a[j]
1. 如果a[i]比较小,我们就++i,显然循环不变式得到保持。
2. 如果a[j]比较小,令old_j = j,然后我们从j开始向后顺序搜索,直到a[j]>a[i],然后我们利用手摇法来交换a[i..old_j-1]和a[old_j...j-1],更新i的值。
重复上面的步骤,直到i == j 或者 j == r + 1
然后就是实现之~
//
// Merge.c
// O(1) : space complexity
//
#include <stdio.h>
void display(int a[], int num);
void swap(int *a, int *b);
void reverse(int a[], int size);
void exchange(int a[], int size, int n);
void merge(int a[], int mid, int num);
void merge_sort(int a[], int size);
int main()
{
int a[6] = {2,5,8,1,3,7};
//merge(a, 3, 6);
merge_sort(a, 6);
return 0;
}
void display(int a[], int num)
{
for (int i = 0; i < num; i++)
printf("%d ", a[i]);
printf("\n");
}
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
/*
space: O(1)
reverse
*/
void reverse(int a[], int size)
{
int s = 0, e = size - 1;
while (s < e && s < size && e > 0) {
swap(&a[s++], &a[e--]);
}
}
void exchange(int a[], int size, int n)
{
reverse(a, n);
reverse(a + n, size - n);
reverse(a, size);
}
/*
a[0, mid-1] and a[mid, num-1] are sorted
*/
void merge(int a[], int mid, int num)
{
int fir = 0, sec = mid;
while (fir < sec && sec < num) {
while (fir < sec && a[fir] <= a[sec]) {
fir++;
}
int maxmove = 0;
while (sec < num && a[fir] > a[sec]) {
maxmove++,sec++;
}
exchange(&a[fir], sec - fir, sec - fir - maxmove);
fir += maxmove;
display(a, num);
}
}
void merge_sort(int a[], int num)
{
if (num <= 1) {
return;
}
merge_sort(a, num/2);
merge_sort(a + num/2, num - num/2);
merge(a, num/2, num);
}