直接插入排序
直接插入排序:
是一种比较简单直观的排序算法,适用处理数据量比较少或者基本有序的数据。
算法思想:
每次将一个待排序的记录,按其关键字大小插入到前面已有序的子序列中合适位置,直到全部记录插入完为止。
不妨设设数组为 a[n]:
(1)初始时,a[0] 自成一个有序区间,无序元素为 a[1]...a[n]。
(2)将 a[i] 插入到有序序列 a[0]...a[i-1] 中合适位置,形成 a[0]...a[i] 的有序序列。
(3)重复第二步直到最后需插入的元素 a[n-1] 插入到前面的有序序列中合适位置,整个插入排序完成。
图形描述:
我们以待排序列 {48,62,35,77,55,14,35,98} 为例来看看直接插入排序的实现过程,其中大括号内是已经有序的子序列,大括号中紫色字体标识的元素是子序列中最后插入的元素。
- {48} 62 35 77 55 14 35 98
- {48 62} 35 77 55 14 35 98
- {35 48 62} 77 55 14 35 98
- {35 48 62 77} 55 14 35 98
- {35 48 55 62 77} 14 35 98
- {14 35 48 55 62 77} 35 98
- {14 35 35 48 55 62 77} 98
- {14 35 35 48 55 62 77 98}
代码实现:
首先我们给出严格按照定义书写的代码(由小到大排序):
void InsertSort(int a[],int n)
{
int i, j, k;
for (i = 1; i < n;++i)
{
//首先待插入元素a[i]在前面a[0,...,i-1]的有序序列中找到自己的位置
for (j = i - 1; j >= 0;--j)
if (a[j] < a[i])
break;
//此时待插入元素a[i]已经找到自己的位置,但它的位置还被别的元素占着
if (a[j] != i - 1)
{
//将比a[i]大的元素向右移动(从小到大排列)
int tmp = a[i];
for (k = i - 1; k>j; --k)
a[k + 1] = a[k];
//将a[i]放到正确的位置处(下表为k处)
a[k+1] = tmp;
}
}
}
现在进行一下改进,将 a[i] 寻找自己自己位置和有序的子序列中比 a[i] 大的数据后移这二个步骤合并。即每次a[i]先和前面一个数据a[i-1]比较,如果a[i] > a[i-1]说明a[0…i]也是有序的,无须调整。否则就令j=i-1,temp=a[i]。然后一边将数据a[j]向后移动一边向前搜索,当有数据a[j]<a[i]时停止并将temp放到a[j + 1]处。
void InsertSort(int a[], int n)
{
int i, j;
for (i = 1; i < n; ++i)
{
if (a[i] < a[i - 1])
{
int tmp = a[i];
for (j = i - 1; j>=0 && a[j]>tmp;--j)
a[j + 1] = a[j];
a[j + 1] = tmp;
}
//在第一层for循环内部以下注释掉的代码也是可以的,二者没有本质的区别
// int tmp = a[i];
// for (j = i - 1; j >= 0; --j)
// {
// if (a[j] > tmp)
// {
// a[j + 1] = a[j];
// }
// else
// break;
// }
// a[j + 1] = tmp;
}
}
再对将a[j]插入到前面a[0…j-1]的有序区间所用的方法进行改写,用数据交换代替数据后移。如果a[j]前一个数据a[j-1] > a[j],就交换a[j]和a[j-1],再j--直到a[j-1] <= a[j]。这样也可以实现将一个新数据新并入到有序区间。
void InsertSort(int a[], int n)
{
int i, j;
for (i = 1; i < n; i++)
{
for (j = i - 1; j >= 0 && a[j] > a[j + 1]; j--)
Swap(a[j], a[j + 1]);
}
}
直接插入排序中第一层 for 循环以 i= 0 开始,内部以 while 循环实现数据的比较、移动和边界的控制又是一种思想的体现。
void InsertSort(int* arr, int n)
{
assert(arr);
for (int i = 0; i < n-1;++i)
{
//将tmp插入以end为结束的有序区间
int end = i;
int tmp = arr[end + 1];
while (end >= 0 && arr[end] > tmp)
{
arr[end + 1] = arr[end];
--end;
}
arr[end + 1] = tmp;
}
}
在直接插入排序代码实现这块提供了多种实现方式,从原始的最基本的算法思想实现到步步改进,从不同维度和实现方式中理解直接插入排序,相信会更好吧哈哈哈
时间复杂度和空间复杂度:
下面讨论最坏时间复杂度,即所有元素倒序排列
对于 n 个元素,首先外层的 for 循环要执行 n-1 次,内层循环是根据 i 来决定的,i = 1,循环1次;i = 2,循环2次;...;i = n-1;循环 n-1 次。总共加起来就是:
根据复杂度的计算规则,保留高阶项并去掉系数得直接插入排序的时间复杂度为。
算法名称 | 时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 最好时间复杂度 | 空间复杂度 |
直接插入排序 |
稳定性:
是稳定的,因为在比较的时候,当两个数相等时不会进行移动,前后两个数的次序不会发生改变。
注意:算法的稳定性是相对的,任何稳定的算法是可以改变为不稳定算法的。