插入排序

    插入排序是排序入门最基础的算法之一,其原理也是简单粗暴的:为第I个元素选择正确的位置,前提是保证前(i-1)个元素已经排序完成,元素i遍历它之前的(i-1)元素,并插入到正确的位置中去,这就是“插入排序”的名字由来。关于插入排序各类算法和数据结构的书籍都会有详细描述,这里不再熬述。废话少说,放码过来。

#include <iostream>
#include <windows.h>
using namespace std;

const int NUM = 20000;

double insertSort1() {

	LARGE_INTEGER freq;
	LARGE_INTEGER st, et;

	int *arr = new int[NUM];
	for(int i = 0; i < NUM; i++) {
		arr[i] = NUM - i;
	}

	QueryPerformanceFrequency(&freq);
	QueryPerformanceCounter(&st);
	for(int i = 1; i < NUM; i++) {
		int tmp = arr[i];	//记住当前arr[i]的值
		int k = i;	//用来标记arr[i]要插入的位置
		for(int j = i - 1; j >= 0; j--){
			if(arr[j] > tmp) {	//当元素arr[j]比arr[i]的值大,则需将arr[j]向下标增大的方向移动,腾出arr[j]的位置
				arr[j+1] = arr[j];
				k = j;	//用k保存arr[j]腾出来的位置
			}
		}
		arr[k] = tmp;	//将tmp的值插入正确的位置
	}
	QueryPerformanceCounter(&et);

	double res = 1000.0*(et.QuadPart - st.QuadPart)/freq.QuadPart;
	return res;
}

double insertSort2() {
	LARGE_INTEGER freq;
	LARGE_INTEGER st, et;

	int *arr = new int[NUM];
	for(int i = 0; i < NUM; i++) {
		arr[i] = NUM - i;
	}

	QueryPerformanceFrequency(&freq);
	QueryPerformanceCounter(&st);
	for(int i = 1; i < NUM; i++) {
		int tmp = arr[i];
		int j;
		for(j = i - 1; j >= 0 && arr[j] > tmp; j--){
				arr[j+1] = arr[j];
		}
		arr[j+1] = tmp;
	}
	QueryPerformanceCounter(&et);

	double res = 1000.0*(et.QuadPart - st.QuadPart)/freq.QuadPart;
	return res;
}

int main()
{
	int pass = 10;
	double sum1 = 0;
	double sum2 = 0;

	for(int i = 0; i < pass; i++) {
		sum1 += insertSort1();
		sum2 += insertSort2();
		
	}
	cout<<"sort1 aveTime: "<<sum1/10<<endl;
	cout<<"sort2 aveTime: "<<sum2/10<<endl;
}
  上面代码中,insertSort1和insertSort2都是插入排序(升序)的实现,insertSort2是稍微优化过的版本。在insertSort1中,关键部分的代码已经写上注释,大概流程是:

    1)初始化大小为NUM的整型数组,倒序排放。NUM大小可根据自己的需求修改,一般来说(MIT公开课里的教授说的啊),NUM的数量在30以下,插排的性能就非常优秀,这也是为什么shell sort排序到一定程度后可以采用插排来整理的原因,但NUM大于30的数量性能瓶颈就开始明显了。

    2)使用插入排序算法排序

    3)计算执行排序的代码所执行的时间

    之所以选择数组倒序是因为这是插入排序效率最差的情况,相当于每为一个元素找到正确位置(也即是数组首元素),你就必须比较和移动当前元素之前的每一个元素,复杂度为O(n^2),这样避免了测试insertSort1和insertSort2两个版本的插排算法是由于随机数组的不同而产生的差距。在main函数中,将每个算法运行10遍,求平均值。测试的过程中,我修改过NUM的数量级,30,100,1000,5000,10000,20000,insertSort2的性能都是要优于insertSort1的,图1为多次运行程序后某一次的测试结果截图,没有意外,每次都是2的耗时低于1。


图1

    原因:在算法1中,为每个arr[i]找到正确位置j之后,没有任何跳出循环的操作,会继续将arr[i]跟j位置以前的元素做比较,然而正确的位置已找到,并由于数组i之前的元素已经排好序,故正确位置不会再改变,也就是说这些比较都是无用功,可避免的。在算法2中,将这个比较的条件放在循环的判断条件里,一旦找到正确位置之后,判断条件不成立而退出循环体,故不会再执行后面那些无用的比较。但是,这个原因在上述代码中应该不存在,因为我采用的是倒序数组,无论如何对每一个元素i,由于正确的位置总是在数组首元素位置,因此一定是做(i-1)次比较和数据赋值。因此,操作这两者的原因我猜想是在算法1中每次循环比算法2多执行了一行“k = j”的用来保存正确位置的代码,造成了耗时多于算法2。无论如何,算法2都要比算法1更优一些。

    附:在C++中,用来测试某些代码执行所耗费的时间,可以采用上述代码中采用的QPC(QueryPerformanceCounter),QPC的用法可以查看MSDN或者这里有示例。在C++中计时还可以使用<time.h>中定义的clock()和GetTickCount(),这两者的计时是基于Timer的,缺点是精度不足,我在测试上述两个算法的差别是,在NUM为1000以下都显示为0毫秒;而QPC是基于Counter的,但也有自己的缺点,并且在MSDN也有官方对其产生的bug进行修补的方法。这篇文章及其补充有对这几种计时方法进行介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值