一:归并排序
普通的归并排序递归实现比较简单,就不在此赘述,一看就懂。下面贴上代码。
#include <iostream>
#include <assert.h>
using namespace std;
template <typename T>
void merge(T arr[], const int start, const int middle, const int end, const int size)
{
T *room = new T[size*2]; //归并排序虽然是O(nlgn)时间,但是需要者O(n)的空间
assert(room != NULL);
int left = start;
int right = middle+1;
int i = 0;
while(left <= middle && right <= end){ //按值由小到达copy进room数组中
if(arr[left] < arr[right])
room[i++] = arr[left++];
else
room[i++] = arr[right++];
}
while(left <= middle) //程序退出循环只有两种可能,分别处理
room[i++] = arr[left++];
while(right <= end)
room[i++] = arr[right++];
for(int i=start,j=0; i<=end; ++i,++j) //将排序完成的一串数copy回原数组
arr[i] = room[j];
delete []room;
}
template <typename T>
void merge_sort(T arr[], int start, int end) //归并排序主函数,start和end分别为起始和终止的下标
{
if(start < end){ //如果start=end直接返回,因为单个元素天然有序
int middle = (int)((start+end)/2); //求中点
merge_sort(arr, start, middle); //归并左半部分
merge_sort(arr, middle+1, end); //归并右半部份
merge(arr, start, middle, end, end-start+1); //合并两部分
}
}
template <typename T>
void print(T arr[], const int size) //打印函数
{
for(int i=0; i<size; ++i)
cout<<arr[i]<<' ';
cout<<endl;
}
int main()
{
int array[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
int len = sizeof(array)/sizeof(int);
merge_sort(array, 0, len-1);
print(array, len);
return 0;
}
二:自然合并排序
这个排序是归并排序的变体,基于非递归实现。它是由小到大合并排序完成的子数组完成排序的。
我们可以首先将数组a中相邻元素两两配对。用合并算法将它们排序,构成n/2组成都为2的排好序的子数组段,然后再将它们排序成长度为4的排好序的字数组段,如此继续下去,直至整个数组排好序。
注意有两种特殊情况:
调用归并操作将相邻的一对子数组进行归并时,有些情况需要对最后一组进行特殊处理:
① 若最后子数组个数小于等于步长s,则最后一个子数组无须和其它子数组归并(即本趟轮空),直接拷贝
① 若最后子数组个数小于等于步长s,则最后一个子数组无须和其它子数组归并(即本趟轮空),直接拷贝
② 若子文件个数为偶数,则要注意最后一对子文件中后一子文件的区间上界是n。
例如: 数组 : 5 4 3 2 1 1 2
第一次2个一组归并: 4 5 2 3 11 2 //第一步2个一组合并,s为1,最后一组只有2一个元素,直接轮空。也可以理解为子数组个数为奇数个,直接轮空
下一次4个一组归并 : 2 3 4 5 11 2 //此时s=2, 11 2的长度为3,大于s,但是小于2s,那么将11和2进行归并即可。
这两种情况代码体现在这里:
if(i+s < n)
merge(x, y, i, i+s-1, n-1);
else{
for(int j=i; j<n; ++j){
y[j] = x[j];
}
}
其实,用通俗的话来说,就是前面的合并完了剩下的不足一个就直接轮空,够一点几个就把那一个和另外半个合并就行了!
全部代码如下:
#include <iostream>
#include <assert.h>
using namespace std;
template <typename T>
void merge(T x[], T y[], int start, int centre, int end)
{
int left = start;
int middle = centre;
int right = middle + 1;
while(left <= centre && right <= end){
if(x[left] < x[right])
y[start++] = x[left++]; //这部分就是简单的比较拷贝,只是和上面归并排序程序不同的是
else //这部分是从start开始copy的,之前的归并排序每次从0开始copy
y[start++] = x[right++];
}
while(left <= middle)
y[start++] = x[left++];
while(right <= end)
y[start++] = x[right++];
}
template <typename T>
void merge_pass(T x[], T y[], int s, int n)
{
int i = 0;
while(i <= n-2*s){ //当i和n相差2s,说明存在可合并的子数组 //举例两元素数组a[] = {1, 2},i=0,同时s=1时,n-2*s=0此时等于i,可进行合并
merge(x, y, i, i+s-1, i+2*s-1); //采用传递起始,中间,终点下标形式,需要-1,例如上面的数组,s=1时终点下标0+2*1-1=1
i += 2*s; //每次按步长走
}
if(i+s < n) //如果前面子数组合并完成,还剩一组子数组,例如5,4,3,2,1,1,2步长为2时,54和32合并,剩余11大小满足子数组,就合并它和2
merge(x, y, i, i+s-1, n-1);
else{ //如果剩余元素为奇数且不满一组子数组时,直接copy,如5,4,3,2,1,两两合并,剩1,此时i=4,n=5,s=1,则i+s=n,直接拷贝
for(int j=i; j<n; ++j){
y[j] = x[j]; //从i开始直接copy
}
}
}
template <typename T>
void merge_sort(T arr[], const int size) //归并排序函数
{
T *extra = new T[size]; //申请额外空间
assert(extra != NULL);
int step = 1; //步长初值为1
while(step < size){
merge_pass(arr, extra, step, size); //将arr数组copy至extra数组,底层实现具体排序
step <<= 1; //步长翻倍
merge_pass(extra, arr, step, size); //再次copy回arr数组
step <<= 1; //继续翻倍,相当于每次拷来拷去的过程同时进行归并排序
}
}
template <typename T>
void print(T *arr, const int size)
{
for(int i=0; i<size; ++i)
cout<<arr[i]<<' ';
cout<<endl;
}
int main()
{
int array[] = {10, 9, 30, 8, 7, 5, 4, 3, 2, 1};
int len = sizeof(array)/sizeof(int);
merge_sort(array, len);
print(array, len);
return 0;
}
*以上代码均经过验证