2021-03-25

这里是想不出名字的标题

排序

桶排序

给5分、3分、5分、2分、8分这五个分数排序
我们只需要借助一个一维数组就可以解决这个问题。
首先我们需要申请一个大小为11的数组 int a[11]。现在有了11个变量,编号从a[0]a[10]。刚开始的时候,我们将a[0]a[10]都初始化为0,表示这些分数还没有人得过。例如:a[0]等于0就表示目前还没有人得过0分。a[1]等于0就表示目前还没有人得过1分……a[10]等于0表示目前还没有人得过10分。
下面开始处理每一个人的分数。
第一个人的分数是5分,将对应的a[5]的值在原来的基础增加1,即将a[5]的值从0改为1,表示5分出现过一次。
第二个人的分数是3分,将对应的a[3]的值在原来的基础增加1,即将a[3]的值从0改为1,表示3分出现过一次。
**注意啦!**第三个人的分数也是5分,将对应的a[5]的值在原来的基础增加1,即将a[5]的值从1改为2,表示5分出现过两次。
……
a[0]~a[10]中的数值其实就是0分到10分每个分数出现的次数。接下来,只需要将出现过的分数打印出来就可以啦,出现几次就打印几次,具体如下:
a[0]为0,表示“0”没有出现过,不打印。
a[1]为0,表示“1”没有出现过,不打印。
a[2]为1,表示“2”出现过1次,打印 2。
a[3]为1,表示“3”出现过1次,打印 3。
a[4]为0,表示“4”没有出现过,不打印。
a[5]为2,表示“5”出现过2次,打印 5 5。
a[6]为0,表示“6”没有出现过,不打印。
a[7]为0,表示“7”没有出现过,不打印。
a[8]为1,表示“8”出现过1次,打印 8。
a[9]为0,表示“9”没有出现过,不打印。
a[10]为0,表示“10”没有出现过,不打印。
最终屏幕输出“2 3 5 5 8”,完整代码如下:

#include<stdio.h>
int main()
{
    int a[11],i,j,t;
    for(i = 0 ; i <= 10 ; i ++) a[i] = 0;//初始化为0

    for(i = 1 ; i <= 5 ; i ++){//循环读入5个数
        scanf("%d",&t);//把每一个数读到变量t中
        a[t] ++;//进行计数
    }

    for(i = 0 ; i <= 10 ; i ++){//依此判断a[0]~a[10]
        for(j = 1 ; j <= a[i] ; j ++)printf("%d ",i);//出现几次就打印几次
    }

    return 0;
}

如果想从大到小排序,只需要将for(i=0;i<=10;i++)改为for(i=10;i>=0;i–)就可以了
这种算法暂且称为“桶排序”。因为其实真正的桶排序要比这个复杂一些,但是目前应该够用了。
这种算法好比有11个桶,编号从0~10。每出现一个数,就在对应编号的桶中放一个小旗子,最后只要数一数每个桶中有几个小旗子就好啦。
可以尝试输入n个0~1000之间的整数,将它们从大到小排序。

#include<stdio.h>
int main()
{
    int a[1001],n,i,j,t;
    for(i = 0 ; i <= 1000 ; i ++) a[i] = 0;//初始化为0

    scanf("%d",&n);//输入一个数n,表示接下来有n个数

    for(i = 1 ; i <= n ; i ++){//循环读入n个数,进行桶排序
        scanf("%d",&t);//把每一个数读到变量t中
        a[t] ++;//进行计数,对编号为t的桶放一个小旗子
    }

    for(i = 1000 ; i >= 0 ; i --){//依此判断编号1000~0的桶
        for(j = 1 ; j <= a[i] ; j ++)printf("%d ",i);//出现几次就打印几次
    }

    return 0;
}

最后说时间复杂度问题。
代码中第6行的循环一共循环了m次(m为桶的个数),第9行的代码循环了n次(n为待排序数的个数),第14行和第15行一共循环了m+n次。所以整个排序算法一共执行了m+n+m+n次。
因此时间复杂度为O(2(m+n))。常数忽略,即O(M+N)。
(;′⌒‘)十分浪费空间,且不能又存整数又存小数,并且不能分数输出对应的名字

冒泡排序

