归并排序的优化

一、对归并算法的优化:


假设有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
若排序结果有误,则会输出错误的排序结果,否则没有输出。

转载于:https://my.oschina.net/u/616237/blog/96789

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值