归并排序要额外多开一个数组用于存放归并后的数据,然后再把排序好的数据放回原数组。
归并排序使用递归的写法,大致分位两个步骤,一个是分解,一个是归并。而归并的数据必须是有序的才行,那么如何保证其最开始是有序的呢?
其实这里进入递归到了最底层,其数据被分为单一个数为一个区间的时候,这里就认为其是有序的。
分解会把数据分解为两个区间,然后做比较,如果是升序的话,一个个比较,把较小的放到临时数组。
此时此段区间就变为有序了,返回上一层这段区间就又可以用来被归并。

简单讲解归并流程,假设这里数组已经被分解为了两个有序的区间:
这里就要依次进行比较然后把较小的数据放到临时数组。把数据放下去以后,i 往前走一步,然后再进行比较。

这里2<4,把2放到下面的数组,然后 j 往前走一位。

依次走到最后数组数据就排序好了,但是这里要注意一个情况,这种比较的方法固定要有一个区间先走完,这里左区间先走完了,但是右区间还剩一个数,也没办法比较了。
所以为了防止这个情况,在归并结束后,如果哪个区间还有数据,就直接加到数组后面就可以,因为排序的数组是有序的,所以直接加到数组后面不会出错。


代码:
- void _MergeSort(int* a, int begin, int end, int* temp)
- {
-
- if (begin >= end) //至少要有两个区间
- return;
-
- int mid = (begin + end) / 2;
-
- //分治 [beging,mid] [mid+1,end]
- _MergeSort(a ,begin,mid,temp);
- _MergeSort(a, mid+1, end, temp);
-
- int begin1 = begin,end1 = mid;
- int begin2 = mid + 1,end2 = end;
-
- int i = begin1; //这里注意是beging1
-
- //归并 把小的放到temp数组中去
- while (begin1 <= end1 && begin2 <= end2) {
-
- if (a[begin1]<a[begin2]) {
-
- temp[i++] = a[begin1++];
- }
- else {
- temp[i++] = a[begin2++];
- }
-
- }
-
- //如果一个数组走完了,还剩有数据,全部放到数组后面去
- while (begin1<=end1) {
- temp[i++] = a[begin1++];
- }
-
- while (begin2<=end2) {
- temp[i++] = a[begin2++];
- }
-
- //把数据拷贝回原数组
- memcpy(a + begin, temp + begin,(end-begin+1)*sizeof(int));
- }
-
-
- //递归实现
- void MergeSort(int *a,int n)
- {
- int* temp = (int*)calloc(n, sizeof(int));
-
- if (temp==NULL){
- perror("tmp初始化失败\n");
- return;
- }
-
- _MergeSort(a, 0, n-1, temp);
- free(temp);
- }
测试:

归并排序的时间复杂度是典型的:O(N*logN );
归并的非递归直接可以在数组中处理。
其思路是把数组中的数进行分组,设gap个数为一组,每组数又有两个区间,这两个区间做比较,归并成一个有序的区间。有序的区间又可以分组,然后归并。

gap=1,每单个一个数分为一组,归并后刚好变为gap=2,两个数的组。依次归并,最后整个数组就有序了。
- //归并排序的非递归
- void MergeSortNonR(int* a, int n)
- {
-
- int* temp = (int*)calloc(n, sizeof(int));
- if (temp == NULL) {
- perror("tmp初始化失败\n");
- return;
- }
-
- int gap = 1;
-
- while (gap < n) {
-
- for (int i = 0; i < n; i += 2*gap) { //注意这里是一次跳两组
-
- //[i][i+gap-1] [i+gap][i+2*gap-1]
- int begin1 = i, end1 = i + gap - 1;
- int begin2 = i + gap, end2 = i + 2 * gap - 1;
-
-
- int j = begin1;
- while (begin1 <= end1 && begin2 <= end2)
- {
- if (a[begin1] < a[begin2]) {
-
- temp[j++] = a[begin1++];
- }
- else {
- temp[j++] = a[begin2++];
- }
- }
-
- while (begin1 <= end1)
- {
- temp[j++] = a[begin1++];
- }
-
- while (begin2 <= end2) {
-
- temp[j++] = a[begin2++];
-
- }
-
- }
-
- //这里每个数据都归并了所以可以全部拷贝回去。
- memcpy(a, temp, sizeof(int) * n);
-
- gap *= 2;
-
- }
-
- free(temp);
- }
思路比较好理解,但是要注意的是边界的控制。如果数组的长度不为2的次方倍数,那么不控制边界会造成越界。
比如一个数组长度等于9,调试的时候这里打印一下区间。
这里就会发现,有点地方的区间大于了数组的长度(数组下标是0-8,9也越界了)。除了begin1不会越界以外,其他的边界都会越界。
所以这里为了防止边界越界,要控制一下边界。
-
- //归并排序的非递归
- void MergeSortNonR(int* a, int n)
- {
-
- int* temp = (int*)calloc(n, sizeof(int));
- if (temp == NULL) {
- perror("tmp初始化失败\n");
- return;
- }
-
- int gap = 1;
-
- while (gap < n) {
-
- printf("gap=%d-->",gap);
- for (int i = 0; i < n; i += 2*gap) { //注意这里是一次跳两组
-
- //[i][i+gap-1] [i+gap][i+2*gap-1]
- int begin1 = i, end1 = i + gap - 1;
- int begin2 = i + gap, end2 = i + 2 * gap - 1;
-
- //修正边界
- if (end1 >= n) { //end1越界
-
- end1 = n - 1;
- begin2 = n;
- end2 = n - 1;
- }
- else if (begin2 >= n) { //begin2越界
-
- begin2 = n;
- end2 = n - 1;
- }
- else if (end2 >= n) { //end2越界
-
- end2 = n - 1;
- }
-
- //打印边界:
- printf("--[%d][%d],[%d][%d]",begin1,end1,begin2,end2);
- int j = begin1;
- while (begin1 <= end1 && begin2 <= end2)
- {
- if (a[begin1] < a[begin2]) {
-
- temp[j++] = a[begin1++];
- }
- else {
- temp[j++] = a[begin2++];
- }
- }
-
- while (begin1 <= end1)
- {
- temp[j++] = a[begin1++];
- }
-
- while (begin2 <= end2) {
-
- temp[j++] = a[begin2++];
-
- }
-
- }
- //这里每个数据都归并了所以可以全部拷贝回去。
- memcpy(a, temp, sizeof(int) * n);
- printf("\n");
- gap *= 2;
-
- }
-
- free(temp);
- }
PS:越界后设 begin2 < end2 ,这样越界的右区间就不会进入归并的步骤了。
测试:

