归并排序的基本思想是将两个或两个以上的有序表组合成一个新的有序表。来张丑图:
来上代码:
//归并排序递归
void Merge(int *arr, int start, int mid, int end)
{
assert(NULL != arr);
int low = start;
int high = mid + 1;
int *brr = (int *)malloc(sizeof(int)*(end - start + 1));
int i = 0;
while (low <= mid && high <= end)
{
if (arr[low] < arr[high])
{
brr[i++] = arr[low++];
}
else
{
brr[i++] = arr[high++];
}
}
while (low <= mid)
{
brr[i++] = arr[low++];
}
while (high <= end)
{
brr[i++] = arr[high++];
}
for (int k = 0; k <i ; k++) ///注意
{
arr[start + k] = brr[k];
}
free(brr);
}
void Mesort(int *arr, int start, int end)
{
assert(NULL != arr);
if (start < end)
{
int mid = (start + end) / 2;
Mesort(arr, start, mid); //先分后合
Mesort(arr, mid + 1, end);
Merge(arr, start, mid, end);
}
}
是不是感觉递归那块有点迷糊,来看下面这这张丑图:
是不是感觉清晰点了,讲真,不懂多画图,没毛病!!
以上是归并排序的递归实现,那么如何将递归变为非递归呢?发现递归主要是一层一层进去,等到合并的两个有序表为1的时候才开始合并,那么我们是不是可以直接控制这两个有序表的大小,没错,当然可以,看代码:
//归并排序非递归
void meger(int *arr, int len, int gap)
{
int *brr = (int *)malloc(sizeof(int)* len);
assert(brr != NULL);
int i = 0;
//初始化要合并的区间,2路归并
int L1 = 0;
int H1 = L1 + gap - 1;
int L2 = H1 + 1;
int H2 = L2 + gap - 1 <= len - 1 ? L2 + gap - 1 : len - 1;
while (L2 <= len - 1)
{
while (L1 <= H1 && L2 <= H2)
{
if (arr[L1] <= arr[L2])
{
brr[i++] = arr[L1++];
}
else
{
brr[i++] = arr[L2++];
}
}
while (L1 <= H1)
{
brr[i++] = arr[L1++];
}
while (L2 <= H2)
{
brr[i++] = arr[L2++];
}
//让区间后移,继续进行合并
L1 = H2 + 1;
H1 = L1 + gap - 1;
L2 = H1 + 1;
H2 = L2 + gap - 1 < len - 1 ? L2 + gap - 1 : len - 1;
}
while (L1<len)
{
brr[i++] = arr[L1++];
}
for (int i = 0; i<len; i++)
{
arr[i] = brr[i]; //最后将值再赋给原来的数组
}
free(brr);
}
void MegerSort(int *arr, int len)
{
//子序列长度不断变化
for (int i = 1; i<len; i = i * 2)
{
meger(arr, len, i);
}
}
程序中用for循环直接控制了要归并的序列的区间大小,即1,2,4,8,16
是不是感觉也非常的清晰,代码中的L1,H1,L2,H2是自己定义的要归并的序列的区间值。
来说下归并排序的复杂度:
递归:
一趟归并需要将待排序序列中的所有记录扫描一遍,耗费O(n),整个归并排序需要进行(log2N,以2为底N的对数向上取整)次,所以总的时间复杂度是O(n*logn)。
空间复杂度是O(n+logn)包括栈空间。
非递归:
避免了栈空间的消耗,避免递归也减少了时间时间开销。建立归并使用非递归。
归并排序是两两比较,不存在跳跃,所以是稳定的一种排序。