一、需求描述

了解各种排序算法的时间和空间复杂度,编程或调用库函数实现各种排序(至少5种)。将各种排序方法用各种数据进行测试。数据包括大规模数据(500000-1000000),小规模数据,最好数据,最坏数据,给定数据,随机数据。对基于比较的排序跟踪比较和交换次数。根据测试结果,对排序算法进行分析时空复杂度以及稳定性分析。

二、已实现算法

实现了插入排序(直接插入排序、希尔排序)、交换排序(冒泡排序、快速排序)、选择排序(直接选择排序)、归并排序共6种排序算法。

三、测试与总结

在该设计中最坏数据为50个元素的递减序列,最好数据为50个元素的递增序列,小规模数据由随机生成10个元素,大规模数据由随机生成500000个元素,随机数据由随机生成50元素。

1.直接插入排序各类数据测试结果如下:

数据

比较次数

交换次数

最坏数据(50)

1274

1274

最好数据(50)

49

49

小规模数据(10)

35

35

大规模数据(500000)

62544953357

62544953357

随机数据(50)

706

706

  • 该算法当初始数据不同时,所耗的时间差异很大,最好情况为正序,时间复杂度的O(n);最坏情况为反序,时间复杂度为O(n2)平均时间复杂度为O(n2)。
  • 该算法只使用i,j,tmp3个辅助变量,与n无关,空间复杂度为O(1),为就地排序。
  • 当R[i].key=R[j].key时,本算法将R[i]放在R[j]的后面,其相对位置保持不变,是一种稳定的排序方法。

2. 希尔排序各类数据测试结果如下:

数据

比较次数

交换次数

最坏数据

308

308

最好数据

203

203

小规模数据(10)

35

35

大规模数据(500000)

28248913

28248913

随机数据

346

346

  • 该算法初始增量为n/2,后一个增量是前一个增量的1/2。一般认为其平均时间复杂度为O(n1.3)。
  • 该算法只使用i,j,gap,tmp4个辅助变量,与n无关,空间复杂度为O(1),为就地排序。
  • 该算法会对数据分组,分别进行直接插入排序,是一种不稳定的排序算法。

3. 冒泡排序各类数据测试结果如下:

数据

比较次数

交换次数

最坏数据

1225

1225

最好数据

1225

0

小规模数据(10)

45

21

大规模数据(500000)

124999750000

187538241828

随机数据

1225

644

  • 该算法最好情况下比较次数为n(n-1)/2,移动次数为0;最坏情况下比较次数为n(n-1)/2移动次数为3n(n-1)/2.平均时间复杂度为O(n2).
  • 该算法只使用i,j,tmp3个辅助变量,与n无关,空间复杂度为O(1),为就地排序。
  • 当R[i].key=R[j].key时,两者相对位置不变,是一种稳定的排序方法。

4. 快速排序

数据

比较次数

交换次数

最坏数据:

1225

147

最好数据

1225

147

小规模数据(10)

21

24

大规模数据(500000)

13321628

4509311

随机数据:

256

168

  • 该算法最坏情况是每次划分所取的基准为当前无序区关键字最小或最大的元素,结果仅仅比划分前无序区元素少了一个,因此必须做n-1次划分,时间复杂度为O(n2),即当元素已递增或递减有序时,比较次数反而最多。;最好情况为每次所取基准为当前无序区中值,其时间复杂度为O(nlogn)。平均时间复杂度为O(nlogn)。
  • 该算法每趟排序使用i,j,tmp个辅助变量,递归数高度平均为logn,空间复杂度为O(logn)。
  • 该算法是一种不稳定的排序算法。

5. 直接选择排序

数据

比较次数

交换次数

最坏数据

1225

75

最好数据

1225

0

小规模数据(10)

45

21

大规模数据(500000)

124999750000

1499910

随机数据:

1225

135

  • 该算法在每趟排序中要进行n-i-1次比较,总比较次数为n(n-1)/2;当表为正序时,移动次数为0,移动次数取最大值3n(n-1)。总的平均时间复杂度为O(n2)。
  • 该算法只使用i,j,tmp,k4个辅助变量,与n无关空间复杂度为O(1),为就地排序。
  • 该算法为不稳定的排序算法。

6. 二路归并排序

数据

比较次数

交换次数

最坏数据

133

592

最好数据

161

592

小规模数据(10)

21

76

大规模数据(500000)

8858700

18980608

随机数据

234

592

  • 该算法需要进行logn趟二路归并,每趟归并时间为O(n),其时间复杂度在最好和最坏情况下均是O(nlogn)。
  • 该算法每趟排序需要一个辅助向量来暂存两个有序表归并结果,但在每趟排序完毕后释放其空间,空间复杂度为O(n).
  • 该算法是一种稳定的排序算法。