还有一种思路,如果区间发生数组越界,那么就不进行归并了。
-
- //归并排序的非递归 第二个控制边界的办法
- void MergeSortNonR_Test(int* a, int n)
- {
-
- int* temp = (int*)calloc(n, sizeof(int));
- if (temp == NULL) {
- perror("tmp初始化失败\n");
- return;
- }
-
- int gap = 1;
-
- while (gap < n) {
-
-
- printf("gap=%d-->", gap);
- for (int i = 0; i < n; i += 2 * gap) { //注意这里是一次跳两组
-
- //[i][i+gap-1] [i+gap][i+2*gap-1]
- int begin1 = i, end1 = i + gap - 1;
- int begin2 = i + gap, end2 = i + 2 * gap - 1;
-
-
- //如果end1、begin2越界就不归了
- if (end1 >= n || begin2 >= n) {
-
- break;
- }
- else if (end2 >= n) { //end2越界还是要归并
-
- end2 = n - 1;
- }
-
-
- int m = end2 - begin1 + 1;
-
- //打印边界:
- printf("--[%d][%d],[%d][%d]", begin1, end1, begin2, end2);
-
-
- int j = begin1;
- while (begin1 <= end1 && begin2 <= end2)
- {
- if (a[begin1] < a[begin2]) {
-
- temp[j++] = a[begin1++];
- }
- else {
- temp[j++] = a[begin2++];
- }
- }
-
- while (begin1 <= end1)
- {
- temp[j++] = a[begin1++];
- }
-
- while (begin2 <= end2) {
-
- temp[j++] = a[begin2++];
-
- }
- //注意如果不归并了,这里不能把数据拷贝回去,不然会把未初始化的值拷贝回去,所以只能拷贝已经归并了的数据。
- memcpy(a + i, temp + i, sizeof(int) * m);
- }
-
-
- printf("\n");
- gap *= 2;
-
- }
-
- free(temp);
- }
PS:如果是区间越界就跳过归并的话,这里要注意就不能把全部的数据都拷贝回原来的数组,因为没发生归并,这里临时数组里面是未初始化的值,拷贝回去会造成覆盖。所以这里要注意,这里拷贝的话只能拷贝已经归并了的数据。
计次排序也要额外申请一个数组,其数组大小是原数组最大元素和最小元素的差值。其存的数据是原数组里面数据出现的次数。
然后按照出现的次数把数据写回原来的数组。此排序是个非比较排序。

代码:
- //计次循环
- void CountSort(int *a,int n)
- {
- //选出最大最小的数,计算差值
- int max=a[0], min=a[0];
-
- for (int i = 0; i < n;i++) {
- if (a[i]>max) {
- max = a[i];
- }
- if (a[i]<min) {
- min = a[i];
- }
- }
-
- int rang = max - min+1;
-
- int* count = (int*)calloc(rang,sizeof(int));
- if (count==NULL) {
- perror("count为NULL");
- exit(-1);
- }
-
- for (int i = 0; i < n;i++) {
-
- //记录出现数据的次数
- count[a[i] - min]++;
- }
-
- //放回原数组
- int k = 0;
- for (int j = 0; j < rang;j++) {
-
- while (count[j]--) {
- a[k++] = j + min;
- }
-
- }
-
-
- free(count);
- }
测试:

局限性:
1.浮点数、字符串不能排序。
2.数据范围大,空间复杂度会很高。
此排序的性质就决定了它只能排序范围比较小,重复度较高的数据。如果数据范围交到,其时间和空间的消耗都比较多。
时间复杂度: O( max(rang,N) )
空间复杂度: O(rang) //最小值和最大值的差值越大约消耗空间