《编程珠玑》代码之路16:直观感受为何程序员需要学习经典算法

作为一名老年ACM菜鸡,经常见到周围除了调库啥也不会的程序员,还经常一脸正经的说:“程序员就是把人家写好的东西拿出来调用一下,顶多改改嘛。”。emmmm,在这个猫猫狗狗都能养活自己的年代,这么想确实没问题 ---- 如果你确定自己不用面临被淘汰的风险,或者在别的领域能首屈一指。

大家应该都了解插入排序和快速排序。

快速排序在后期其实是由一块一块接近有序的小块组成的数组,递归下去计算小块的成本其实挺大,如果在某个条件下停下来,就会得到一个接近有序的数组。此时,如果用人鬼都觉得没用的插入排序对整个中间数组排序,整体的效率要比快排要高。因为插入排序虽然是平方级算法,但它在数组接近有序时,性能线性的。

能发现递归效率低下时停止递归过程,看见人鬼都看不上的插入排序,提升整个算法的效率,这也许就是艺术了吧。


生活不止眼前的苟且,还有诗和远方。

远方和诗,不是调库就能感受到的,就像很多人识字,却感受不到诗歌的美。

有人编程只用脑,懂得编程之美的人,还知道要用心。

旅行如此,编程如此,恋爱如此,生活亦如此。


好了言归正传:

插入排序就是每次拿到一个数,给它插到合适的位置,emmmm,没毛病。

先看一段插入排序的代码:

for (int i = 0; i < n; ++i){
    int t = array[i];
    for (int j = i; j > 0 && array[j - 1] > t; --j){
        array[j] = array[j - 1];
    }
    array[j] = t;
}

至于快速排序大家也许都比较熟悉,就每次找一个支点,把数组调整为支点左边的都小于支点的值,右边的都大于等于支点的值,然后用分治的思想,把比当前支点小的部分和比当前支点大的部分分而治之。

对于如何把一个数组分成大于支点和小于支点的两部分,其实有很多方法,其中一个是,从右半部分找一个小于支点值的,然后从左半部分找一个大于支点值的,交换他们,不断重复这个过程。

下面实现的算法,实际已经要比直接调用C函数库快3-4倍。

void qSort(int left, int right){
	if (left >= right){
		return;
	}

	int t = array[left], i = left, j = right + 1;
	while(1){
		do{
			i++;
		}while(i <= right && array[i] < t);

		do{
			j--;
		}while(array[j] > t);

		if (i > j) break;
		swap(array[i], array[j]);
	}

	qSort(left, j - 1);
	qSort(j + 1, right);
}

 然而,简单的把快速排序和插入排序结合,还能继续让整个排序快近30%!!!

可以想象快速排序的递归树,最下面的几层,其实都是在用很大的开销算几个数字。。。

那么在某个时间段,比如rihgt - left <= cutoff 时,停止快排过程,然后调用插入排序。

cutoff的值因机器不尽相同,可以用不同的值在自己的电脑上测试,50左右是个不错的选择。

在快排上加个返回条件,应该没啥难度吧。。

所以这个排序函数,变成了这个样子:

sort(){

    qsort();

    insertSort();

}

在调用qsort后,紧跟着调用insertSort。

这样一来,我们就得到了一个,比书上和C函数库都要快的排序算法。

并不是什么时候,都有库可调,更不是库永远都是最好的,不仅仅会用库,还要超越库,才是一个优秀的程序员该做的。

猜你喜欢

转载自blog.csdn.net/beijixiong5622/article/details/84838754
今日推荐