六个基础排序算法(选择、冒泡、插入、希尔、归并、快速)

写基础排序算法的原因

 一是因为这是基础,然后再就是我有时候闲的没事会默想,然后就有的细节不确定了,我又是一个比较追求完美的人,我就又会去看。然后过一段时间有的细节又记不住了,周而复始。思路当然晓得,但是不能一次把细节都想对,就感觉很不完美。
 所以我要写在我的博客上面,顺便把经常记不住的“不完美”细节标注(就直接标注成注释了)一下。

说明

  • 引用了一些第四版《算法》的观点。(比如一些算法优缺点)
  • 以从小到大排序为例。
  • 算法稳定性:算法能够保存数组中重复元素的相对位置,称为稳定。
  • 基础的交换操作被这几个算法都调用了,代码如下:
//基础交换操作
void exchange(int *a, int i, int j)
{
    a[i] ^= a[j];
    a[j] ^= a[i];
    a[i] ^= a[j];
}

一、选择排序

 主要思路就是再乱序部分选择一个最小的放到有序的部分。第一次找一个最小的放第一个,第二次除了第一个(有序)再找一个最小的放在第二个,第三次除了第一第二个(有序)再找一个最小的放在第三个,……
 通过两个循环实现,第一个循环代表第几次找,第二个循环用来找到乱序部分的最小数。
 不稳定,时间复杂度N方,空间复杂度1(常数)。
 代码如下:

void selectSort(int *a, int n)
{
    int sub = 0;
    for(int i=0;i<n;++i)
    {
        sub = i;
        for(int j=i; j<n; ++j)
        {
            if(a[sub]>a[j])  //这里是sub和j比较,经常写错成i
            {
                sub = j;
            }
        }
        if(sub != i)        //交换之前,最好自检,否则sub等于i时,出错
       	    exchange(a,sub,i);
    }
}

二、冒泡排序

 主要思路从头开始,向尾部比较、交换,把较大的数值交换到尾部去,以此实现从小到大的排列顺序。
 通过两个循环实现。第一个循环可以看作是比较遍数,n个值比较n-1遍;第二个循环用来把较大的数值“浮动”到尾部去,注意第二个循环中不需要与有序部分作比较,所以判断条件为j<n-1-i,i在这里是指尾部的有序个数。
 冒泡排序慢,但稳定,空间复杂度N方。

void bubbleSort(int *a, int n)
{
    for(int i=0; i<n-1; ++i)
    {
        for(int j=0; j<n-1-i; ++j)
        {
            if(a[j]>a[j+1])
            {
                exchange(a,j,j+1);
            }
        }
    }
}

三、插入排序

 主要思路是把一个数值插入到有序的部分,从有序部分的尾部开始比较,如果小于就交换,然后往前比较交换,一直到这个数值大于有序数值,就是它正确的位置了。
 依旧是两个循环。第一个循环是确定要插入的数值,这个数值前面是有序数列,后面是乱序数列。第二个循环用来比较和交换。
 适合对已经部分有序的数列进行排序;稳定,时间复杂度介于N和N方之间,空间复杂度为常数。
 代码如下:

void insertSort(int *a, int n)
{
    for(int i=0; i<n; ++i)
    {
        for(int j=i; j>0; --j)
        {
            if(a[j]<a[j-1])
            {
                exchange(a,j,j-1);
            }
        }
    }
}

四、希尔排序

 希尔排序就是进行了很多遍插入排序,不过存在一个增量,跳跃式的分组进行插入排序。虽然同样是“插入排序”的原理,但当数据量相当大的时候,希尔排序的效率应该是大于插入排序的,数据量小的时候不确定。另外增量的计算方法一般通过经验确定,这里用了第四版《算法》里的增量计算方式。
 适合对已经部分有序的数列进行排序,且数据量相当大。不稳定,时间复杂度为NlogN,空间复杂度为常数。
 代码如下:

void hillSort(int *a, int n)
{
    int h=1;
    while(h<n/3)
        h = 3*h+1;


    while(h>=1)
    {
        for(int i=h; i<n; i++)		//++i
        {
            for(int j=i; j>=h; j-=h)	//j>=h,注意等号,因为每一遍要与a[0]比较
            {
                if(a[j]<a[j-h])
                {
                    exchange(a,j,j-h);
                }
            }
        }
        h /= 3;
    }
    qDebug()<<count;
}

五、归并排序

 归并排序是分而治之的思想,这里写从上至下的递归算法,从下之上的虽然写起来简约但是感觉不直观。主要思路是每次将数列对半分,分到不可分为止,然后按对半分的顺序依次合并,在合并的过程中排序。在合并过程中用到了额外的一些空间,用来辅助。
 稳定,时间复杂度NlogN,空间复杂度为N。如果稳定性很重要而空间又不是问题,归并排序很好,可以考虑。

void merge(int *a, int low, int mid, int hi)
{
    int i=low;
    int j=mid+1;

    int *auk;
    auk = (int*)malloc(sizeof(int)*ARRAYNUM);//辅助数组这里是局部变量,其实声明成全局变量更方便。
    for(int k=low; k<=hi; ++k)
    {
        auk[k] = a[k];
    } 
    
    //这里low和hi数组长度无关,所以注意k<=hi,要有等号
    for(int k=low; k<=hi; ++k)
    {
        if(i>mid)
            a[k] = auk[j++];
        else if(j>hi)
            a[k] = auk[i++];
        else if(auk[i]>auk[j])	//辅助数组比较,不是原数组
            a[k] = auk[j++];
        else
            a[k] = auk[i++];
    }
    free(auk);
}

void lumpSort(int *a, int low, int hi)
{
    if(low >= hi)
        return;

    int mid = low+(hi-low)/2;
    lumpSort(a, low, mid);
    lumpSort(a, mid+1, hi);
    merge(a, low, mid, hi);
}

六、快速排序

 大名鼎鼎的快速排序,相当通用的算法。快速排序也是分治的思想。主要思路:在乱序部分中,选择一个基准值(这里选的是第一个),将比基准值小的数值放到左边,比基准值大的数值放到右边。然后分别以左边(较小的部分)和右边(较大的部分)作为上面描述中的乱序部分,继续选择基准值调整位置。
 这里写的是递归的快速排序,因为感觉递归比较直观。
 不稳定,时间复杂度为NlongN,空间复杂度为lgN。快速排序比较快的原因是内循环中指令比较少,是很多情况下的最佳选择。

int partition2(int *a, int low, int hi)
{
    int key = a[low];
    int i=low;
    int j=hi+2;

    while(1)
    {
        while(key>a[++i])
            if(i>hi)
                break;
        while(key<a[--j])
            if(j<low)
                break;
        if(i>=j)
            break;
        exchange(a,i,j);
    }
    exchange(a,low,j);	//交换low和j,我经常写成low和i。
    return j;
}

void quickSort(int *a, int low, int hi)
{
    if(low >= hi)
        return;

    int j = partition2(a, low, hi);
    quickSort(a, low, j-1);	//注意这里两边继续递归都不再包括已经排序好的基准值a[j]了
    quickSort(a, j+1, hi);
}

小结

 我觉得了解主要思路,然后写出来算法,有些许细节不对应该没关系,再改就是了,但是我可能是有点轻微强迫症。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值