“插入排序”是數列排序的算法之一。
其思路引點來自於我們平時打撲克牌的習慣。
“我們在整理撲克牌時,往往會傾向於將無序的撲克牌升序或降序的排列,其方法在於拿起一張牌,與其他牌對比,如果是升序排列,那就與左邊的牌進行對比,將其放在比此牌大且比此牌小的位置,重複這個過程,就會得到一個有序的牌組。”
- 算法思路
首先,得到一個隨機的數列。
左端的數字已完成排序。
然後,取出那些尚未操作的左端的數字,將其與已經操作的左側的數字進行比較。如果左邊的數字較大,交換兩個數字。重複此操作,直到出現一個較小的數字或者數字到達左端。
這種情況下,由於5大於3,所以交換了數字,數字到達了左端,停止數字移動。
這樣,“3”已經完成了排序。
和之前同樣取出左端的數字,與左邊的數字進行比較。
由於“5”大於“4”,所以交換了數字。由於“3”小於“4”,出現了更小的數字,所以“4”停止移動。
這樣,“4”完成了排序。
重複上述操作,直至所有的數字完成排序。
- 動畫演示
- 代碼清單及其測試結果
#include <iostream>
#include <ctime>
template <class T>
int getSizeOfArray(T& is){
return sizeof(is)/ sizeof(is[0]);
}
void insertionSort(int *is,int size){
for(int i=1;i<size;i++){
int currentIndex = i;
for(int j=currentIndex-1;j>=0;j--){
if(is[j]>is[currentIndex]){
int cup = 0;
cup = is[j];
is[j] = is[currentIndex];
is[currentIndex] = cup;
currentIndex = j;
}
}
}
}
int main() {
using namespace std;
clock_t startTime,endTime;
int is[] = {2,3,5,1,0,8,6,9,7};
int size = getSizeOfArray(is);
cout<< "原數列:";
for(int i = 0;i<size;i++)
{
cout<< is[i] << " ";
}
cout<< "\n" << "選擇性排序後:";
startTime = clock();//計時開始
insertionSort(is,size);
endTime = clock();//計時結束
for(int i = 0;i<size;i++)
{
cout<< is[i] << " ";
}
cout << "\n"<<"The run time is: " <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
return 0;
}
- 另一種思路
我們通過動畫演示和上述的代碼清單可以發現,是利用不斷的比較交換來實現排序的過程。但,其實,我們還有另外一種實現方法,並不會改變“插入排序”的核心思路,這個與我們平時打牌最相似,話不多說,直接看代碼。
#include <iostream>
#include <ctime>
template <class T>
int getSizeOfArray(T& is){
return sizeof(is)/ sizeof(is[0]);
}
void moveArray(int *is,int currentIndexOfI,int currentIndexOfJ){
for(int i = currentIndexOfI;i>currentIndexOfJ;i--){
is[i] = is[i-1];
}
}
void insertionSort2(int *is,int size){
for(int i=1;i<size;i++){
int locationIndex = -1;
for(int j=i-1;j>=0;j--){
if(is[i]<is[j]){
locationIndex = j;
}
}
if(locationIndex!=-1){
int cup;
cup = is[i];
moveArray(is,i,locationIndex);
is[locationIndex] = cup;
}
}
}
int main() {
using namespace std;
clock_t startTime,endTime;
int is[] = {2,3,5,1,0,8,6,9,7};
int size = getSizeOfArray(is);
cout<< "原數列:";
for(int i = 0;i<size;i++)
{
cout<< is[i] << " ";
}
cout<< "\n" << "選擇性排序後:";
startTime = clock();//計時開始
insertionSort2(is,size);
endTime = clock();//計時結束
for(int i = 0;i<size;i++)
{
cout<< is[i] << " ";
}
cout << "\n"<<"The run time is: " <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
return 0;
}
通過觀察,可以明顯發現,這是一種將數組片段推移的實現方法,就和我們平時打牌一樣,從中間發現一張較小的牌,直接找到位置,將其插入,並將右邊的牌整體向右移動。
- 實現方法對比
在數據量小的時候,在相同的數列情況下,不同實現方式(即3小節與4小節)算法運行的時間是幾乎相同的。下面看看大整數數列下,誰的性能更好。
隨機數範圍:r屬於[0,100]
樣本量(單位:個) | 100 | 500 | 1000 | 2000 | 5000 | 10000 | 100000 |
實現方法一(單位:s) | 2.2e-05 | 0.000521 | 0.002355 | 0.007207 | 0.04969 | 0.154 | 13.5 |
實現方法二(單位:s) | 2.2e-05 | 0.000518 | 0.001811 | 0.008545 | 0.051375 | 0.165 | 15.3 |
我們不難發現,在樣本數小於5,000時,似乎實現方法二更爲省時,但樣本數一旦大於5,000,很明顯實現方法一更爲快速。
- 算法分析
如果目標是把n個元素的序列升序排列,那麼採用插入排序存在最好情況和最壞情況。最好情況就是,序列已經是升序排列了,在這種情況下,需要進行的比較操作需(n-1)次即可。最壞情況就是,序列是降序排列,那麼此時需要進行的比較共有n(n-1)/2次。插入排序的賦值操作是比較操作的次數加上 (n-1)次。平均來說插入排序算法的時間複雜度爲O(n^2)。因而,插入排序不適合對於數據量比較大的排序應用。但是,如果需要排序的數據量很小,例如,量級小於千,那麼插入排序還是一個不錯的選擇。