插入排序

中心思想:考虑你正在斗地主,对方发给你一堆牌,你右手摸牌,左手拿牌,第一次摸到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;
    }
}

在一万个数据的运行结果:
数据随机生成,循环20次得到
插入排序有个挪位操作,每次循环必须判断移动的元素是否大于待插入的元素,为了省去不必要的判断,我们可以先用二分法寻找合适的插入位置。

折半插入:

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,即变成了直接插入排序,但由于之前的排序是数组相对有序(大小错落有致),所以移位运算大大减少,因此性能得到提高:
这里写图片描述
以上,便是插入排序的大致内容,这是我的第一篇博客,谢谢观看。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值