排序算法學習整理三(插入)
三、插入排序:
插入排序是一種非常簡單的排序,它的實現難度低於冒泡和選擇。(我第一個for循環寫出的排序就是插入排序)插入排序類似整理撲克牌,將每一張牌插到其他已經有序的牌中適當的位置。
基本思想:
插入排序由N-1趟排序組成,對於P=1到N-1趟,插入排序保證從位置0到位置P上的元素為已排序狀態。
簡單的說,就是插入排序總共需要排序N-1趟,從Index為1開始,講該位置上的元素與之前的元素比較,放入合適的位置,這樣循環下來之後,即為有序數組。
代碼實現
1 void insertionSort(int *array, int len) 2 { 3 forinsertionSort(int i = 1; i < len; i++) 4 { //即使i從開始0開始,也不會進入第二個循環。 5 for (int j = 0; j < i; j++) 6 { /*從0~i都是已經排好序的,將array[i]與array[0]~array[i-1]一一進行比較, 7 找到插入點*/ 8 if (array[i] < array[j]) 9 { //找到大於array[i]的元素就立即交換(這裏存在優化的可能) 10 array[i] = array[i] ^ array[j];11 array[j] = array[i] ^ array[j]; 12 array[i] = array[i] ^ array[j]; //交換 13 } 14 } 15 } 16 }
代碼的問題很明顯,就像選擇排序為什麽會比冒泡快一樣,我們可以減少交換次數來優化代碼。
但是不交換怎麽排序呢?用賦值語句進行覆蓋實現,
其核心的代碼如下:
for(j = i; j > 0 && temp < array[j-1]; j--) { array[j] = array[j-1]; }
array[j] = temp;
打個比方:
原數組元素為 : 7 1 2 3 第一輪: 1 < 7;執行array[j] = array[j-1] 1 1 2 3; 再執行array[j] = temp; 1 7 2 3; 第二輪: 2 < 7;執行array[j] = array[j-1] 1 2 2 3 再執行array[j] = temp; 1 2 7 3 2 !< 1;退出循環 第三輪: 3 < 7;執行array[j] = array[j-1] 1 2 3 3 再執行array[j] = temp; 1 2 3 7 3 !< 2;退出循環;
代碼實現:
1 void insertionSort(int *array, int len) 2 { 3 int i, j; 4 int temp = 0; 5 for (i = 1; i < len; i++) 6 { 7 temp = array[i]; //記錄需要插入的元素 8 for(j = i; j > 0 && temp < array[j-1]; j--) 9 { //從array[i]開始和自己的前一個相比較(存在優化的可能) 10 //如果array[i] < array[j-1],就代表array[i]插入的位置不對, 11 //如果temp < array[j-1],不成立,就代表到了array[i]該插入的位置了 12 array[j] = array[j-1]; 13 } 14 array[j] = temp; //找到正確位置後立即插入 15 } 16 }insertionSor
現在讓我來想一想一個問題,一個數組其元素為 5 1 2 3 4 6 7 8,
根據插入排序的代碼可得到相應的過程
- 第一輪:1 5 2 3 4 6 7 8 一次比較,兩次賦值
- 第二輪:1 2 5 3 4 6 7 8 兩次比較,三次賦值
- 第三輪:1 2 3 5 4 6 7 8 三次比較,四次賦值
- 第四輪:1 2 3 4 5 6 7 8 四次比較,五次賦值
這個數組的元素除了5原本都是有序的,但是,為了找到5正確的插入位置,總共進行了10次比較,14次賦值。
明眼人都看得出來5的正確位置應該是整個數組的最中間的位置,可是計算機偏偏就是個“瞎子”,那我們該怎麽樣讓這個“瞎子”,知道這個元素是在數組的最中間呢?
這就涉及到到上篇拓展的內容——運用二分查找來這個元素的正確位置。
這裏先貼上二分的核心代碼(建議先看懂二分查找再來看二分排序)
while (left <= right) //如果走到頭都沒有找到就退出 { mid = (left+right) / 2; //記錄中間數 if (arr[mid] > temp) //和中間數相比較 { right = mid - 1; //比中間數小就向前走 } else { left = mid + 1; //比中間數大就向後走 } }
這裏是用循環來實現二分查找,當然,我們也可以用遞歸來實現。這裏為了節省時間,我就不再多做解釋。
從所貼的代碼可看出通過二分查找我們確定元素5的正確位置只需要1次比較和5次賦值,大大減少了比較次數和賦值次數。
當然對於二分排序來說采用折半查而減少,為O(nlogn),但是元素交換的次數仍為O(n2),二分排序算法是穩定的。
下面我們來看一下完整的代碼:
1 void insertsort(int *arr, int n) 2 { 3 int i, j, temp, left, right, mid; 4 5 for (i = 1; i < n; i++) 6 { 7 left = 0; 8 temp = arr[i]; 9 right = i-1; 10 11 while (left <= right) 12 { 13 14 mid = (left+right) / 2; 15 if (arr[mid] > temp) 16 { 17 right = mid - 1; 18 } 19 else 20 { 21 left = mid + 1; 22 } 23 } 24 25 for (j = i-1; j > right; j--) 26 { 27 arr[j+1] = arr[j]; 28 } 29 arr[right+1] = temp; 30 } 31 }insertsort
大家可以自行嘗試寫一下遞歸版的二分排序,我在這裏就貼代碼了。
排序算法學習整理三(插入)