插入排序(直接插入排序--折半插入排序--谢尔排序)

插入排序分为直接插入排序,折半插入排序,谢尔排序等。

1.直接插入排序

基本思想:以排好序的记录子序列(简称子序列)开始,每次将待排序的一个记录,按照其关键字大小插入到前面已经排好序的子序列中的适当位置,即保持关键字大小有序,排好序的记录子序列每次增加一条记录,直到所有的记录都排好序为止。即: 第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从后向前扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。

示例(带岗哨的直接插入排序过程):

若有个元素和插入元素相等,则把插入元素放到相等元素后面。排序前后,关键值相同的记录相对位置没有改变,故插入排序是稳定的。

(1)设置岗哨的直接插入排序

假设带排序记录存放在R[1]~R[n]中,R[0]作为岗哨。在当前对第i个记录处理之前,R[1]~R[i-1]有序,R[i]~R[n]无序,将R[i]插入到有序区中适当位置,使R[1]~R[i]有序:先将R[i]放入R[0],R[0]保存当前要处理的记录,再将R[i]的关键字值依次与有序区中记录R[j](j=i-1,i-2,i-3,...,1,0)比较,若R[j]>R[0],则R[j]后移,若R[j]<=R[0],查找结束,j+1就是R[0]的插入位置。
直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小或与待比较数值相等的并将待比较数值置入其后一位置,结束该次循环。
岗哨作用:
① 进人查找(插入位置)循环之前,它保存了R[i]的副本,使不致于因记录后移而丢失R[i]的内容;
② 在查找循环中"监视"下标变量j是否越界。一旦越界(即j=0),因为R[0]可以和自己比较,
  循环判定条件不成立使得查找循环结束,从而避免了在该循 环内的每一次均要检测j是否越界(即省略了循环判定条件 "j>=1" )。
C++代码:
#include <iostream>
using namespace std;

void InsertSort(int R[],int n)
{//岗哨R[0],直接插入排序
    for(int i=2;i<=n;i++)
    {
        R[0]=R[i];
        int j=i-1;
        for(;R[j]>R[0];j--)
        {
            R[j+1]=R[j];
        }
        R[j+1]=R[0];
        cout<<endl;
        for(int i=1;i<=n;i++)
           cout<<R[i]<<' ';
    }
}
int main()
{
    int R[]={0,49,38,65,97,76,13,27,49};
    int size=sizeof(R)/sizeof(int);
    for(int i=1;i<=size-1;i++)
    {
        cout<<R[i]<<' ';
    }
    cout<<endl;
    InsertSort(R,size-1);
    return 0;
}


直接插入排序算法分析:

设待排序对象个数为n,则该算法的主程序执行n1趟。排序码比较次数和对象移动次数与对象排序码的初始排列有关
  • 最好情况下,排序前对象已经按照要求的有序。比较次数为n-1,移动次数为0。则对应的时间复杂度O(n)
  • 最坏情况下,排序前对象为要求的顺序的反序。待比较的第i个对象R[i]至多与前面i-1个对象以及岗哨都做关键字比较,并且每做1次比较就要做1次数据移动(具体可以从上面给出的代码中看出)。总的比较次数和移动次数均为:1+2+...+N-1 ,故时间复杂度为O(N^2)

(2)不设置岗哨的直接插入排序

在每一趟插入排序时,扫描变量每取一个下表值,都要判断是否越界。
C++代码:

#include <iostream>
using namespace std;

void InsertSort(int R[],int n)
{
    for(int i=2;i<=n;i++)
    {
        R[0]=R[i];
        int j=i-1;
        for(;R[j]>R[0]&&j>0;j--)
        {
            R[j+1]=R[j];
        }
        R[j+1]=R[0];
        cout<<endl;
        for(int i=1;i<=n;i++)
           cout<<R[i]<<' ';
    }
}
int main()
{
    int R[]={0,49,38,65,97,76,13,27,49};
    int size=sizeof(R)/sizeof(int);
    for(int i=1;i<=size-1;i++)
    {
        cout<<R[i]<<' ';
    }
    cout<<endl;
    InsertSort(R,size-1);
    return 0;
}

(3)折半插入排序

