直接插入排序
算法介绍
一种最简单的排序方法,其基本操作是将一条记录插入到已排好序的有序表中,从而得到一个新的、记录数量增1的有序表。
算法例题
用随机函数生成16个2位正整数(10~99),利用 直接插入排序法 将其排序。
算法思路
书面概括
- 将待排序的元素存放在数组 r[1...n] 中, r[1] 是一个有序序列;
- 循环 n-1 次,每次使用顺序查找法,查找 r[i] (i=2,...,n) 在已排好序的序列 r[1...r-1] 中的插入位置,然后将 r[i] 插入表长为 i-1 的有序序列 r[1..i-1] ,直到将 r[n] 插入表长为 n-1 的有序序列 r[1...n-1] ,最后得到一个表长为 n 的有序序列。
个人概括
每趟排序都将 待排序序列 中的 第一个元素 插入到 有序序列 的适当位置,n 趟排序后将待排序序列中所有元素都插入到有序序列中。
考虑情况
-
监视哨:r[0] 存放每趟排序的待排序元素。
-
每趟排序过程会有三种情况
- 待排序序列中的第一个元素大于有序序列中最大的元素,不需移动,直接将该元素更新为当前有序序列中的最大元素;
- 待排序序列中的第一个元素小于有序序列中的元素,但不小于有序序列中最小的元素;
- 待排序序列中的第一个元素小于有序序列中最小的元素,但是因为监视哨的存在,2和3的情况归为一类。
- 在 2 和 3 的情况下,需要将有序序列中大于待排序元素的元素往后移一位(就是给待排序元素腾位置),然后将待排序元素插入空出来的位置。
-
后移有序序列元素可以直接进行,无需再次保存,因为监视哨已经保存了待排序元素。
-
我的算法是先统计有序序列中大于待排序元素的元素个数(等于后移次数),再进行后移插入,所以需要设置一个变量记录。
-
而更优的写法是一边判断一边后移。
图解过程
( ) 为监视哨,[ ] 为待排序序列,{ } 为有序序列,< > 为比较中的元素," " 为待插入位置。
1.第一趟排序
( ) [ 49 38 65 97 76 13 27 49 ]
设置监视哨
( 49 ) [ 49 38 65 97 76 13 27 49 ]
比较,大于等于当前比较元素,比较结束
(<49>) [<49> 38 65 97 76 13 27 49 ]
更新有序序列
( 49 ) {" "}[ 38 65 97 76 13 27 49 ]
( ) {"49"}[ 38 65 97 76 13 27 49 ]
2.第二趟排序
( ) { 49 }[ 38 65 97 76 13 27 49 ]
设置监视哨
( 38 ) { 49 }[ 38 65 97 76 13 27 49 ]
比较,小于当前比较元素,继续往前比较
( 38 ) {<49>}[<38> 65 97 76 13 27 49 ]
(<38>) { 49 }[<38> 65 97 76 13 27 49 ]
大于等于当前比较元素,比较结束
(<38>) { 49 }[<38> 65 97 76 13 27 49 ]
更新有序序列
( 38 ) {" " 49 }[ 65 97 76 13 27 49 ]
( ) { 38 49 }[ 65 97 76 13 27 49 ]
3.第三趟排序
( ) { 38 49 }[ 65 97 76 13 27 49 ]
设置监视哨
( 65 ) { 38 49 }[ 65 97 76 13 27 49 ]
比较,大于等于当前比较元素,比较结束
( 65 ) { 38 <49>}[<65> 97 76 13 27 49 ]
更新有序序列
( 65 ) { 38 49 " "}[ 97 76 13 27 49 ]
( ) { 38 49 "65"}[ 97 76 13 27 49 ]
4.第四趟排序
( ) { 38 49 65 }[ 97 76 13 27 49 ]
设置监视哨
( 97 ) { 38 49 65 }[ 97 76 13 27 49 ]
比较,大于等于当前比较元素,比较结束
( 97 ) { 38, 49, <65> }[<97> 76 13 27 49 ]
更新有序序列
( 97 ) { 38 49 65 " "}[ 76 13 27 49 ]
( ) { 38 49 65 "97"}[ 76 13 27 49 ]
5.第五趟排序
( ) { 38 49 65 97 }[ 76 13 27 49 ]
设置监视哨
( 76 ) { 38 49 65 97 }[ 76 13 27 49 ]
比较,小于当前比较元素,继续往前比较
( 76 ) { 38 49 65 <97>}[<76> 13 27 49 ]
( 76 ) { 38 49 <65> 97 }[<76> 13 27 49 ]
大于等于当前比较元素,比较结束
( 76 ) { 38 49 <65> 97 }[<76> 13 27 49 ]
更新有序序列
( 76 ) { 38 49 65 " " 97 } [ 13 27 49 ]
( ) { 38 49 65 "76" 97 } [ 13 27 49 ]
6.第六趟排序
( ) { 38 49 65 76 97 }[ 13 27 49 ]
设置监视哨
( 13 ) { 38 49 65 76 97 }[ 13 27 49 ]
比较,小于当前比较元素,继续往前比较
( 13 ) { 38 49 65 76 <97>}[<13> 27 49 ]
( 13 ) { 38 49 65 <76> 97 }[<13> 27 49 ]
( 13 ) { 38 49 <65> 76 97 }[<13> 27 49 ]
( 13 ) { 38 <49> 65 76 97 }[<13> 27 49 ]
( 13 ) {<38> 49 65 76 97 }[<13> 27 49 ]
(<13>) { 38 49 65 76 97 }[<13> 27 49 ]
大于等于当前比较元素,比较结束
(<13>) { 38 49 65 76 97 }[<13> 27 49 ]
更新有序序列
( 13 ) {" " 38 49 65 76 97 } [ 27 49 ]
( ) {"13" 38 49 65 76 97 } [ 27 49 ]
7.第七趟排序
( ) { 13 38 49 65 76 97 } [ 27 49 ]
设置监视哨
( 27 ) { 13 38 49 65 76 97 } [ 27 49 ]
比较,小于当前比较元素,继续往前比较
( 27 ) { 13 38 49 65 76 <97>}[<27> 49 ]
( 27 ) { 13 38 49 65 <76> 97 }[<27> 49 ]
( 27 ) { 13 38 49 <65> 76 97 }[<27> 49 ]
( 27 ) { 13 38 <49> 65 76 97 }[<27> 49 ]
( 27 ) { 13 <38> 49 65 76 97 }[<27> 49 ]
( 27 ) {<13> 38 49 65 76 97 }[<27> 49 ]
大于等于当前比较元素,比较结束
( 27 ) {<13> 38 49 65 76 97 }[<27> 49 ]
更新有序序列
( 27 ) { 13 " " 38 49 65 76 97 } [ 49 ]
( ) { 13 "27" 38 49 65 76 97 } [ 49 ]
8.第八趟排序
( ) { 13 27 38 49 65 76 97 } [ 49 ]
设置监视哨
( 49 ) { 13 27 38 49 65 76 97 } [ 49 ]
比较,小于当前比较元素,继续往前比较
( 49 ) { 13 27 38 49 65 76 <97>} [<49>]
( 49 ) { 13 27 38 49 65 <76> 97 } [<49>]
( 49 ) { 13 27 38 49 <65> 76 97 } [<49>]
( 49 ) { 13 27 38 <49> 65 76 97 } [<49>]
大于等于当前比较元素,比较结束
( 49 ) { 13 27 38 <49> 65 76 97 } [<49>]
更新有序序列
( 49 ) { 13 27 38 49 " " 65 76 97 }
( 49 ) { 13 27 38 49 "49" 65 76 97 }
9.结果
{ 13 27 38 49 49 65 76 97 }
算法效率
- 最好情况:待排序序列为顺序序列
- 比较次数KCN:n-1;
- 移动次数RMN:0;
- 最坏情况:待排序序列为逆序序列
- 比较次数KCN:2 + 3 + ... + n ≈ n²/2;
- 移动次数RMN:(2 + 1) + (3 + 1) + ... + (n + 1) ≈ n²/2;
所以时间复杂度为O(n²);
下面的代码是我按照自己的思路进行编写的,算法效率没有达到应有的程度。应该判断是否需要移动再设置监视哨。
空间复杂度为O(1),监视哨。
算法特点
- 稳定排序;
- 链式存储结构也适合;
- 更适合于初始记录基本有序(正序)的情况,当序列完全无序,尤其是逆序,且元素过多,时间复杂度会大大提高。
算法代码
#include<iostream>
#include<ctime>
using namespace std;
//这个为了统计所以这样写,时间复杂度比课本高
void InsertSort(int* array, int n)
{
int recordMove = 0;
int recordCompare = 0;
for (int i = 1; i <= n; i++)
{
//记录有序序列中大于待排序元素的元素个数
int record = 0;
//监视哨
array[0] = array[i];
//将待排序元素与有序序列中的元素从大到小进行比较
while (array[i] < array[i - record - 1])
{
record++;
recordCompare++;
}
//后移有序序列,给待排序元素腾空间
for (int k = 1; k <= record; k++)
{
array[i - k + 1] = array[i - k];
recordMove++;
}
//将待排序元素插入有序序列中
array[i - record] = array[0];
recordMove++;
cout << "第" << i << "趟排序:" << endl;
for (int j = 1; j <= 16; j++)
{
if( j == 1) cout << " [ ";
cout << array[j] << " ";
if (j == i) cout << " ] ";
}
cout << endl << endl;
}
cout << "比较次数:" << recordCompare << endl;
cout << "移动次数:" << recordMove << endl << endl;
}
//时间复杂度更低,一边比较一边后移
void insertSort(vector<int>& v) {
for (int i = 1; i < v.size(); i++) {
int save = v[i];
int j = i;
while (j != 0 && v[j-1] > save) {
v[j] = v[j-1];
j--;
}
v[j] = save;
}
}
int main()
{
//生成随机16个正整数
int positiveInteger[17];
time_t t;
srand((unsigned)time(&t));
cout << "生成16个2位正整数:" << endl;
for (int i = 1; i <= 16; i++)
{
positiveInteger[i] = (rand() % (100 - 10)) + 10;
cout << positiveInteger[i] << " ";
}
cout << endl << endl;
//插入排序
InsertSort(positiveInteger, 16);
//打印排序后数组
cout << "排序后数组:" << endl;
for (int i = 1; i <= 16; i++)
{
cout << positiveInteger[i] << " ";
}
cout << endl << endl;
system("pause");
return 0;
}