直接插入排序:
void Insert_Sort_Direct( ElementType a[], int n )
{
int i, j;
for( i = 1; i < n; i++ ){ //外部循环,此时假定a[0]是已经排好序的,所以把下标从1到n-1的数据依次插入序列中
int tmp = a[i]; //暂时存储要插入的元素
for( j = i; j > 0 && a[ j - 1 ] > tmp; j-- ) //内部循环,从当前位置开始向前比较,如果前面的元素大于待插入元素且前面还有元素
a[j] = a[ j - 1 ]; //就把元素向后挪动,腾出位置
a[ j ] = tmp; //把要插入的元素放进腾出的位置来
}
}
空间效率:只借助常数个辅助单元,空间复杂度为O(1);
时间效率:取决于待排序表的初始状态(只有操作有两步,一个是比较,一个是移动)。
最好情况:原始数据顺序排放,则每添加一个数据需要1次比较和0次挪动,所有对n个数据需要n次比较,此时时间复杂度为O(n)。
最坏情况:原始数据逆序排放,则每添加一个数据需要进行i次比较和i次移动(i为下标),所以完整的排序需要n(n - 1) / 2次比较和移动。
平均情况下需要n(n + 1) / 4次比较和移动,时间复杂度为O(n^2)。
稳定性:稳定。(因为总是从后向前比较发现比前面元素小才会移动,当元素相等时不会发生移动)。
折半插入排序:
void Insert_Sort_Binary( ElementType a[], int n )
{
int i, j, low, high, mid;
for( i = 1; i < n; i++ ){ //外部循环,此时假定a[0]是已经排好序的,所以把下标从1到n-1的数据依次插入序列中
int tmp = a[i]; //暂时存储要插入的元素
low = 0; //设置下界为0
high = i - 1; //设置上界为当前已排好序的最后一个元素的位置
while( low <= high ){ //默认排递增序列
mid = ( low + high ) / 2; //取中间值
if( a[mid] < a[i] ) //如果要插入的元素比中间值大
low = mid + 1; //说明应该插在中间值的右边,所以修改下界
else
high = mid - 1; //否则说明应该插在中间值的左边,所以修改上界
}
for( j = i - 1; j >= high + 1; j-- ) //跳出循环时low大于high,此时high+1位置就是要插入的位置,所以循环移动元素腾出空位
a[j + 1] = a[j];
a[ high + 1 ] = tmp; //把待插入元素放进腾出来的位置
}
}
这里理解之所以要在high+1位置插入,可以把折半查找最后的各种情况罗列出来,然后得到统一的结论,即待插入元素要放在high+1的位置,或者说放在low的位置。
时间复杂度:从两部分分析,第一部分是比较部分,采用折半插入算法,比较部分的时间复杂度为O(nlogn)(n个元素,每个需要logn),第二部分是移动部分,它依然取决于序列的初始状态,和直接插入排序算法的移动部分完全一样,也是O(n^2),所以总的时间复杂度也是O(n ^2)。
稳定性:稳定(原因与直接插入排序一样)。
逆序对(inversion):对于下标i < j,如果A[i] > A[j],则称( i, j )是一对逆序对。
时间复杂度下界:任意N个不同元素组成的序列平均有N(N - 1)/ 4个逆序对,如果仅靠交换相邻两元素来排序的算法(交换一次消去一个逆序对),其平均时间复杂度为O(n^2)。
希尔排序(缩小增量排序):
void Shell_Sort( ElementType a[], int n )
{
int i, j, d; //d是步长
for( d = n / 2; d > 0; d /= 2 ){ //希尔增量序列,d每次等于上一次一半向下取整,最后一次d=1,结束之后跳出循环
for( i = d; i < n; i++ ){
int tmp = a[i]; //暂时存储要插入的元素
for( j = i; j >= d && a[ j - d ] > tmp; j -= d ) //从后向前根据间隔进行比较和移动
a[j] = a[ j - d ];
a[j] = tmp; //把待插入元素放进腾出来的位置
}
}
}
空间效率:仅借助常数的辅助单元,所以空间复杂度为O(1)。
时间效率:最坏情况下O(n^2)(增量元素不互质,小增量可能根本不起作用)。
稳定性:不稳定。
还有一些其它的增量序列可以使算法的时间复杂度更小一些。