关于排序

简单排序

通常我们在获取了一些数据之后会对这些数据进行处理,常用的操
作有排序,查找,插入,删除等等,本节给大家介绍两种基本的排
序方法:冒泡排序,插入排序、选择排序、桶排序。

冒泡排序
输入n个数,将这n个数从小到大输出。

冒泡排序的思想:以n个人站队为例,从第1个开始,依次比较相
邻的两个是否逆序(高在前,矮在后),若逆序就交换这两人,即
第1个和第2个比,若逆序就交换两人,接着第2个和第3个比,若
逆序就交换两人,接着第3个和第4个比,若逆序就交换两人,…
…,直到n-1和n比较,经过一轮比较后,则把最高的人排到最后,
即将最高的人像冒泡一样逐步冒到相应的位置。原n个人的排序问
题,转换为n-1个人的排序问题。第二轮从第1个开始,依次比较
相邻的两个人是否逆序,若逆序就交换两人,直到n-2和n-1比较。
如此,进行n-1轮后,队列为有序的队列。

从上述分析中可以看出,每进行一轮的比较之后,n个数的排序规模就转化为n-1个数的排序规模。

例如有6个元素需要排序:

​ 6 5 3 4 1 2

第一趟排序:
在这里插入图片描述
第二趟排序:

在这里插入图片描述
第三趟排序:
在这里插入图片描述

第四趟排序:

第五趟排序:
在这里插入图片描述

五趟结束后,6个整数就已经排序完成。排序过程中,大数慢慢的
往后,相当于气泡上升,所以叫冒泡排序。

归纳上述排序过程,具体实现步骤如下:

(1)读入数据存放在a数组中。

(2)对数组的第1个数据到i个数据进行一次遍历,比较相邻的前后
两个数据,如果前面数据大于后面的数据,就将两个数据交换,
最大的一个数据就“冒”到数组第i个位置。

(3)第i个位置的数确定后,i—,确定第i-1个位置,如果i为1,排
序完成,否则重复第二、第三步。

程序实现方法:用两层循环完成算法,外层循环i控制每轮要进
行多少次的比较,第1轮比较n-1次,第2轮比较n-2次,……,
最后一轮比较1次。内层循环j控制每轮i次比较相邻两个元素是
否逆序,若逆序就交换这两个元素。

【程序实现】

#include<iostream>
using namespace std;
int a[10010];
int main()
{
    int n,t;
    cin>>n;
    for(int i=1;i<=n;i++)
       cin>>a[i];
    for(int i=n-1;i>=1;i--)     //进行n-1轮冒泡 
       for(int j=1;j<=i;j++)   //每次进行i次比较 
       {
           if(a[j]>a[j+1])    //相邻元素比较,逆序就交换 
           {
              t=a[j];
              a[j]=a[j+1];
              a[j+1]=t;
           }
       }
   for(int i=1;i<=n;i++)      //输出结果 
      cout<<a[i]<<" ";
    return 0;
}

插入排序

插入排序思想:回忆一下玩扑克牌时抓牌的情景,为了方便打牌,
抓牌时一般一边抓牌一边按花色和大小插入恰当的位置,当抓完
所有的牌时,手中的牌便是有序的,这种排序方法即插入排序。

当读入一个元素时,在已经排序好的序列中,搜寻它正确的位置
,再放入读入的元素。但不该忽略一个重要的问题:在插入这个
元素前,应当先将它后面的所有元素后移一位,以保证插入位置
的原元素不被覆盖。

例如:设n=8,数组a中8个元素是: 36,25,48,12,65,43,20,58,执
行插入排序后,其数据变动情况(用[ ]括起来的数字表示已经
排好序,_表示插入的数据):

第0步:[36] 25 48 12 65 43 20 58
第1步:[25 36] 48 12 65 43 20 58
第2步:[25 36 48] 12 65 43 20 58
第3步:[12 25 36 48] 65 43 20 58
第4步:[12 25 36 48 65] 43 20 58
第5步:[12 25 36 43 48 65] 20 5
第6步:[12 20 25 36 43 48 65] 5
第7步:[12 20 25 36 43 48 58 65]
归纳上述排序过程,具体实现步骤如下:

读入数据存放在a数组中。

