“插入排序”是数列排序的算法之一。
其思路引点来自于我们平时打扑克牌的习惯。
“我们在整理扑克牌时,往往会倾向于将无序的扑克牌升序或降序的排列,其方法在于拿起一张牌,与其他牌对比,如果是升序排列,那就与左边的牌进行对比,将其放在比此牌大且比此牌小的位置,重复这个过程,就会得到一个有序的牌组。”
- 算法思路
首先,得到一个随机的数列。
左端的数字已完成排序。
然后,取出那些尚未操作的左端的数字,将其与已经操作的左侧的数字进行比较。如果左边的数字较大,交换两个数字。重复此操作,直到出现一个较小的数字或者数字到达左端。
这种情况下,由于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)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。