1. 程式人生 > >《程式設計珠璣》程式碼之路16:直觀感受為何程式設計師需要學習經典演算法

《程式設計珠璣》程式碼之路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函式庫都要快的排序演算法。

並不是什麼時候,都有庫可調,更不是庫永遠都是最好的,不僅僅會用庫,還要超越庫,才是一個優秀的程式設計師該做的。