若将搜索待插入位置的操作改为在[1:i-1]上进行折半查找实现,则得到改进的直接插入排序算法。
代码:
#include <iostream>
using namespace std;
void BiInsertSort(int R[],int n)
{
    for(int i=2;i<=n;i++)
    {
        R[0]=R[i];//岗哨
        int low=1,high=i-1;
        while(low<=high)
        {
            int m=(low+high)/2;
            if(R[m]>R[0]) high=m-1;
            else low=m+1;
        }
        for(int j=i-1;j>=low;j--) R[j+1]=R[j];
        R[low]=R[0];
    }
}
int main()
{
    int R[]={0,49,38,65,97,76,13,27,49};
    int size=sizeof(R)/sizeof(int);
    for(int i=1;i<=size-1;i++)
    {
        cout<<R[i]<<' ';
    }
    cout<<endl;
    BiInsertSort(R,size-1);
    return 0;
}
算法分析:

 1)时间复杂度:

   折半插入排序比直接插入排序明显减少了关键字之间的比较次数,但是移动次数是没有改变。所以,折半插入排序和插入排序的时间复杂度相同都是O(N^2),在减少了比较次数方面它确实相当优秀,所以该算法仍然比直接插入排序好。

 2)空间复杂度:

   折半插入排序和插入排序一样只需要一个多余的缓存数据单元来放第 i 个元素,所以空间复杂度是O(1),因为排序前2个相等的数在序列的前后位置顺序和排序后它们两个的前后位置顺序相同,所以它是一个稳定排序。 

2、谢尔排序

谢尔排序是插入排序的一种,又称缩小增量排序。
基本思想:设计递减整型增量序列的d1,d2,...,dt,dt=1<......<d2<d1,依次取增量d1,d2,......,对每个增量di,把记录按照下标以该增量逻辑分组,即所有下标距离为di的记录分为一组,且对每组记录用直接插入排序法排序,最后当增量为1时,待排记录的序列按关键字值基本有序,此时整个序列在一组,进行直接插入排序,从而完成谢尔排序。

基本做法:
(1)取初值i=1
(2)h=di,把所有记录逻辑上分成若干个各元素相距为h的子记录集合
(3)对各个子记录集合进行直接插入排序
(4)i++,只要i<=t,重复(2),直到dt=1时进行一次插入排序后结束。此时,全部记录按关键字值有序。

示例:



算法分析:
(1)时间复杂度:
希尔排序的时间复杂度与增量(即,步长gap)的选取有关。例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²),而Hibbard增量的希尔排序的时间复杂度为O(N3/2)。
(2)稳定性
希尔排序是不稳定的算法,它不满足稳定算法的定义。对于相同的两个数,可能由于分在不同的组中而导致它们的顺序发生变化。
算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!
 
代码1:
#include <iostream>
using namespace std;

void ShellSort(int R[],int d[],int t,int n)
{//R[1]~R[n]为待排记录,d增量数组,t为d[]长度,d[t-1]=1(最后一个增量为1)
    int i,j,k,temp;
    for(k=0;k<t;k++)
    {
        int h=d[k];//增量
        for(i=h+1;i<=n;i++)
        {//每趟分组内直接插入排序
            temp=R[i];
            j=i-h;
            while(R[j]>temp&&j>0)//没岗哨
            {
                R[j+h]=R[j];
                j=j-h;
            }
            R[j+h]=temp;//插入记录
        }
        cout<<endl;
        for(int p=1;p<=n;p++)
        {
            cout<<R[p]<<' ';
        }
    }
}
int main()
{
    int R[]={0,19,41,11,17,47,43,13,37,31,23};
    int n=sizeof(R)/sizeof(int);
    int d[]={5,2,1};
    int t=sizeof(d)/sizeof(int);
    cout<<"原数组:"<<endl;
    for(int i=1;i<=n-1;i++)
    {
        cout<<R[i]<<' ';
    }
    cout<<endl<<"排序过程:"<<endl;
    ShellSort(R,d,t,n-1);
    return 0;
}


代码2:

#include <iostream>
#include <vector>
using namespace std;

void ShellSort3(vector<int> R,vector<int> d)
{//R[1]~R[n]为待排记录,d增量数组,t为d[]长度,d[t-1]=1(最后一个增量为1)
    int i,j,k,temp;
    int t=d.size(),length=R.size();
    int n=length-1;
    for(k=0;k<t;k++)
    {
        int h=d[k];//增量
        for(i=h+1;i<=n;i++)
        {//每趟分组内直接插入排序
            temp=R[i];
            j=i-h;
            while(R[j]>temp&&j>0)//没岗哨
            {
                R[j+h]=R[j];
                j=j-h;
            }
            R[j+h]=temp;//插入记录
        }
        cout<<endl;
        for(int p=1;p<=n;p++)
        {
            cout<<R[p]<<' ';
        }
    }
}

