排序算法C语言版(下)

排序算法(上))中,介绍了时间复杂度为 O ( n 2 ) O(n^2) O(n2)的三种排序算法,即选择排序、冒泡排序、插入排序。接着上篇文章,本篇主要讲两种应用更加广泛的、时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)的排序算法,即快速排序和归并排序算法,归并排序有两种思路,也是本文要讲的目的之一,即对比递归思想和迭代思想。

1. 快速排序

快速排序是所有排序算法中速度最快的一种,也属于原地排序(在同一个数组中完成排序任务),本文重点分析:

快速排序的基本思想是:通过一趟排序将要排序的序列分割成独立的3个部分,即左部,基准值,右部。其中左部的所有数据都比基准值小,右边的所有数据都比基准值大。然后再按此方法分别对左部和右部进行快速排序。因此,整个过程可以用递归的思路完成。

具体的算法框架分为两个子步骤,分别用两个子函数来完成:
1)、确定基准值,如果基准值的位置不在中间(这里的中间不是值物理位置中间,而是值在逻辑位置中间,逻辑位置中间就是说基准值左边的不论有多少元素,元素值都比基准值小,右边不管有多少个元素,元素值都比基准值大),要想办法把它移动到“中间”位置。

int LctPivK(int Arr[],int i,int j)//确定基准值的子函数
{
    int pivkey; //此变量用于缓存基准值
    pivkey = Arr[i];
    while(i<j) //i为头部索引,j为尾部索引
    {
       while(i<j&&Arr[j]>=pivkey) // 只要头部索引与尾部索引不重合就一直循环
            j--; //当满足尾部索引处的值比基准值大的时候,就将尾部索引往头部移动
       Arr[i] = Arr[j]; //否则就把尾部的值移动到头部
       while(i<j&&Arr[i]<pivkey) 
            i++; //当满足头部索引处的值比基准值小的时候,就将尾部索引往尾部移动
       Arr[j] = Arr[i]; //否则就把头部的值移动到尾部
    }
    Arr[i] = pivkey; //将基准值放回数组中,并返回基准值的索引值
    return i;
}

上面的函数把原始数列的首个值用作基准值,然后把该值移动到数组的“逻辑中间”位置,移动的具体思路是:先将首个值缓存到一个变量中,然后从数组的两头开始分别跟基准值比较大小,如果靠近尾部的数据比基准值大那不做处理,将尾部的那个数组索引值减小往头部方向移动,一旦尾部出现比基准值小的元素,就将该值挪到头部索引处(一开始的头部索引就是0,已经将第零个元素值作为基准值缓存起来了,所以不怕它被尾部的值覆盖掉),这是尾部索引处的值刚好重复了一次,然后从头部索引开始,将头部值与基准值比较,如果比基准值小不做处理将头部索引往尾部移动,否则将头部索引值复制到尾部索引,就这样重复比较直到头部索引与尾部索引指到同一个位置为止,然后将该位置的值更新为基准值,并返回此时的基准值的索引。

2)、编写递归函数,其中递归函数又有两个子部分组成,即:终止条件(要确定什么时候停止递归循环)和递归循环体(此算法中,循环体承担了分割数据的作用)。

void Quick_sort(int *Arr,int lft,int rht)
{
    int k;
    if(lft<rht)
    {
        k = LctPivK(Arr,lft,rht);
        Quick_sort(Arr,lft,k-1);
        Quick_sort(Arr,k+1,rht);
    }
}

上面的函数中,第一个形参为数组首地址,第二、三个分别表示左部、右部的索引,如果长度为n的数组Arr,调用快速排序时:”Quick_sort(Arr,0,n-1); “即可。上面的递归函数的终止条件是 左索引>=右索引。否则就一直分割数组直到左右索引值相等。

2. 归并排序

归并排序的基本思想是:将两个有序的子序列合并为一个子序列,如果给了一个长度为n的无序的数列那就先做分割,将数列分割为n个长度为1的子数列,然后一段一段地合并,合并的过程中如果考虑大小逻辑关系,就可以得到一个有序的序列。归并排序的重点是将两个有序的子序列合并的完成,具体实现如下:

void Merge(int a[],int b[],int lw,int md,int hg) //注释1
{
    int i,j,k;
    i = lw;
    j = md+1;
    k = lw;
    while(i<=md&&j<=hg) //注释2
    {
        if(a[i]<a[j])
        {
            b[k++] = a[i++];
        }
        else
        {
            b[k++] = a[j++];
        }
    }
    while(i<=md) //注释3
        b[k++] = a[i++];
    while(j<=hg)
        b[k++] = a[j++];
}

此函数的功能是将一个数组的两段合并到另一个数组中。根据函数体的注释位置,此函数分为三小部分:
注释1:解释以下形参的意义,a数组是提供原始数据的数组,b数组用于接收合并后的有序数据,后面三个形参均与a数组有关,此函数是合并a数组的两段有序子序列,所以lw表示第一段的起始位置,md表示第一段的结束位置,md+1表示第二段的起始位置,gh表示第二段的结束位置(注意,两段在同一个数组中必须挨着,同时两端的首尾位置都有用,都可以放数据,注意调用的时候小心数据溢出)。
注释2:从此处开始是一个循环体,两段长度不同,所以在公共长部分要一个一个比较两段的元素,然后放到b数组的同位置处。
注释3:两段数据不一样长的话总有一段是长的,那就将长的那一段在上面循环剩下的那部分加到b后面。
注意,如果合并的是a数组的4~7 ~ 11 部分,合并后在b中也是4 ~ 11 的位置!

归并排序的实现方式有两种可以用递归的思想,也可以用迭代的思想,本文将分别讨论这两种思路的实现:
1)、递归法

void Msort(int s[],int t[],int ss,int tt)
{
    int r[tt-ss+1],m; //注释1 
    if(ss==tt)
        t[ss] = s[ss];
    else
    {
        m = (ss+tt)/2; //注释2
        Msort(s,r,ss,m);
        Msort(s,r,m+1,tt);
        Merge(r,t,ss,m,tt);
    }
}

函数的思路为:将无序的序列S[ss:tt]分成等长的两部分,这里的也要确定一个中间位置,并且此处的中间的位置就是数组的物理中间位置,分成两部分后再分别对两部分划分,划分至每段只有一个元素位置,然后调用合并子函数,合并为有序的数列。
解释以下形参:s数组为待排序的数组,t数组为最终排序的结果,ss、tt分别代表s数组的起始、终止位置。
注释1:在此处定义了一个辅助的数组,注意该数组的长度设置不能小于待排序的数组的长度;
注释2:确定了分段位置,然后递归先递归分割,到最后再归并。

总的来说,函数的使用方法是 Msort(num,num,0,n-1);

关于三种排序算法的比较:就其时间复杂度来看三者差别不大,但在空间复杂度方面,快速排序算法的优越性是很明显的,从上面的程序来看,递归归并排序不属于原地排序,占用的空间大小明显高于快速排序,同样地迭代归并排序的空间占用更大,三者中最大,鉴于此,快速排序凭借快速、稳定、空间复杂度小的有点用途十分广泛!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值