C++之排序算法

这是一个新手的总结。

数据的排序方法有许多,这里总结一下我所知道的。

一:选择排序。

插入排序的时间复杂度最好的情况是已经是正序的序列,只需比较(n-1)次,时间复杂度为O(n),最坏的情况是倒序的序列,要比较n(n-1)/2次,时间复杂度为O(n^2 ) ,平均的话要比较时间复杂度为O(n^2 )。

插入排序适用于数据量小的时候,当数据量增大时就会过于消耗时间。

基本思想:每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在待排序的数列最前面,直到所有的待排序数据元素排完,需要用两层循环。

方法步骤

1,读入数据,存放在一个数组中,

2,从数组a[1]~~a[n]中寻找最小(或最大)的元素与第一位的元素交换

3,从数组a[2]~~a[n]中寻找最小(或最大)的元素与第二位的元素交换

......

直到第n-1各元素和最后一个元素比较排序后结束。

代码如下:

#include<iostream>
using namespace std;
int s[10002];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)
      cin>>s[i];
    int x;//记录最小的位置
    int temp;//交换的时候用 也可以用swap
    for(int i=1;i<n;++i)//循环n-1次
    {
        x=i;
        for(int j=i;j<=n;++j)
            if(s[x]>s[j])
              x=j;
        if(x!=i)
          temp=s[i],s[i]=s[x],s[x]=temp;
        /*
        或者直接用 swap(s[i],s[x]);
        */
        
    }
    for(int i=1;i<=n;++i)
      cout<<" "<<s[i];
    return 0;
}


 二:冒泡排序

冒泡排序的平均时间复杂度为O(n^2),但当输入的数据已经是正序的序列时,只需比较(n-1)次,时间复杂度为O(n)。

同样冒泡排序适用于数据量少的时候,但大量数据时比较容易超时。

基本思想:比较相邻的两个元素的大小如果逆序就交换,一直到数列的最后,经过一次的比较能够找出最大或最小的元素并放在正确位置,然后前面剩余的元素重复这个过程,一直到比较最后两个元素。这样就像冒泡一样将最大或最小的元素放到数组的最后面。

方法步骤:(以升序排列为例)

1,读入数据,并存放在数组中,

2,比较相邻的前后两个数据,如果前面的数据大于后面的数据,交换两个数据。

3,对数组所有元素进行一次遍历后,最大的元素就好像冒泡一样跑到了数组的最后

4,对剩余的元素重复上一个的过程,直到剩下两个元素比较结束。

代码如下:

#include<iostream>
using namespace std;
int s[10001];
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;++i)
      cin>>s[i];
    for(int i=n-1;i>=1;--i)//进行n-1次循环
    {
        for(int j=0;j<i;++j)
        {
            if(s[j]>s[j+1])//判断大小
            {
                swap(s[j],s[j+1]);//交换两个元素
            }
        }
    }
    for(int i=0;i<n;++i)
      cout<<s[i]<<" ";
    return 0;
}

冒牌排序可以加一个bool变量优化,见代码:

bool ok;
    for(int i=1;i<=n-1;++i)//进行n-1次循环,另一种写法
    {
        ok=true;
        for(int j=0;j<n-i;++j)
        {
            if(s[j]>s[j+1])
            {
                swap(s[j],s[j+1]);
                ok=false;
            }
        }
        if(ok)
          break;
    }

这种优化减少了无用的循环次数。

三:插入排序

插入排序的平均时间复杂度与前两种方法相同,时O(n^2)。当待排数组有序时,此时复杂度为O(N),当待排数组是逆序时,时间复杂度为O(N^2)。同样对小数据量的运算占有优势,另一个优势是可以对大小相同的元素进行元素元素下标排序。

基本思想:回忆一下打牌时的情景,一边抓牌一边将其放到合适的位置,当结束抓牌时手牌是有序的。正式一点就是说读入一个元素,寻找它的正确位置,将其放入,当不要忘了把后面的元素都要后移一位。

方法步骤:

1,读入元素,放入数组中,

2,从第二个元素开始判断位置,将其放入,

3,循环上一个过程,直到将最后一个元素放入正确的位置。

详细见代码;

#include<iostream>
using namespace std;
int s[10001];
int main()
{
    int n,j,temp;
    cin>>n;
    for(int i=0;i<n;++i)
      cin>>s[i];
    for(int i=0;i<n;++i)
    {
        for( j=i-1;j>=0;--j)//判断位置
          if(s[j]<s[i])
            break;
        if(j!=i-1)//位置改变了
        {
            temp=s[i];//保存下来
            for(int t=i;t>j+1;--t)//把数字后移
              s[t]=s[t-1];
            /*
            for(int t=i-1;t>j;--t)
              s[t+1]=s[t];
            与上面那个意义一样。
            */
            s[j+1]=temp;
        }
    }
    for(int i=0;i<n;++i)
      cout<<s[i]<<" ";
}