冒泡排序基本思想:每次比较两个相邻的元素,如果他们顺序错误就把他们交换过来。
例如需要将12 35 99 18 76这5个数进行从大到小的排序。既然是从大到小排序,也就是说越小越靠后
首先比较第1位和第2位的大小,现在第1位是12,第2位是35。12比35小,因此需要交换两个数的位置。
……
每次都是比较相邻的两个数,如果后面的数比前面的数大就交换两个数的位置。一直比较下去直到最后两个数比较完毕后,最小的数就在最后一个了。就如同气泡,一步一步往后“翻滚”,直到最后一位。所以这个排序的方法叫做“冒泡排序”。
现在,排序只将5个数中最小的一个归位了。完成了“一趟”。剩下的4个数字再用相同方式一一归位。
总结:如果有n个数进行排序,只需将n-1个数归位,也就是说要进行n-1趟操作。而“每一趟”都需要从第1位开始进行相邻两个数的比较,将比较小的一个数放在后面,比较完毕后向后挪一位继续比较下面两个相邻数的大小,重复此步骤,直至最后一个尚未归位的数,已经归位的数则无需再进行比较。
ヾ(❀ω)ノ゙每次和同学拍毕业照的时候就总会大家被换来换去的,麻烦死了。可能想到这个算法的人也是因为拍集体照才想的这种算法吧。
示例:

#include<stdio.h>
int main()
{
    int a[100],i,j,t,n;
    scanf("%d",&n);//输入一个数n,表示接下来有n个数
    for(i = 1 ; i <= n ; i ++)scanf("%d",&a[i]);//循环读入n个数到数组a中
    //冒泡排序的核心部分
    for(i = 1 ; i <= n - 1 ; i ++){//n个数排序,只用进行n-1趟
        for(j = 1 ; j <= n - i ; j ++){
            //从第1位开始比较直到最后一个尚未归位的数
            if(a[j] < a[j+1]){//比较大小并交换
                t = a[j];
                a[j] = a[j+1];
                a[j+1] = t;
            }
        }
    }
    for(i = 1 ; i <= n ; i ++)printf("%d ",a[i]);

    return 0;
}

将上面的代码稍微修改就能解决输出相应名字的问题。

#include<stdio.h>
struct student{
    char name[21];
    int score;
};//创建一个结构体来存储得分和姓名

int main()
{
    struct student a[100],t;
    int i,j,n;
    scanf("%d",&n);//输入一个数n,表示接下来有n个数
    for(i = 1 ; i <= n ; i ++)scanf("%s %d",a[i].name,&a[i].score);//循环读入
//从高到低排序
    for(i = 1 ; i <= n - 1 ; i ++){
        for(j = 1 ; j <= n - i ; j ++){
            if(a[j].score < a[j+1].score){//比较分数大小并交换
                t = a[j];
                a[j] = a[j+1];
                a[j+1] = t;
            }
        }
    }

    for(i = 1 ; i <= n ; i ++)printf("%s\n",a[i].name);

    return 0;
}

冒泡排序的核心部分是双重嵌套循环。时间复杂度是O(N^2)。

快速排序

假设现在对“6 1 2 7 9 3 4 5 10 8”这10个数进行排序。
首先在这个序列中随便找一个数作为基准数(就是一个用来参照的数)。为了方便,就让第一个数6作为基准数。接下来,需要将比基准数大的数都放在6的右边,比基准数小的数都放在6的左边,就会变成类似于这种排列

3 1 2 5 4 6 9 7 10 8

在初始状态下,数字6在序列的第1位。目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。
方法很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从找一个小于6的数,再从找到一个大于6的数,然后交换它们。这里可以用两个变量i和j,分别指向序列最左边和最右边。给这两个变量取一个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵j指向序列的最右边(即j=10),指向数字8。
首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要。哨兵j一步一步地向左挪动(即j–),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个大于6的数停下来。最后哨兵j停在了数字5前面,哨兵i停在了数字7面前。
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下:

6 1 2 5 9 3 4 7 10 8

到此,第一次交换结果。接下来哨兵j继续向左挪动(再次友情提醒,每次必须是哨兵j先出发)。发现4之后停下来。哨兵i也继续向右挪动,他发现了9之后停下来。此时再次进行交换,交换之后的序列如下:

6 1 2 5 4 3 9 7 10 8

第二次交换结束,“探测”继续。哨兵j继续向左挪动, 他发现了3 (比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动。!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。

3 1 2 5 4 6 9 7 10 8

到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6, 6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。
OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3 1 2 5 4”,右边的序列是"9 7 10 8"。
再用同样的方式对左右两边的序列进行排序。
快速排序的每一轮处理其实就是将这一轮的基准数归位,直至所有的数都归位为止,排序就结束了。
快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相 邻的数之间进行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的, 都是O(N^2),它的平均时间复杂度为0 (NlogN)。其实快速排序是基于一种叫做“二分”的思想。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值