直接插入排序

算法介绍

一种最简单的排序方法,其基本操作是将一条记录插入到已排好序的有序表中,从而得到一个新的、记录数量增1的有序表。


算法例题

用随机函数生成16个2位正整数(10~99),利用 直接插入排序法 将其排序。


算法思路

书面概括

  1. 将待排序的元素存放在数组 r[1...n] 中, r[1] 是一个有序序列;
  2. 循环 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] 存放每趟排序的待排序元素。

  • 每趟排序过程会有三种情况

    1. 待排序序列中的第一个元素大于有序序列中最大的元素,不需移动,直接将该元素更新为当前有序序列中的最大元素;
    2. 待排序序列中的第一个元素小于有序序列中的元素,但不小于有序序列中最小的元素;
    3. 待排序序列中的第一个元素小于有序序列中最小的元素,但是因为监视哨的存在,2和3的情况归为一类。
    4. 在 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;
}

运行结果


posted @ 2019-11-28 21:25  肥斯大只仔  阅读(342)  评论(0编辑  收藏  举报