前面学习了初等排序,其时间复杂度一般为O(),当面对庞大的输入数据时,这种排序效率就显得十分低下,在这里,我们运用递归与分治的思想,可以实现更加高效的排序算法
今天我学习的是归并排序
归并排序
我们先引入这样一个情景:
给定两个非严格递增数组a与b,我们要将其合并成为一个非严格递增数组c,该如何进行处理呢?
显然,只需要开两个指针分别指向a和b,利用其有序的特点,将两个指针指向的较小的元素先填入数组c,如此循环,直到将a,b的元素全部填完,就可以得到答案了
在上述的排序方法中,时间复杂度为O(n+m),n、m分别为数组a、b的长度
可见,如果我们在给数组排序的时候,能够将其一半一半地分解,再排序成为两个有序的数组,再进行合并,就可以大大减少时间复杂度
这时我们可以发现,这个分解的方法,不正是我们分治思想的体现吗?
分解的两个子数组,又可以继续分解为他们自身的子数组,如此往复,直到数组不可再分
而这个方法的具体实现,就要依靠函数的递归
这样一来,一个长度为N的数组,大概需要经过次的分解
再加上子数组自身需要O(n)的时间复杂度,我们可以得到归并排序的时间复杂度为O(nlogn),这已经是相当高效的!
当然,我们在归并排序的时候,需要临时占用部分内存空间来保存子数组,这是一种牺牲空间换时间的策略
实现代码如下:
//merge:用于将两个有序子数组合并的函数
//nums:需要排列的数组
//temp:临时储存合并排序的数组,长度与nums相同
//left:指向第一个子数组的第一个元素
//mid:指向第一个子数组的最后一个元素
//right:指向第二个子数组的最后一个元素
void merge(int* nums, int* temp, int left, int mid, int right){
int i=left;
int j=mid+1; //j指向第二个子数组的第一个元素,用于遍历第二个子数组
int k=left; //k用来指向temp数组中与第一个子数组的第一个元素相同下标的地方,用于后面对应合并数组
while(i<mid+1 && j<right+1){ //条件防止溢出
if(nums[i]<nums[j]){
temp[k++]=nums[i++]; //第一个子数组的元素较小,则将它填入临时数组,然后k,i分别+1
}
else{
temp[k++]=nums[j++]; //同理第二个数组
}
} //当跳出循环时,两个子数组至少有一个遍历完毕,则我们需要将未遍历完的数组元素全部填入temp
while(i<mid+1){ //第一个子数组未遍历完时:
temp[k++]=nums[i++];
}
while(j<right+1){ //第二个子数组未遍历完
temp[k++]=nums[j++];
}
//此时,我们得到合并完的数组temp
for(int n=left;n<right+1;n++){
nums[n]=temp[n]; //将临时数组temp复制到原数组nums中
}
}
//MergeSort:用于实现归并排序的函数
//nums:同上
//temp:同上
//left:输入数组的左边界(对于一个子数组而言)
//right:输入数组的右边界
void MergeSort(int* nums, int* temp, int left, int right){
if(left<right){//子数组元素个数大于1个时
int mid=left+(right-left)/2; //将其分割,算式是防止int溢出
MergeSort(nums, temp, left, mid);//对分割出的第一个子数组排序
MergeSort(nums, temp, mid+1, right);//对第二个排序
merge(nums, temp, left, mid, right);//合并
}
}
//使用MergeSort时,记得自己开一个temp数组:
int main(){
int nums[10]={2,6,18,5,99,3,77,52,36,10};
int* temp=(int*)malloc(sizeof(int)*10);
MergeSort(nums,temp,0,9);
for(int i=0;i<10;i++){
printf("%d\n",nums[i]);
}
return 0;
}
归并排序很好地体现了分治与递归的思想,而递归往往会占用较多的栈空间,这里我有一个疑问,能否用迭代的方式写出归并排序的实现代码?
由于归并排序需要新开一个空间为n的数组来临时储存排列好的元素,在面对元素较多的数组时,该算法需要占用较多的内存空间。
事实上,在只有排序的目的的前提下,我们通常使用效率更高(或相同)且占用空间更少的快速排序算法,而归并排序的优势并不在此
归并排序的优势在于在分治的情况下,我们使之产生了局部有序的数组,这是一点十分重要的性质,在后面的题目我会对其进行应用,进行更深一步的学习