四:堆排序

堆排序的时间复杂度是最短的一种方法,只有O(n),但是对数组空间的要求对于前四个要大,且只用于数字的排序。

基本思想:若待排序的元素的值在一个明显的范围内,可将有限个相同的元素存放在一个桶内,桶的序号就是元素的值。

方法步骤:

在输入元素的同时,计算相同元素个数并将其储存起来,

输出时只要将每个元素按个数将“桶”的编号输出即可。

详细见代码:

#include<iostream>
#include<cstdio>//memset(s,0,sizeof(s)) 用到
int s[101];
using namespace std;
int main()
{
    int n;
    cin>>n;
    int k;//输入的那个数
    for(int i=1;i<=n;++i)
    {
        cin>>k;
        s[k]++;//储存相同元素的个数
    }
    for(int i=0;i<=100;++i)
    {
        while(s[i]!=0)//每个元素按个数是输出,输出的同时就是有序的。
        {
            cout<<i<<" ";
            --s[i];
        }
    }
    return 0;
}

五:快速排序

快速排序的时间复杂度O(nlog2n)相较与冒泡排序来说要短。但是存在一定的不稳定性,可能时间复杂度能达到O(n^2)。

快速排序是对冒泡排序的一种改进,减少了比较和循环次数


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


方法步骤;


1,将序列分为前后两部分,取序列的中间值为分割数记为mid,


2,升序排列时前一部分找比mid大的数,后一部分找比mid小的数,


3,判断两者的位置,符合要求两者交换,


4,观察排序是否到了边界,如果未到则递归分开搜索左右区间。


目前而言快速排序法还是被认为最好的一种排序方法。

看代码前先看一张图解:

再来看代码:

#include<iostream>
using namespace std;
int s[100001];
int qsort(int l,int r)//将大的放在数组右边,将小的放在左边
{
    int i=l,j=r;//下面的计算不能直接调用l与r,后面需要判断是否排序完成需要用到l r
    int mid=s[(l+r)/2];//将当前序列的中间作为分隔数
    do
    {
        //如果选择降序排列,将下面两个while的大小判断交换即可
        while(s[i]<mid)
          ++i;//寻找左面大于中间数的数
        while(s[j]>mid)
          --j;//右面小于中间数的数
        if(i<=j)
        {
            swap(s[i],s[j]);//交换
            ++i,--j;
        }
    }
    while(i<=j);//继续寻找
    if(j>l) qsort(l,j);//寻找到最后j==l,i==r
    if(i<r) qsort(i,r);//未到两个数的边界,递归继续搜索
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)//下表表示位置,最好从1开始
      cin>>s[i];
    qsort(1,n);
    cout<<s[1];
    for(int i=2;i<=n;++i)
      cout<<" "<<s[i];
    return 0;
}


六:归并排序

归并排序的时间复杂度是O(nlog2n),但是所需要的内存空间要大于其它的排序方法,她需要一个辅助数组来完成。

基本思想:该算法是分治法(Divide and Conquer)的典型应用。先将每一个子序列排序,再将有序的子序列合并,得到完全有序的数列,方式称为二路归并。

步骤比较抽象,还是先看一张图片


解释在代码中: 

#include<iostream>
using namespace std;
int str[10001];
int a[10001];//过程使用
void msort(int s,int t)
{
    if(s==t)
      return ;
    int mid=(s+t)/2;
    msort(s,mid);//分解左序列
    msort(mid+1,t);//分解右序列
    int i=s,j=mid+1,k=s;
    while(i<=mid&&j<=t)
    {
        if(str[i]<=str[j])//寻找数小的,即生序排列
        //换成大写则降序排列
        {
            a[k]=str[i];//将每一个符合条件的数存到a[]辅助数组中
            ++i,++k;
        }
        else
        {
            a[k]=str[j];
            ++j,++k;
        }
    }
    //下面这一步的意思是将剩余的原本就符合条件的复制到a[]中排序
    while(i<=mid)//把左边的序列剩余的从s[]复制到a[]
    {
        a[k]=str[i];
        ++k,++i;
    }
    while(j<=t)//把右边的序列剩余的从s[]复制到s[]
    {
        a[k]=str[j];
        ++k,++j;
    }
    for(int i=s;i<=t;++i)//将数组返回
      str[i]=a[i];
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)
      cin>>str[i];
    msort(1,n);
    for(int i=1;i<=n;++i)
      cout<<str[i]<<" ";
    return 0;
}


欢迎评论指出错误,大家一起进步。

主要参考资料

(C++版)信息学奥赛一本通


 







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值