一、对归并算法的优化:
假设有a和b两个有序序列:
a1,a2,a3,……,an 和 b1,b2,b3,……,bm
1、若an <= b1,直接返回。
代价:一次比较时间。
分析:an <= b1 在归并前期发生的概率不会很大,但是到后期时,归并序列基本有序,an <= b1 发生的概率会大很多,
所以这种优化个人认为值得采用。
2、尽可能缩短a序列和b序列。比如a序列为1,3,5,7,9,11,b序列为4,6,9,13,17,21,40,则a序列中1,3和b序列中的
13,17,21,40可不参与归并。
3、归并前先将a序列拷贝到临时数组中,归并时直接在原数组中进行。
代价:假设2中a序列缩短i,b序列缩短j,则一次归并的代价为2*(n-i)+(m-j)次复制。无改优化的算法代价为
2*(n-i+m-j)。(比较次数相同,不予讨论)
分析:这本是C++STL应对归并算法内存不足时所采用的策略,采用这种策略不仅能提高效率还能降低内存占用。
二、对排序策略的优化:
当归并序列小于某个阈值(C++STL阈值设为20)时,采用插入排序进行排序。
三、对实现方式的优化:
插入排序和归并排序都采用非递归算法,因为非递归算法总是比递归算法效率高,但不是一定,比如快速排序。
C++代码(STL风格)如下:
/*
*macro.h
*定义命名空间qmk
*/
#ifndef MACRO_H
#define MACRO_H
#define BEGIN_NAMESPACE_QMK namespace qmk \
{
#define END_NAMESPACE_QMK }
#endif
/*
*constant.h
*定义插入排序阈值
*/
#ifndef CONSTANT_H
#define CONSTANT_H
#include<cstdint>
#include"macro.h"
BEGIN_NAMESPACE_QMK
uint32_t INSERTIONSORT_THRESHOLD = 20;
//cstdint为C++11头文件,非C++11编译器则使用stdint.h,该文件包含uint32_t的定义
END_NAMESPACE_QMK
#endif
/*
*algorithm_base.h
*实现归并算法
*/
#ifndef ALGORITHM_BASE_H
#define ALGORITHM_BASE_H
#include<cstdint>
#include"constant.h"
#include"macro.h"
BEGIN_NAMESPACE_QMK
template<typename T>
void merge_forward(T *begin,T *middle,T *end)
{
if(begin == middle || middle == end || *(middle-1) <= *middle)
{
return; //优化一(1)
}
while(*begin <= *middle)
{
++begin; //优化一(2)
}
uint32_t buf_len = middle-begin;
T *buffer = new T[buf_len];
for(T *ptr = buffer,*ptr_src = begin ; ptr_src != middle ; ++ptr,++ptr_src)
{
*ptr = *ptr_src; //优化一(3)
}
T *beg_one = buffer,*beg_two = middle,*first_end = buffer+buf_len;
while(beg_one != first_end && beg_two != end)
{
*begin++ = (*beg_one <= *beg_two ? *beg_one++ : *beg_two++);
//优化一(3)。在原数组中进行归并。
}
for( ; beg_one != first_end ; ++begin,++beg_one)
{
*begin = *beg_one;
//优化一(2)。如果序列a先结束,则b序列剩余的元素已位于正确位置,可不参与归并
}
delete[] buffer;
}
END_NAMESPACE_QMK
#endif
/*
*insertion_sort.h
*插入排序的实现(非递归)
*/
#ifndef INSERTION_SORT_H
#define INSERTION_SORT_H
#include"macro.h"
BEGIN_NAMESPACE_QMK
template<typename T>
void insertion_sort_iteration(T *begin,T *end)
{
for(T* ptr_key = begin+1 ; ptr_key < end ; ++ptr_key)
{
T const key = *ptr_key;
T *ptr_scan = ptr_key;
if(*ptr_scan < *begin)
{
for( ; ptr_scan != begin ; --ptr_scan)
{
*ptr_scan = *(ptr_scan-1);
}
}
else //拆分两种情况,减少比较次数(参考C++STL)
{
for( ; *(ptr_scan-1) > key ; --ptr_scan)
{
*ptr_scan = *(ptr_scan-1);
}
}
*ptr_scan = key;
}
}
END_NAMESPACE_QMK
#endif
/*
*merge_sort.h
*归并排序的实现(非递归)
*/
#ifndef MERGE_SORT_H
#define MERGE_SORT_H
#include<cstdint>
#include"algorithm_base.h"
#include"constant.h"
#include"macro.h"
#include"insertion_sort.h"
BEGIN_NAMESPACE_QMK
template<typename T>
void merge_sort_iteration(T* begin,T* end)
{
for(T *ptr_begin = begin,*ptr_end = NULL ; ptr_begin < end ; ptr_begin = ptr_end)
{
ptr_end = ptr_begin + INSERTIONSORT_THRESHOLD;
insertion_sort_iteration(ptr_begin,ptr_end > end ? end : ptr_end);
//归并序列小于阈值时才用插入排序进行排序
}
for(uint32_t length = INSERTIONSORT_THRESHOLD,lst_length = end - begin ;
length < lst_length ; length <<= 1)
{
uint32_t d_length = length << 1;
for(T *ptr_begin = begin,*ptr_end = NULL,*loop_end = end - length ;
ptr_begin < loop_end ; ptr_begin += d_length)
{
ptr_end = ptr_begin + d_length;
merge_forward(ptr_begin,ptr_begin+length,ptr_end > end ? end : ptr_end);
}
}
}
END_NAMESPACE_QMK
#endif
上述优化是本人综合C++STL,java SE和自己的一些见解得出来的四不像归并排序,欢迎拍砖。
测试代码如下:
#include<iostream>
#include<algorithm>
#include<iterator>
#include<string>
#include<sstream>
#include<cstdlib>
#include<cstdint>
#include"../merge_sort.h"
int main(int32_t argc,char **argv)
{
using namespace std;
using namespace qmk;
if(argc<2)
{
cerr<<"No enough parameter."<<endl;
exit(1);
}
int32_t length = argc-1;
int32_t *array = new int32_t[length];
int32_t *array_copy = new int32_t[length];
stringstream *ss = static_cast<stringstream*>(operator new(sizeof(stringstream)));
for(int32_t idx = 1 ; idx < argc ; ++idx)
{
new(ss) stringstream(string(argv[idx]));
*ss>>array[idx-1];
ss->~stringstream();
}
operator delete(ss);
merge_sort_iteration(array,array+length);
copy(array,array+length,ostream_iterator<int32_t>(cout," "));
cout<<endl;
return 0;
}
linux测试脚本如下:
用法:
./Generate.sh <程序名> <程序测试次数> <归并序列长度> <排序结果输出文件>
#!/bin/bash
if [ $# -lt 4 ]
then
echo No enough parameter.
echo "Generate.sh <program> <data count> <args count> <output file>"
exit 1
fi
counter=$2
for ((count=0; count<counter; ++count))
do
argsCounter=$[$RANDOM%$3+1]
args=''
for ((argsCount=0; argsCount<argsCounter ; ++argsCount))
do
args=$args+$RANDOM+' '
done
./$1 $args >>$4
echo >>$4
done
exit 0
用法:
./Judge.sh <比较方式,-lt(小于)或-gt(大于)> <排序结果输入文件>
#!/bin/bash
if [ $# -lt 2 ]
then
echo No enough parameter.
echo "Judge.sh <compare method> <input file>"
exit 1
fi
declare -a LINE
cat $2 | while read -a LINE
do
length=$[${#LINE[@]}-1]
for ((count=0 ; count<length ; ++count))
do
if [ ${LINE[$[$count+1]]} $1 ${LINE[$count]} ]
then
echo ${LINE[@]}
break
fi
done
done
exit 0
若排序结果有误,则会输出错误的排序结果,否则没有输出。