一、对归并算法的优化:
假设有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用法:
#!/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若排序结果有误,则会输出错误的排序结果,否则没有输出。