数据结构:排序算法总结

排序方式

排序的稳定性:假设 ki=kj1in1jnij ,且在排序前的序列中 ri 领先于 rj (即i < j)。如果排序后 ri 仍领先于 rj ,则称所用的排序方法是稳定的;反之,是不稳定的。
内排序和外排序:内排序是在排序整个过程中,待排序的所有记录全部被放置在内存中。外排序是由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进行。
影响内排序的3个方面:
1,时间性能:时间开销主要在于(比较和移动)
2,辅助空间:除了存放待排序所占用的存储空间之外,执行算法所 需要的其他存储空间
3,算法的复杂性:算法本身的复杂度(非时间复杂度)


按算法复杂度分类:
简单算法:冒泡排序,简单选择排序,直接插入排序
改进算法:希尔排序,堆排序,归并排序,快速排序

排序方法最好时间复杂度最差时间复杂度平均时间复杂度空间复杂度稳定性
冒泡排序O( n2 )O( n2 )O( n2 )O(1)稳定
改进的冒泡排序O( n ) O(n2)O( n2 )O(1)稳定
简单选择排序O( n2 )O( n2 )O( n2 )O(1)不稳定
直接插入排序O( n ) O(n2)O( n2 )O(1)稳定
希尔排序O( n1.3 )O( n2 )O( log2n )~O( n2 )O(1)不稳定
堆排序O( nlog2n )O( nlog2n )O( nlog2n )O(1)不稳定
归并排序(递归)O( nlog2n )O( nlog2n )O( nlog2n )O( n+log2n )稳定
归并排序(迭代)O( nlog2n )O( nlog2n )O( nlog2n )O( n ) 稳定
快速排序 O(nlog2nO( n2 )O( nlog2n )O( log2n )~O( n ) 不稳定

代码实现:

冒泡排序实现

void Bubblesort1(SqList *L)
{
    int i,j;
    for(i=1;i<L->length;i++)
        for(j=L->length-1;j>=i;j--)
        {
            if(L->r[j]>L->r[j+1])
            {
                swap(L,j,j+1);
            }
        }
}

改进的冒泡排序

void Bubblesort2(SqList *L)
{
    int i,j;
    bool flag=true;
    for(i=1;i<L->length && flag;i++)
        for(j=L->length-1;j>=i;j--)
        {
            flag=false;
            if(L->r[j]>L->r[j+1])
            {
                swap(L,j,j+1);
                flag=true;
            }
        }
}

简单选择排序

在n-i+1个记录中选取关键字最小的记录作为有序序列的第i个记录。

void SelectSort(SqList *L)
{
    int i,j,min;
    for(i=1;i<L->length;i++)
    {
        min=i;
        for(j=i+1;j<=L->length;j++)
        {
            if(L->r[min]>L->r[j])
                min=j;
        }
        if(i!=min)
            swap(L,i,min);
    }

}

直接插入排序

将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录数增1的有序表。

void InsertSort(SqList *L)
{
    int i,j;
    for(i=2;i<=L->length;i++)
    {
        if(L->r[i]<L->r[i-1])
        {
            L->r[0]=L->r[i];
            for(j=i-1;L->r[j]>L->r[0];j--)
            {
                    L->r[j+1]=L->r[j];
            }
            L->r[j+1]=L->r[0];
        }
    }
}

希尔排序

将原本有大量记录数的记录进行分组,分割成若干个子序列,此时每个子序列待排序的记录个数就比较少了,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时(只是基本有序时),再对全体记录进行一次直接插入排序

void ShellSort(SqList *L)
{
    int i,j;
    int increment=L->length;
    do
    {
        increment=increment/3+1;
        for(i=increment+1;i<=L->length;i++)
        {
            if(L->r[i]<L->r[i-increment])
            {
                L->r[0]=L->r[i];
                for(j=i-increment;j>0 && L->r[0]<L->r[i];j-=increment)
                    L->r[j+increment]=L->r[j];
                L->r[j+increment]=L->r[0];
            }
        }
    }
    while(increment>1);
}

根据代码可知,划分间隔为increment,每隔increment的数进行大小比较排序,排序完成后,increment取之前的三分之一,直到increment不满足大于1的条件(由increment=increment/3+1 可以保证增量序列最后一个增量值为1),循环结束。
在这里增量的选择对排序效率有极大影响,大量研究表明,较好的增量序列为:
Δ[k]=2tk+11(0kt[log2(n+1)])

堆排序

即对简单选择排序进行的一种改进,利用堆进行排序的方法
此处堆是具有以下性质的完全二叉树
每个结点的值都小于或等于其左右孩子结点的值,称为大顶堆

                  90
                 /  \
               70    80
              / \    / \
            60  10 40  50
            / \
          30   20

或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

                  10
                 /  \
                20   70
               / \   / \
             30  50 90 80
             / \
            60  40

以层次遍历的方式给结点从1开始编号,则有

kik2ikik2i+1


kik2ikik2i+1

其中 1in2

在大顶堆的情况下,此时,整个序列的最大值就是堆顶的根节点,将它移走(末尾元素放到根节点处),再将剩余元素重新构造一个大顶堆,一次循环,可以得到一个有序序列。
代码实现:

void HeapSort(SqList *L)
{
    int i;
    for(i=L->length/2;i>1;i--)
        HeapAdjust(L.i.L->length); //把L中的r构建成一个大顶堆
    for(i=L->length;i>1;i--)
    {
        swap(L,1,i);//将堆顶记录和当前未经排序子序列的最后一个记录交换
        HeapAdjust(L,1,i-1);//将L->r[1..i-1]重新调整为大顶堆
    }
}

前一个循环是构建一个大顶堆,后一个循环是,把根节点(即最大的值和尾元素互换,然后1至i-1个元素重新构建大顶堆,第i个为最大值)依次循环,最终得到由小到大的有序表

void HeapAdjust(SqList *L,int s,int m)
{
    int temp,j;
    temp=L->r[s];
    for(j=2*s;j<=m;j*=2)
    {
        if(j<m && L->r[j]<L->r[j+1])
            ++j;
        if(temp>=L->r[j])
            break;
        L->r[s]=L->r[j];
        s=j;
    }
    L->r[s]=temp;
}

时间复杂度分析:他的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上,。构建堆得时间复杂度为O(n),正式排序时,第i次堆顶记录重建堆时间复杂度均为O( log2n ),所以总的时间复杂度为O( nlog2n )。

归并排序

归并排序原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2] ([x]表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,… …,如此重复,直到得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。

代码实现
首先采用递归的方法实现:

void Merge( int SR[],int TR[],s,m,t)
{
    int i=s.j=m+1;
    int count=s;
    while(i<m+1&&j<t+1)
    {
        if(SR[i]<SR[j])
            TR[count++]=SR[i++];
        else
            TR[count++]=SR[j++];
    }
    if(i<m+1)
        while(i<m+1)
            TR[count++]=SR[i++];
    if(j<t+1)
        while(j<t+1)
            TR[count++]=SR[j++];
}
//将SR[s...t]归并排序为TR1[s...t]
void MSort(int SR[],int TR1[],int s,int t)
{
    int m;
    int TR2[MAXSIZE+1];//临时排序空间
    if(s==t)
        TR1[s]=SR[s];
    else
    {
        m=(s+t)/2;
        MSort(SR,TR2,s,m);
        MSort(SR,TR2,m+1,t);
        Merge(TR2,TR1,s,m,t);
    }
}
void  MergeSort(SqList *L)
{
    MSort(L->r,L->r,1,L->length);
}

非递归的算法实现,采用迭代的方法:

void 

快速排序

快速排序的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

代码实现:

//交换顺序表L中子表的记录,使枢轴记录到位,并返回其所在位置
//此时在它之前(后)的记录均不大(小)于它
int Partition(SqList *L,int low,int high)
{
    int pivotkey ;
    pivotkey=L->[low];
    while(low<high)
    {
        while(low<high && L->r[high]>=pivotkey)
            high--;
        swap(L,low,high);
        while(low<high && L->r[low]<=pivotkey)
            low++;
        swap(L,low,high);
    }
    return low;
}
//对顺序表L中的子序列L->r[low...high]做快速排序
void QSort(SqList *L,int low,int high)
{
    int pivot;
    if(low<high)
    {
        pivot=Partition(L,low,high);
        QSort(L,low,pivot-1);
        QSort(L,pivot+1,high);
    }
}
//对顺序表L做快速排序
void QuickSort(SqList *L)
{
    QSort(L,1,L->length);
}

基数排序

基数排序:

希尔排序相当于直接插入排序的升级,同属于插入排序类
堆排序相当于简单选择排序的升级,同属于选择排序类
快速排序相当于冒泡排序的升级,同属于交换排序类

这里写图片描述

reference:《大话数据结构》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值