归并排序,C语言实现

归并排序是一种不依赖初级版本的高级排序算法,具有稳定性且时间复杂度始终为O(N * logN)。不同于堆排序,归并排序在内存充足时表现优秀。本文介绍了归并排序的迭代实现,探讨了其空间复杂度,并提出了两种优化方案:小规模数据用插入排序和减少复制次数。虽然C语言中动态数组大小限制导致指针使用,但在高级语言中这不是问题。
摘要由CSDN通过智能技术生成

前面提到三种简单排序和三种高级排序之间的关系:
冒泡排序 (初级) ----> 快速排序 (高级)
选择排序 (初级) ----> 堆排序 (高级)
插入排序 (初级) ----> 希尔排序 (高级)
但是归并排序不在其中,它是一种很另类的排序方法。它的另类表现在:
1、它没有初级版本,只有高级形式。
2、另外三种高级排序都是不稳定的排序方式,但归并排序是稳定的。
3、归并排序跟堆排序一样,时间复杂度永远都是O(N * logN),效率非常稳定。但是堆排序有准备阶段,而归并排序没有,所以归并排序比堆排序快,它的速度仅次于快速排序。
4、归并排序比较占内存,空间复杂度为O(N),比其它所有排序算法都要高。正所谓想要马儿长得好,一定要给马儿吃得饱。当设备有足够多的内存,且要求排序结果稳定的情况下,归并排序是一种不错的选择。

归并排序的思路:把两个有一个数的数组拼接成一个有两个元素的有序数组;然后把两个有两个元素的有序数组拼接成一个有四个元素的有序数组;再把两个有四个元素的有序数组拼接成一个有八个元素的有序数组。。。。。。最终得到一个完整的数组。

没有插图。。。。

归并排序有递归和迭代等两种实现方式。这里只实现迭代方式。除了专门介绍指针的章节,在本书大部分内容都避免使用指针,但是归并排序的代码却无法避免使用指针。原因是在申请数组时,C语言不支持用变量作为数组的大小。如果想动态申请数组,只能手动向操作系统申请空间。但这个问题在高级语言 (包括C++、Java、C#、Python、PHP等) 中不是问题,因为这些高级语言中都可以使用变量作为数组的大小。

归并排序的代码如下:

#include <stdio.h> 
#include <stdlib.h>

int arr[] = {4, 1, 5, 7, 3, 2, 9, 8, 6, 0, 5, 4, 3, 6, 7, 8};
int size = 0;

//归并:将arr数组中from ~ middle 和 middle ~ to的两个子数组进行有序合并
void combine(int from, int middle, int to)
{
    int i, j, k, size1;
    int * p; //指针,指向整个数组
    int * temp; //指针,指向临时空间

    i = from;
    j = middle;
    k = 0;
    size1 = to - from + 1;
    p = arr;

    //向操作系统申请临时空间
    temp = (int *)(malloc(size1 * sizeof(int)));

    //有序合并:将数组中的内容复制到临时数组中
    while (i < middle && j <= to)
    {
		//根据大小排序
		if (p[i] < p[j])
		{
		    temp[k] = p[i];
		    k++;
		    i++;
		}
		else //if(p[i] > p[j])
		{
		    temp[k] = p[j];
		    k++;
		    j++;
		}
    }

    //前一个子数组的内容没有完全复制到临时数组中,则继续复制
    while (i < middle)
    {
		temp[k++] = p[i];
		i++;
    }

    //后一个子数组的内容没有完全复制到临时数组中,则继续复制
    while (j <= to)
    {
		temp[k++] = p[j];
		j++;
    }

    //将临时数组中的内容重新交还给arr
    for (k = 0, i = from; k < size1; k++)
    {
		p[i] = temp[k];
		i++;
	}
	free(temp); //释放指针,回收内存
}

//迭代的归并排序
void combineSortFor()
{
    int step; //step是步长,取值范围是1, 2, 4, 8。。。

    //两个子数组,第一个子数组的范围是left1和right1
    //第二个子数组的范围是left2和right2
    int left1, right1, left2, right2;
    int size = sizeof(arr) / sizeof(int);

    for (step = 1; step < size; step *= 2) //这个循环产生步长,一共循环logN次
    {
		//这个循环是为了进行单层的归并排序
		for (left1 = 0; left1 + step < size; left1 = right2)
		{
		    //确定范围
		    right1 = left1 + step;
		    left2 = right1;
		    right2 = left2 + step;
		
		    //确定范围:右侧不能超出长度
		    if (right2 > size)
		    {
				right2 = size;
		    }
		    combine(left1, right1, right2 - 1);
		}
    }
    return;
}

int main()
{
	size = sizeof(arr) / sizeof(int);
	combineSortFor();
	return 0;
}

归并排序也有优化方案:
1、跟快速排序一样,当数据规模较小时,可以用插入排序。由于插入排序是稳定的排序方法,所以这种优化方式不影响归并排序的稳定性。
2、上述代码是将排序好的结果从临时数组复制到原来的数组中。其实可以不用复制。在下一轮排序中直接从临时数组排序到原来的数组即可。如果最后排序完成的数据是在临时数组中,再把这些结果复制到原来的数组中。这样可以减少logN次复制,可以大大提高排序效率。上述两种优化方案就请读者自己完成吧。

上述7种排序算法的时间复杂度都在O(N * logN) ~ O(N * N) 之间。那么是不是还能找到更快的排序算法呢?已经证明:基于两个数的比较的排序算法的时间复杂度最低是O(N * logN)。也就是说,即使有更快的算法出现,新算法也不可能比快速排序等有质的飞跃。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值