以数组为例说明归并排序的思想:
归并排序又称为二路归并排序,其思想就是分治策略;每一个元素自身都是有序的,然后俩个结合为一组,组内进行排序,然后俩个组再结合为一组,组内排序。
从概念上讲,归并排序的工作原理如下:
1.将未排序的数组划分为n个子数组,每个子数组包含一个元素(一个元素的数组认为是有序的)。
2.反复合并子数组以产生新的有序子数组,直到只剩一个子数组。这将是归并排序。
先将数组中1个元素划分为一块:
l1:当前操作的第一个组的起始点 初始值l1=0;
h1:当前操作的第一个组的终止点 初始值h1=l1+gap-1 (gap是分组规模,也就是分为gap个组)
l2:当前操作的第二个组的起始点 初始值l2=h1+1 (l2永远在h1后边一个的位置)
h2:当前操作的第二个组的终止点 初始值 h2=l2+gap-1<len?l2+gap-1:len-1;(下面再解释)
第一次分组的时候,l1和h1都指向5,l2和h2都指向了9;然后将这四个指针所指向的俩块数组合并:
合并的时候要进行排序,先从里面各取一个最小值进行比较,也就是第一块子数组的 l1和第二块子数组的 l2,进行比较,较小的 l1 的值放入到我们已经申请好的堆内存当中,接着 l1 = l1+1;在进行比较 l1 和l2的值,较小的放入到堆内存当中,再向后走,一直到 l1大于终止点h1或者l2大于终止点 h2,就认为其中一个子数组的元素已经被取完了,再将另一个未取完的数组的值直接挪到堆内存当中即可。
前俩个元素处理完,这四个指针就接着向后跑,
l1 = h2 + 1;
h1 = l1 + gap - 1;
l2 = h1 + 1;
h2 = l2 + gap - 1 < len ? l2 + gap - 1 : len - 1;
继续执行上述操作,一直到将数组遍历完,也就是当l1>=数组长度len的时候,认为数组遍历完了。
数组遍历完后得到的新数组是这样:
之前我们是一个元素为一组,俩俩合并,现在由于合并的元素都是有序的,进行俩个元素为一组;俩俩合并:
继续分别取l1 和 l2的元素进行比较,小的(l2)放入申请好的数组中;l2+1;再次进行比较 l1 与l2 元素的大小……,一直到 l1大于终止点h1或者l2大于终止点 h2,再将还有剩余的子数组的元素挪到新数组中(子数组一定是有序的,因为是我们之前排序过的)。
第二次分组执行完后:
再接着gap=4,四个一组进行俩个组之间的合并。
一直这样排序,知道分的组比数组本身都大,那么排序就完成了!
下面说这个问题:
初始值 h2=l2+gap-1<len?l2+gap-1:len-1;
h2可能会有溢出的情况,所以当h2溢出的时候,直接把数组最后一个下表赋值给他就好了,不需要再多加进行判断。
可以看到,归并排序的时间复杂度为O(nlogn),时间复杂度比起其他算法略低但是空间复杂度为O(n)
下面是归并排序的代码:
const int SIZE = 10;
void Merge(int arr[], int gap)
{
int l1 = 0;
int h1 = l1 + gap - 1;
int l2 = h1 + 1;
int h2 = l2 + gap - 1 < SIZE ? l2 + gap - 1 : SIZE - 1;
int* brr = new int[SIZE] {};
int i = 0;
while (l2 < SIZE)
{
while (l1 <= h1 && l2 <= h2)
{
if (arr[l1] > arr[l2])
{
brr[i++] = arr[l2++];
}
else
{
brr[i++] = arr[l1++];
}
}
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 < SIZE ? l2 + gap - 1 : SIZE - 1;
}
while (l1 <SIZE)
{
brr[i++] = arr[l1++];
}
memmove(arr, brr,SIZE*sizeof(int));
delete[] brr;
brr = nullptr;
}
int main()
{
int arr[SIZE] = { 5,9,4,2,6,7,1,3,8,0 };
for (int i = 1; i < SIZE; i *= 2)
{
Merge(arr, i);
}
for (int i = 0; i < SIZE; ++i)
{
cout << arr[i] << " ";
}
}
结果:
感谢阅读!