对第i个位置的数a[i]进行插入

从排好序的1~i-1个数从左往右遍历一遍,找到第一个比a[i]
大的数,记录下当前位置t。

将t之后的元素,t~i-1全部往后移动一个位置,第t个位置存放
a[i]。i增加1 ,继续插入,重复第二到第四步。
#include<iostream>
using namespace std;
int a[10010];
int main()
{
   int n,t,temp;
   cin>>n;
   for(int i=1;i<=n;i++)
      cin>>a[i];
   for(int i=1;i<=n;i++)      //将第i个数a[i]进行插入 
   {
      t=i;temp=a[i];              //t表示找到的合适位置,默认t为本身位置 ,如果没找到比a[i]大的数,就插入原位置,,temp表示要插入的数a[i] 
      for(int j=1;j<=i-1;j++) //在已经排好序的1~i-1中找合适位置 
      {
          if(a[j]>a[i])   //如果找到第一个比a[i]大的数就是合适位置 ,退出循环 
         {
            t=j; //a[i]就插入t这个位置,t以后的元素全部往后移动一个位置 
              break;
           }
       }
       for(int j=i-1;j>=t;j--)  //将t~i-1往后移动一个位置 
           a[j+1]=a[j];
       a[t]=temp;           //插入
    }
    for(int i=1;i<=n;i++)      //输出结果 
       cout<<a[i]<<" ";
    return 0;
}

选择排序:

选择排序的思想就是,第一小的在第一个位置,第二小的在第二个
位置,…,第n小的在第n个位置,这样就能实现n个数的排序。

算法流程:

1.每一次排序取出待排序列的第一个数x作为基准数,通过遍历找
到比x更小的数,然后交换

2.接下来在剩下的待排序列中,重复第一步。这样,有序序列不
断变长,待排序列不断变短,n趟排序后整个序列有序

【程序实现】

#include <iostream>
using namespace std;
int main()
{
    int i,j,n,k,temp,a[101];//k记录最小值的下标,temp为交换变量 
    cin>>n;
    for(i=1;i<=n;i++) cin>>a[i];
    //选择排序 
    for(i=1;i<=n;i++){      //n趟排序
         k=i;               //以待排序列的第一个数为基准数 
         for(j=i+1;j<=n;j++){ //从基准数的下一个数开始找 
            if(a[j]<a[k])    //找更小的值 
                  k=j;          //更新最小值下标 
        }
        temp=a[i]; //与待排序列第一个数交换 
        a[i]=a[k];
        a[k]=temp;
    }
    for(i=1;i<=n;i++)
     cout<<a[i]<<" ";
    return 0;
}

桶排序:

桶排序的思想,把数据当做小球,设置一系列的桶,对应的小球放
到对应的桶里。

算法流程:

1.设置一个数组,数组每一个空间就是一个桶,将数组下标做为桶
的编号。

2.遍历输入数据,并且把数据一个一个放到对应的桶里去。数据的
编号就是数据的值,比如1就放到a[1],100就放到a[100]。

3.顺序遍历整个数组,访问每个桶,将不是空的桶的编号输出,就
是一个有序的数组了。

从这个流程其实还可以看出,在输出的时候数据只会输出一次,所
以桶排序还有去重的功能

虽然桶排序比其他的排序快的多,但它的数组大小是由数据的大小
来决定的。也就是说大数据很大的时候,桶排序是不适用的。

从桶排序也可以看出,一个算法的时间复杂度与空间复杂度很难都
很优秀。要么空间换时间,要么时间换空间。

【程序实现】

#include <iostream>
using namespace std;
int main()
{
    int i,x,n,maxn=-1,a[10001]; //数组a就是一个桶,maxn记录数据的最大值
    cin>>n;  //数据个数
    for(i=1;i<=n;i++) {
        cin>>x;
        a[x]++;   //对应的数据放到对应的桶
        if(maxn<x)  maxn=x;
    }
    for(i=1;i<=maxn;i++)  //遍历桶的范围是数据的最大值,而不是数据的个数
        if(a[i]!=0)
             cout<<i<<" ";
    return 0;
}

练习