四.排序算法的选择

  • 若n较小,可采用直接插入或直接选择排序
  • 若表元素为基本有序(正序),应选择直接插入或冒泡排序
  • 若n较大,则应选择时间复杂度为O(nlogn)的排序方法,如快速排序、归并排序。

代码:

void InsertSort(RecType R[],int n)//直接插入排序
{
    int i,j;
    RecType tmp;
    for(i=1;i<n;i++)
    {
        tmp=R[i];
        j=i-1;
        while(j>=0&&tmp.key<R[j].key)//从右向左找有序区插入位置
        {
            R[j+1]=R[j];//将关键字大于R[i].key的元素后移
            j--;
        }
        R[j+1]=tmp;//插入
    }
}
void ShellSort(RecType R[],int n)//希尔排序
{
    int i,j,gap;
    RecType tmp;
    gap=n/2;//增量设置为n/2
    while(gap>0)
    {
        for(i=gap;i<n;i++)//对相隔gap的元素直接插入排序
        {
            tmp=R[i];
            j=i-gap;
            while(j>=0&&tmp.key<R[j].key)
            {
                R[j+gap]=R[j];
                j-=gap;
            }
            R[j+gap]=tmp;
        }
        gap=gap/2;//减小增量
    }
}
void BubbleSort(RecType R[],int n)//冒泡排序
{
    int i,j;
    RecType tmp;
    for(i=0;i<n-1;i++)
    {
        for(j=n-1;j>i;j--)
        {
            if(R[j].key<R[j-1].key)
            {
                tmp=R[j];
                R[j]=R[j-1];
                R[j-1]=tmp;
            }
        }
    }
}
void QuickSort(RecType R[],int s,int t)
{
    int i=s,j=t;
    RecType tmp;
    if(s<t)//确保元素数大于2
    {
        tmp=R[s];//取第一个元素作为基准
        while(i!=j)//从两端交替向中间扫描
        {
            while(j>i&&R[j].key>=tmp.key)//从右向左扫描,找小于tmp.key的R[j]
                {
                    j--;
                }
            R[i]=R[j];//交换
            M++;
            while(i<j&&R[i].key<=tmp.key)//从左向右扫描,找大于tmp.key的R[i]
                {
                    i++;
                }
            R[j]=R[i];//交换
        }
        R[i]=tmp;
        QuickSort(R,s,i-1);//对左区间递归排序
        QuickSort(R,i+1,t);//对右区间递归排序
    }
}
void SelectSort(RecType R[],int n)//直接选择排序
{
    int i,j,k;
    RecType tmp;
    for(i=0;i<n-1;i++)//归位第i个元素
    {
        k=i;
        for(j=i+1;j<n;j++)//找无序区关键字最小的元素
        {
            if(R[j].key<R[k].key)
                k=j;
        }
        if(k!=i)//交换
        {
            tmp=R[i];
            R[i]=R[k];
            R[k]=tmp;
        }
    }
}
void Merge(RecType R[],int low,int mid,int high)//两有序表直接归并为一个有序表
{
    RecType *R1;
    int i=low,j=mid+1,k=0;//k为R1角标,i,j为第1、2段角标
    R1=(RecType*)malloc((high-low+1)*sizeof(RecType));//动态分配空间
    while(i<=mid&&j<=high)//扫描
    {
        if(R[i].key<=R[j].key)//比较两段中关键字小的元素放入R1
        {
            R1[k]=R[i];
            i++;k++;
        }
        else
        {
            R1[k]=R[j];
            j++;k++;
        }
    }
    while(i<=mid)//第一段余下部分放入R1
    {
        R1[k]=R[i];
        i++;k++;
        M++;
    }
    while(j<=high)//第二段余下部分放入R1
    {
        R1[k]=R[j];
        k++;j++;
    }
    for(k=0,i=low;i<=high;k++,i++)//R1复制到R
    {
        R[i]=R1[k];
    }
    free(R1);
}
void MergePass(RecType R[],int length,int n)//对整表进行一趟排序
{
    int i;
    for(i=0;i+2*length-1<n;i=i+2*length)    //归并两length长的相邻子表
        Merge(R,i,i+length-1,i+2*length-1);
    if(i+length-1<n)                        //余下两个子表,后者长度小于length
        Merge(R,i,i+length-1,n-1);
}
void MergeSort(RecType R[],int n)//自底向上的二路归并排序算法
{
    int length;
    for(length=1;length<n;length=2*length)//进行logn趟归并
        MergePass(R,length,n);
}