归并排序是效率非常高的一种排序方式,和快速排序一样用了分治的思想。分治法的精髓在于将一个复杂问题分割成为多个简单的子问题,然后将子问题逐个解决,最终合并在一起以后就是复杂问题的解了。
归并排序对数组排序时有两种实现方式,一种是利用递归来实现,另一种利用循环来实现,也可以叫自顶向下的归并排序和自底向上的归并排序。两种实现方式略有不同,但是基本思想是一样的,那听我慢慢道来。
递归版的归并排序的思想其实挺简单的,简而言之就是将数列不断分割成子序列,直到不能分割为止,然后再将排序好的子序列两两合并最终得到一个完整的数列。具体例子如下:
[3 7 6 4 8 9 2 1]
/ \
分割 [3 7 6 4] [8 9 2 1]
/ \ / \
分割 [3 7] [6 4] [8 9] [2 1]
/ \ / \ / \ / \
分割 [3] [7] [6] [4] [8] [9] [2] [1]
\ / \ / \ / \ /
合并 [3 7] [4 6] [8 9] [1 2]
\ / \ /
合并 [3 4 6 7] [1 2 8 9]
\ /
合并 [1 2 3 4 6 7 8 9]
分割时从数列中间开始,将数列分成左右两部分,然后对分割后的序列继续进行分割直到分割不能再分为止。然后对已经排好序的子序列进行合并,重新产生一个排序好的数列。
来看看代码如何实现:
//num -- 待排序的数组
//start -- 排序的起点
//end -- 排序数列的长度
void MergeSort(int *nums, int start, int end) {
//判断起点是否小于终点
if (end-start<2) {
return;
}
//进行分割操作
int mid = (start + end) / 2;
MergeSort(nums, start, mid);
MergeSort(nums, mid , end);
//归并两个子列
Merge(nums, start, mid, end);
}
//归并函数
void Merge(int *nums, int start, int mid, int end) {
//需要一个临时数组来存放排序好的数据
int *temp = (int *)malloc(sizeof(int)*(end - start));
// 对分割数列进行归并
int i = start;
int j = mid;
int index = 0;
while (index + start < end) {
//从两个子序列中取出数据放入临时数组
if (i < mid && (j == end || nums[i] < nums[j])) {
temp[index++] = nums[i++];
}
else {
temp[index++] = nums[j++];
}
}
//将临时数组的值重现赋给数组
for (int i = start; i < end; i++) {
nums[i] = temp[i - start];
}
//释放临时数组
free(temp);
}
循环实现和递归版本基本类似,不过理解上可能稍微要难一点点,也会多一点技巧,先看看思路:
[3 7 6 4 8 9 2]
| | | | | | |
分割 [3] [7] [6] [4] [8] [9] [2]
/ / / / | | \
合并 [3 7] [4 6] [8 9] [2]
\ / \ /
合并 [3 4 6 7] [2 8 9]
\ /
合并 [2 3 4 6 7 8 9]
简单来说就是先把数组划分为n组,刚开始每组只有一个值。然后相邻的小组不断进行两两合并,最终合并成为一个组。我这里用了奇数个数据,方便理解对于非2的幂次方时数组的处理逻辑。
看下代码:
//nums -- 待排序数组
//length -- 数组长度
void MergeSort(int *nums, int length) {
//i每次乘2,是因为每次合并以后小组元素也变成两倍个了
for (int i = 1; i < length; i *= 2) {
//对拥有2的幂次方个数值的小组进行两两合并
int index = 0;
while (2 * i + index <= length) {
index += 2 * i;
Merge(nums, index - 2 * i, index - i, index);
}
//index+i
上面介绍的数组的归并排序算法,如果想知道如何对链表进行归并排序,点击这里。