分数线划定:世博会志愿者的选拔工作正在 A 市如火如荼的进行。为了选拔最合适的人才,A 市对所有报名的选手进行了笔试,笔试分数达到面试分数线的选手方可进入面试。面试分数线根据计划录取人数的150%划定,即如果计划录取m名志愿者,则面试分数线为排名第m*150%(向下取整)名的选手的分数,而最终进入面试的选手为笔试成绩不低于面试分数线的所有选手。

现在就请你编写程序划定面试分数线,并输出所有进入面试的选手的报名号和笔试成绩。

【输入】

第一行,两个整数n,m(5 ≤ n ≤ 5000,3 ≤ m ≤ n),中间用一个空格隔开,其中n 表示报名参加笔试的选手总数,m 表示计划录取的志愿者人数。输入数据保证m*150%向下 取整后小于等于n。

第二行到第 n+1 行,每行包括两个整数,中间用一个空格隔开,分别是选手的报名号k(1000 ≤ k ≤ 9999)和该选手的笔试成绩s(1 ≤ s ≤ 100)。数据保证选手的报名号各不相同。

【输出】

第一行,有两个整数,用一个空格隔开,第一个整数表示面试分数线;第二个整数为进入面试的选手的实际人数。

从第二行开始,每行包含两个整数,中间用一个空格隔开,分别表示进入面试的选手的报名号和笔试成绩,按照笔试成绩从高到低输出,如果成绩相同,则按报名号由小到大的顺序输出。

【样例输入】

6 3

1000 90

3239 88

2390 95

7231 84

1005 95

1001 88

【样例输出】

88 5

1005 95

2390 95

1000 90

1001 88

3239 88

提示:

m150% = 3150% = 4.5,向下取整后为4。保证4 个人进入面试的分数线为88,但因为88有重分,所以所有成绩大于等于88 的选手都可以进入面试,故最终有5 个人进入面试。

题目大意 将面试选手的报名号和笔试成绩,先按照笔试成绩从高到低输出,如果成绩相同,再按照报名号由小到大的顺序输出。

思路分析 主要是排序算法的条件有所变化,不再是简单的比较分数的大小,还需要考虑分数相同时,按照报名号大小进行排序,这就需要更改排序算法的判断条件。我们可以选择两个数组来a,b来分别存储分数和报名号,a[i],b[i]就表示第i个人的分数和报名号,在交换变量的时候一定要a[i]和b[i]同时交换。

数据排序后,我们要选择k*1.5位选手进入面试,但可能会出现提示里所说的同分的情况,所以我们不能直接输出,还需要继续判断后面是否还有分数相同的选手,确定实际进入面试的选手人数。
参考代码

#include <iostream>
using namespace std;
int main()
{
  int i,j,n,k,temp,m; //temp为交换变量,m保存第k位的分数 
  int a[5005],b[5005]; //数组a存储分数,数组b存储报名号 
  cin>>n>>k;
  for(i=1;i<=n;i++) cin>>b[i]>>a[i]; //输入报名号和分数 
  for(i=1;i<=n-1;i++)  //冒泡排序
  for(j=1;j<=n-i;j++){ 
   if(a[j]<a[j+1]) //按分数从大到小 
   {
    temp=a[j]; //交换a,b 
    a[j]=a[j+1];
    a[j+1]=temp;
    temp=b[j]; 
    b[j]=b[j+1];
    b[j+1]=temp;
   }
  else if(a[j]==a[j+1]&&b[j]>b[j+1]) //分数相同,按报名号从小到大 
   {
    temp=a[j]; //交换a,b 
    a[j]=a[j+1];
    a[j+1]=temp;
    temp=b[j]; 
    b[j]=b[j+1];
    b[j+1]=temp;
   }
  }
  k=k*1.5; //进入面试人数k*1.5 
  m=a[k]; //第k位的分数 
  while(m==a[k]) k++; //可能存在与k位相同分数的选手 
  k--; //while循环结束时,k其实多加了1,要减去 
  cout<<a[k]<<" "<<k<<endl; //输出答案 
  for(i=1;i<=k;i++)
   cout<<b[i]<<" "<<a[i]<<endl;
  return 0;
}

排序就讲到这里了,还请各位大佬归我个赞。
这上面提到的冒泡排序有点耗时间,最好进新一些优化,优化的代码以及讲解如下(一个链接):
点我

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值