中心思想:考虑你正在斗地主,对方发给你一堆牌,你右手摸牌,左手拿牌,第一次摸到10放到左手,第二次摸到3就放到10左边,第三次摸到K放10右边,左手上的牌一直保持有序,直到左手拿着所有的牌为止。这就是插入排序。
插入排序从第二个元素开始,在前面已排好序的序列中寻找合适的位置并插入,使之仍然有序。
插入排序最多的步骤在于移位操作,每个元素插入之前,数组的大于待插入值的元素必须事先挪位,故插入排序在最坏情况下是o(n*n)的复杂度。
直接插入:
//插入排序,不支持参数检查
void insert_sort(int * a , int n)
{
int i,j,key;
for(i=1;i<n;++i)
{
key = a[i];//key 为待插入的元素
//挪位
for( j = i-1;j>=0 && a[j] >key;--j)
{
a[j+1] = a[j];//两重循环,故O(n*n)
}
//在合适的位置插入
a[j+1] = key;
}
}
在一万个数据的运行结果:
插入排序有个挪位操作,每次循环必须判断移动的元素是否大于待插入的元素,为了省去不必要的判断,我们可以先用二分法寻找合适的插入位置。
折半插入:
void insert_sort(int * a , int n)
{
int i,j,key,low,high,mid;
for(i=1;i<n;++i)
{
key = a[i];
//二分查找运算
low = 0,high = i-1;
while(low <= high)
{
mid = (low + high)/2;
if(key > a[mid])
low = mid + 1;
else// if(key <= a[mid])
high = mid - 1;
}
//挪位
for( j = i-1;j >= low;--j)
{
a[j+1] = a[j];
}
a[j+1] = key;
}
}
在一万个数据的运行结果:
结果快了大概十多毫秒。。。我想应该是移位操作步数并没有因此减少,只是对比较操作优化了一下而已。所以效果并不明显。
二路插入:
那么有没有办法减少移位的步数呢?仔细一想,有一种情况是不需要移位的,那就是待插入的元素刚好插入到有序序列的最后。我们想一下,如果有序序列前面还有位置放会怎样?本来需要把所有有序序列移位的现在也变成不需要移位了。那么如何实现呢?我们可以利用循环数组的想法来做。用first表示有序序列的开始,last表示有序序列的结尾,如果待插入元素小于first值或者大于last值,就放到外面去;如果待插入元素刚好处于first值和last值之间,那么就插入到有序序列中间,这和直接插入一样。
举例:如果有序序列是 1 2 3 5,待插入的是0,那么我就把它放到数组的最后,让first等于最后一个(first = n -1),让last = 2。
二路插入的思想是以空间换取时间的想法。
void insert_sort(int * a , int n)
{
int *t;
t = new int [n];
int i,j,key,first=0,last=0;
t[0] = a[0];
for(i=1;i<n;++i)
{
key = a[i];//key 为待插入的元素
//如果待插入元素比first值还小
if(key <= t[first])
{
first = (first-1+n)%n;
t[first] = key;
}
//如果待插入元素比last值还大
else if(key >= t[last])
{
last = last + 1;
t[last] = key;
}
//如果待插入元素处于first和last之间
else //if(key > t[first] && key < t[last])
{
//挪位
for( j = last;j != first && t[j] > key; j= (j-1+n)%n)
{
t[(j+1)%n] = t[j];//两重循环,故O(n*n)
}
//在合适的位置插入
t[(j+1)%n] = key;
last = (last + 1)%n;
}
}
//赋值给a数组
j=0;
i =first;
do{
a[j++] = t[i];
i = (i+1)%n;
}while(i != (last + 1)%n);
}
但是运行结果却出乎意料:
但是对于完全逆序的数据却出乎意料的快:
经过仔细分析,原来是取余(循环链表需要用到模运算)造成的影响,于是修改一下:
void insert_sort(int * a , int n)
{
int *t;
t = new int [n];
int i,j,key,first=0,last=0;
t[0] = a[0];
for(i=1;i<n;++i)
{
key = a[i];//key 为待插入的元素
//如果待插入元素比first值还小
if(key <= t[first])
{
first = (first-1+n)%n;
t[first] = key;
}
//如果待插入元素比last值还大
else if(key >= t[last])
{
t[++last] = key;
}
//如果待插入元素处于first和last之间
else //if(key > t[first] && key < t[last])
{
//挪位
int k1,k2;
for( j = last;j != first && t[j] > key; j= (!j)?(j-1+n):j-1)//避免取余带来的性能损耗
{
k1= (!(j-n+1))?j+1-n:j+1;
t[k1] = t[j];//两重循环,故O(n*n)
}
//在合适的位置插入
t[(!(j-n+1))?j+1-n:j+1] = key;
++last;
}
}
//赋值给a数组
j=0;
i =first;
do{
a[j++] = t[i];
i = (!(i-n+1))?i+1-n:i+1;
}while(i != last + 1);
}
运行结果:
我只能说我已经尽力了,对于均匀随机产生的数据,二路插入只有70多毫秒,比直接插入还要慢一点,但是对于极端情况即完全逆序或者完全升序的情况来说是很快的,权衡一下应该选择二路插入好一点。
希尔排序:
为了省略移位带来的影响,希尔提出了这样一种思路:如果待排序的数组元素相对有序,那么是否会降低移位操作的频率?
如果在插入元素之前,数组都相对有序,大的元素都在后头,小的元素都在前面,这样只需移动少许的元素就可以使数组升序。
上代码:
void insert_sort(int * a , int n)
{
int i,j,key,step;//step 为步长
step = n >>1;
while(step!=0)
{
//cout<<step<<endl;
for(i=0;i<n;i += step)
{
key = a[i];//key 为待插入的元素
//挪位
for( j = i-step;j>=0 && a[j] >key;j -= step)
{
a[j+step] = a[j];//两重循环,故O(n*n)
}
//在合适的位置插入
a[j+step] = key;
}
step = step >>1;
}
}
我感觉希尔排序跟直接插入排序的代码差不多,不同的是下标值的变换,希尔排序会依次缩小步长,增大待排序的元素,直到步长为1,即变成了直接插入排序,但由于之前的排序是数组相对有序(大小错落有致),所以移位运算大大减少,因此性能得到提高:
以上,便是插入排序的大致内容,这是我的第一篇博客,谢谢观看。
2612

被折叠的 条评论
为什么被折叠?



