C语言排序——归并排序
原理
是一种基于分治策略的排序算法,包含“划分”和“合并”阶段。(先递归左子数组,再递归右子数组,最后处理合并。)
- 划分阶段:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题。
- 合并阶段:当子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较 长的有序数组,直至结束。
示例
“2 4 1 0 3 5”
拆分阶段:
- 首先将数组“2 4 1 0 3 5”拆分为两个子数组:“2 4 1”和“0 3 5”。
- 继续拆分“2 4 1”为“2”和“4 1”,再将“4 1”拆分为“4”和“1”。
- 拆分“0 3 5”为“0”和“3 5”,然后将“3 5”拆分为“3”和“5”。
排序与合并阶段:
- 单个元素的子数组“2”、“4”、“1”、“0”、“3”、“5”本身就是有序的。
- 合并“2”和“4”为“2 4”,因为 2 小于 4。
- 合并“1”和前面的“2 4”,首先比较 1 和 2,1 小,结果为“1 2 4”。
- 合并“0”和“3”为“0 3”。
- 合并“5”和前面的“0 3”,先比较 5 和 0,0 小,再比较 5 和 3,3 小,结果为“0 3 5”。
- 最后合并“1 2 4”和“0 3 5”,首先比较 1 和 0,0 小,结果为“0”,接着比较 1 和 3,1 小,结果为“0 1”,再比较 2 和 3,2 小,结果为“0 1 2”,继续比较 4 和 3,3 小,结果为“0 1 2 3”,最后比较 4 和 5,4 小,结果为“0 1 2 3 4 5”。
代码(C)
/* 合并左子数组和右子数组 */ void merge(int *nums, int left, int mid, int right) { // 左子数组区间 [left, mid], 右子数组区间 [mid+1, right] // 创建一个临时数组 tmp ,用于存放合并后的结果 int tmpSize = right - left + 1; int *tmp = (int *)malloc(tmpSize * sizeof(int)); // tmp 数组用于存储合并过程中的临时结果,避免在原数组上直接操作导致数据混乱。 // 初始化左子数组和右子数组的起始索引 int i = left, j = mid + 1, k = 0; // i 指向左子数组的当前元素,j 指向右子数组的当前元素,k 指向临时数组的当前位置。 // 当左右子数组都还有元素时,比较并将较小的元素复制到临时数组中 while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { tmp[k++] = nums[i++]; } else { tmp[k++] = nums[j++]; } } // 这个循环比较左右子数组的元素,将较小的元素放入临时数组。 // 将左子数组和右子数组的剩余元素复制到临时数组中 while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } // 如果左右子数组中有一个已经遍历完,另一个子数组可能还有剩余元素,将这些剩余元素直接复制到临时数组。 // 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 for (k = 0; k < tmpSize; ++k) { nums[left + k] = tmp[k]; } // 将临时数组中的有序元素复制回原数组的对应位置,完成合并。 // 释放内存 free(tmp); // 释放临时数组占用的内存空间。 } /* 归并排序 */ void mergeSort(int *nums, int left, int right) { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 如果子数组只有一个元素或者没有元素,说明已经有序,直接返回。 // 划分阶段 int mid = (left + right) / 2; // 计算中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 将数组不断划分为左右两个子数组,直到子数组长度为 1。 // 合并阶段 merge(nums, left, mid, right); // 当左右子数组都有序后,调用 merge 函数将它们合并为一个有序的数组。 }
复杂度
时间复杂度:
空间复杂度:为