int main()
{
    vector<int> R;
    R.push_back(0);
    R.push_back(9);
    R.push_back(41);
    R.push_back(11);
    R.push_back(17);
    R.push_back(47);
    R.push_back(43);
    R.push_back(13);
    R.push_back(37);
    R.push_back(31);
    R.push_back(23);

    vector<int> d;
    d.push_back(5);
    d.push_back(2);
    d.push_back(1);
    int length=R.size();
    cout<<"原数组:"<<endl;
    for(int i=1;i<=length-1;i++)
    {
        cout<<R[i]<<' ';
    }
    cout<<endl<<"排序过程:"<<endl;
    ShellSort3(R,d);
    return 0;
}

(1)设置岗哨的谢尔排序

若要像直接插入排序那样,将待插入记录R[i](h+1<=i<n)在查找插入位置之前保存到岗哨中,则必须先计算R[i]属于哪一组,才能决定用哪个岗哨保存R[i]。为避免这种计算,可将R[i]保存到另一个辅助记录x中,而将所有岗哨的关键字设置为少于序列中任何关键字即可。因增量是变化的,故各趟排序中所需岗哨数目不同,但可按最大增量d1来设置岗哨(增量越大,分组越多,一趟排序中岗哨越多)。
代码:
#include <iostream>
#include <vector>
using namespace std;

void ShellSort1(int r[],int d[],int n)
{//r[0]~r[d[0]-1]为岗哨,r[d[0]]~r[n+d[0]-1]为待排序列
//d为增量序列数组,t为d[]长度,d[t-1]=1
//d[0]是第一个增量d1,即最大增量。待排序列共n个数
    int i,j,k=0,h,temp,maxint=32767;
    for(i=0;i<d[0];i++)//设置岗哨
    {
        r[i]=-maxint;
    }
    do{
        h=d[k];//h取本趟增量
        for(i=h+d[0];i<=n+d[0]-1;i++)
        {
            temp=r[i];//保存待插入记录r[i]
            j=i-h;//j为每组中往回找组内插入位置的初始值
            while(r[j]>temp)//查找插入位置及后移
            {
                r[j+h]=r[j];
                j=j-h;//得到前一记录位置
            }
            r[j+h]=temp;//插入r[i]
        }//本趟排序完成
        k++;
        cout<<endl;
        for(int p=d[0];p<=n+d[0]-1;p++)
        {
            cout<<r[p]<<' ';
        }
    }while(h!=1);//增量为1排序后终止算法
}

int main()
{
    int R[]={0,0,0,0,0,19,41,11,17,47,43,13,37,31,23};
    int size=sizeof(R)/sizeof(int);
    int d[]={5,2,1};
    //int t=sizeof(d)/sizeof(int);
    cout<<"原数组:"<<endl;
    for(int i=d[0];i<=size-1;i++)
    {
        cout<<R[i]<<' ';
    }
    cout<<endl<<"排序过程:"<<endl;
    ShellSort1(R,d,size-d[0]);
    return 0;
}

void ShellSort2(int r[],int d[],int n)
{//r[0]~r[d[0]-1]为岗哨,r[d[0]]~r[n+d[0]-1]为待排序列
//d为增量序列数组,t为d[]长度,d[t-1]=1
//d[0]是第一个增量d1,即最大增量。待排序列共n个数
    int i,j,k=0,h,temp,maxint=32767;
    int q=0;
    for(i=0;i<d[0];i++)//设置岗哨
    {
        r[i]=-maxint;
    }
    do{
        h=d[k];//h取本趟增量
        for(i=h+d[0];i<=n+d[0]-1;i++)
        {
            r[q]=r[i];//岗哨,保存待插入记录r[i]
            j=i-h;//j为每组中往回找组内插入位置的初始值
            while(r[j]>r[q])//查找插入位置及后移
            {
                r[j+h]=r[j];
                j=j-h;//得到前一记录位置
            }
            r[j+h]=r[q];//插入r[i]
        }//本趟排序完成
        k++;q++;
    }while(h!=1);//增量为1排序后终止算法
}



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值