插入排序
最基本的排序,很多书上的说法“就像我们平时打牌一样”,原理很简单
设 a 为一组牌 共 n 张,升序排列
(1)将第 i 张牌与前 i-1 张排好序的牌比较,(方向向前)
(2)找到应在的位置 k (a[k] < a[i]),将 k + 1 至 i - 1 张牌向后挪一张。
(3)把第 i 张牌放入 位置 k+1
这是我的代码
public static void sort(int[] data){
int key = 0,j=0;
for(int i = 1; i < data.length; i ++){
key = data[i];
j = i -1;
while(j > 0 && data[j] > key){
data[j + 1] = data[j];
j --;
}
data[j + 1] = key;
}
}
时间复杂度
最好情况下, 即 数组已有序 , 的时间复杂度为 O(
n
) ;
最坏情况下, 即 数组倒叙, 的时间复杂度为 O(
n2
) ;
平均时间复杂度为 O(
n2
) ;
折半插入
插入排序的最好情况很理想,是线性的,但平均情况却非常差,幸好有一种改进的方法,折半。在一般的插入过程中寻找位置 k 的过程是逐一的向前查找,但我们注意到 i 之前的牌堆是有序的,正好符合折半查找的前提。利用折半的方式可以使每次的查找复杂度从 O(
n
) 降为 O(
log(n)
)。
但无法避免的是,还要将 i - k 个数向后移动。所以折半插入的时间复杂度任然为O(
n2
)
我的代码
public static void sort(int[] data){
int key = 0,m = 0,low = 0,high =0;
for(int i = 1; i < data.length; i ++){
key = data[i];
low = 0;
high = i - 1;
while(low <= high){ //折半查找
m = (low + high) / 2;
if(data[m] > key){
high = m - 1;
}else{
low = m + 1;
}
}
for(int j = i - 1; j >= high + 1; j --){
data[j + 1] = data[j];
}
data[high + 1] = key;
}
}
希尔排序
希尔排序,又叫“减少增量排序”,它在插入排序的基础上做了极大的改进。
分析一下插入排序的特征,
- 在最好情况下,即已排序的情况下,时间复杂度是线性的。那么如果数组“基本有序”就会大大提高插入排序的效率。
- 算法简单,当 n 值较小时效率较高
这就是希尔排序的思想。将整个数组分成若干组,分别进行插入排序,再合并若干组,再排序。逐渐逼近“有序”,直到合为一个数组。
而且希尔排序的分组,不是简单的切割数组,而是将相隔一定“增量”的数分为一组。
原理如下
取合适增量 d
将数组中 同一增量上的数进行出入排序
减小 增量 d,再排序 , 直到 d = 1
我的代码如下
public static void sort(int[] data){
int d=data.length;
while(true)
{
d=d/2; //逐渐减小增量
for(int x=0;x<d;x++)
{
//进行插入排序
for(int i=x+d;i<data.length;i=i+d)
{
int temp=data[i];
int j;
for(j=i-d;j>=0&&data[j]>temp;j=j-d)
{
data[j+d]=data[j];
}
data[j+d]=temp;
}
}
if(d==1) // 直到增量为 1
{
break;
}
}
}
希尔排序的复杂度不好分析,涉及到数学上尚未解决的问题。
有人指出,当增量序列为 dlta[k]=2t−k+1−1 时,希尔排序的时间复杂度为 O( n3/2 )
希尔排序中“增量”的选择是影响其效率的主要因素。而增量序列的确定也是非常复杂的,这里不做讨论,又兴趣的可以研究一下。
参考文献:
[1] 严蔚敏 吴伟民 . 数据结构(C语言版) . 清华大学出版社
[2] 邹恒明 . 算法之道 . 机械工业出版社
[3] 算法导论 . 机械工业出版社