昨晚看《演算法導論》,其中有一個舉例是插入排序。上午我用C語言實現了該演算法。本來以為自己理解了昨晚所看的演算法,誰知實現的時候還是出現了些問題。我們在學習時會遇到這樣的現象:你以為自己會了,潛意識裡認為只要懂了原理花些時間就可以搞定。所以我們看書時大多隻關注原理。這樣本身也沒有什麼不好,因為人的大腦有限,每天思考也不能過於長,否則容易分神。而記住原理忽略細節可以是大腦的一種自我保護機制。同時這也是一個陷阱,我們時常以為自己理解了卻沒有驗證這種理解,這種理解往往在運用時會出現一些問題,顯得不太可靠。
第一次實現
經過思考,我用C語言實現了插入排序:
#include "stdafx.h"#define SIZE 7void PrintNewLine();void PrintArray(int arr[]);void InsertionSort(int arr[]);int _tmain(int argc, _TCHAR* argv[]){int original[] = {2,5,3,1,4,7,6};PrintArray(original);PrintNewLine();InsertionSort(original);PrintNewLine();PrintArray(original);char wait = getchar();return 0;}void PrintArray(int arr[]){for(int i = 0; i < SIZE; i++)printf("%d ",arr[i]);printf("\n");}void PrintNewLine(){printf("\n");}void InsertionSort(int arr[]){for(int i=1; i < SIZE; i++){int key = arr[i];while(key < arr[i-1] && i > 0) //i>0 role as Termination{//Exchage key and element which postion at key-1arr[i] = arr[i-1];arr[i-1] = key;//Due to key advanced, so key's previous element will be compared next.i--;}PrintArray(arr);}}
輸出如下:
2 5 3 1 4 7 62 5 3 1 4 7 62 3 5 1 4 7 62 3 5 1 4 7 61 2 3 5 4 7 61 2 3 5 4 7 61 2 3 5 4 7 61 2 3 5 4 7 61 2 3 4 5 7 61 2 3 4 5 7 61 2 3 4 5 7 61 2 3 4 5 6 71 2 3 4 5 6 71 2 3 4 5 6 7
問題
通過每次迭代的結果可知,上面的InsertionSort方法中有一個缺陷。演算法的迭代次數遠遠超出了預計。導致這個原因的是:我們在內層while迴圈中對迭代子i的操作,影響到外層的for迴圈中的i取值。這樣每次迭代外層迴圈都是從下標1開始。而從下標1開始會重複迭代那些已經排序好的元素。
其實簡單些講,這個是常見編程陷阱即:命名衝突(Naming Conflict)
第二次實現
要解決這個問題,並不是很難,我們引入一個內層while迴圈專用的迭代子j,代碼如下:
void InsertionSort(int arr[]){for(int i=1; i < SIZE; i++){int key = arr[i];int j = i;while(key < arr[j-1] && j > 0) //i>0 role as Termination{//Exchage key and element which postion at key-1arr[j] = arr[j-1];arr[j-1] = key;//Due to key advanced, so key's previous element will be compared next.j--;}PrintArray(arr);}}
輸出如下:
2 5 3 1 4 7 62 5 3 1 4 7 62 3 5 1 4 7 61 2 3 5 4 7 61 2 3 4 5 7 61 2 3 4 5 7 61 2 3 4 5 6 71 2 3 4 5 6 7
問題
由輸出我們可以看見,命名衝突問題已經解決。我們外層迴圈一共執行了n-1次(n為需排序元素個數,此處為7)。但這個實現跟演算法導論上的實現還是有一些不同。我的實現將每次待排序的元素Key跟Key前面的元素進行交換。而演算法導論上的實現更加嚴謹:先移動元素,待元素移動好了,再將Key插入適當的位置。
每次都移動Key並不是必要的,必要的移動只是最後一步,即:將Key移動到適當的位置。
這個在實現上的區別就是將一條語句從內層迴圈移動至外層迴圈。
第三次實現
void InsertionSort(int arr[]){for(int i=1; i < SIZE; i++){int key = arr[i];int j = i;while(key < arr[j-1] && j > 0) //i>0 role as Termination{//move key to previous positionarr[j] = arr[j-1];j--;}arr[j] = key; /*將key的賦值從內層迴圈移動至外層迴圈*/PrintArray(arr);}}