信息学竞赛基础之结构体排序

此博客原文地址: https://www.cnblogs.com/BobHuang/p/12418236.html

一、冒泡排序引入

首先我们可以来看一个题目TZOJ1454:三个数排序
给定三个数,要求你对其进行从小到大的排序。
解题思路:
我们可以先把a和b进行排序(如果a比b大就进行交换),之后把b和c进行排序(如果b比c大就进行交换)。这样你c一定是最大值了,所以要对a和b进行排序(如果a比b大就进行交换)

#include <bits/stdc++.h>
using namespace std;
int main() {
    int a, b, c, t;
    char d;
    //字符d是为了两个数字之间的逗号
    cin >> a >> d >> b >> d >> c;
    if (a > b) {
        //三个数字才能完成交换
        //先把a复制到空杯子t,再把b复制到a,最后把t复制到b
        t = a; a = b; b = t;
    }
    if (b > c) {
        t = b; b = c; c = t;
    }
    if (a > b) {
        t = b; b = a; a = t;
    }
    cout << a << " " << b << " " << c << "\n";
    return 0;
}

冒泡排序是怎么样一种算法呢,就是对如上你设计的算法的一个扩展。经过a和b以及b和c的一趟排序,我们让c最大了,但是a和b的大小我们是无法保证的,所以要再进行一趟。我们经过一趟排序可以确定当前趟最后一个数所在位置,所以我们共需要n-1趟。每次比较的下标呢,都从第一个开始,到当前的趟的最后一个。
我们可以对如上代码进行循环化改造。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int n = 3, a[3];
    char c;
    for (int i = 0; i < n; i++) {
        if (i == 0)
            cin >> a[i];
        else
            cin >>c>> a[i];
    }
    //需要进行n-1趟
    for (int i = 0; i < n - 1; i++) {
        //i=0,需要比较到n-1,循环需要进行到n-2
        //i=1,需要比较到n-2,循环需要进行到n-3
        //我们需要比较的元素从0到第n-i-2,循环需要进行到n-i-1
        for (int j = 0; j < n - 1 - i; j++) {
            //相邻两数进行比较
            if (a[j] > a[j + 1]) {
                //不满足大小交换,swap函数等同之前的借助变量t交换
                swap(a[j], a[j + 1]);
            }
        }
    }
    for(int i=0;i<n;i++) {
        cout<<a[i];
        if(i==n-1)cout<<"\n";
        else cout<<" ";
    }
    return 0;
}

这个排序方式的动画是这样的
冒泡排序
其他排序的可视化演示地址:https://www.bobhuang.xyz/sort/

二、结构体排序

我们可以来看一个题目,TZOJ1741通讯录,我们可以设置这样一个结构体数组,需要我们存储的有tzojid,电话,籍贯以及出生年月,我们可以把他们都定义下来,因为排序也只用到了这些元素

struct Person
{
	//tzojid
	string tzoj;
	//电话
	string phone;
	//籍贯
	string address;
	//出生年月
	int y,m,d;
} a[101];

排序的规则有哪些呢?首先根据地名作为第一关键字进行字典序排序,由于每年老乡之间都要为队友庆祝生日,因此将生日作为第二关键字进行递增排序。也许你在老乡之间能够找到一个非常有缘的队友即你们的生日是同一天,那么就根据tojid作为第三关键字进行字典序排序,由于tzojid是唯一的,因此总能排序。
如何进行排序呢,我们可以将所有的情况全都封装到一个cmp函数里,这样我们就可以少写交换了。可以得到如下代码,恭喜你完成了这个题目。可以仔细学习下cmp函数的逻辑书写,注意冒泡的循环千万不能写错,否则你是无法完成循环的。

#include <bits/stdc++.h>
using namespace std;
struct Person
{
    //tzojid
	string tzoj;
	//电话
	string phone;
	//籍贯
	string address;
	//出生年月
	int y,m,d;
} a[101];
//调用函数,需要传入这两个Person,类型为结构体定义的类型
int cmp(Person a, Person b)
{
    //籍贯不相等,比较籍贯
    if (a.address != b.address)
        return a.address < b.address;
    //籍贯相等,月份不相等,比较月份
    if (a.m != b.m)
        return a.m < b.m;
    //籍贯相等,月份相等,日子不相等,比较日子
    if (a.d != b.d)
        return a.d < b.d;
    //籍贯相等,月份相等,日子相等
    //但tzoj肯定不相等,比较tzojid,小的在前
    return a.tzoj < b.tzoj;
}

int main()
{
    int n;
    cin >> n;
    char e;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i].tzoj >> a[i].phone >> 
        a[i].address >> a[i].y >> e >> a[i].m 
        >> e >> a[i].d;
    }
    //进行n-1趟的循环
    for (int i = 0; i < n - 1; i++)
    {
        //循环需要排序的下标
        for (int j = 0; j < n - 1 - i; j++)
        {
            //小于返回的是true,大于也就是不满足(!)需要进行交换
            if (!cmp(a[j], a[j + 1]))
                swap(a[j], a[j + 1]);
        }
    }
    //循环输出
    for (int i = 0; i < n; i++)
    {
        cout << a[i].tzoj << " " << a[i].phone << " " 
        << a[i].address << " " << a[i].y << "-" << a[i].m 
        << "-" << a[i].d << endl;
    }
    return 0;
}

三、扩展

直接调用sort函数进行排序,我们仅需要给他的是需要排序的地址,比如上述代码可以简写为

#include <bits/stdc++.h>
using namespace std;
struct Person
{
    //tzojid
	string tzoj;
	//电话
	string phone;
	//籍贯
	string address;
	//出生年月
	int y,m,d;
} a[101];
//调用函数,需要传入这两个Person,类型为结构体定义的类型
int cmp(Person a, Person b)
{
    //籍贯不相等,比较籍贯
    if (a.address != b.address)
        return a.address < b.address;
    //籍贯相等,月份不相等,比较月份
    if (a.m != b.m)
        return a.m < b.m;
    //籍贯相等,月份相等,日子不相等,比较日子
    if (a.d != b.d)
        return a.d < b.d;
    //籍贯相等,月份相等,日子相等
    //但tzoj肯定不相等,比较tzojid,小的在前
    return a.tzoj < b.tzoj;
}

int main()
{
    int n;
    cin >> n;
    char e;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i].tzoj >> a[i].phone >> 
        a[i].address >> a[i].y >> e >> 
        a[i].m >> e >> a[i].d;
    }
    //对a数组进行排序
    sort(a,a+n,cmp);
    //循环输出
    for (int i = 0; i < n; i++)
    {
        cout << a[i].tzoj << " " << a[i].phone 
        << " " << a[i].address << " " << a[i].y 
        << "-" << a[i].m << "-" << a[i].d << endl;
    }
    return 0;
}

可见我们将冒泡排序部分直接省略到了sort(a,a+n,cmp);这句话的意思是我们将a数组的下标从0到n-1的进行了排序,他满足的<号关系是cmp里所定义的。其实a是这个指针,sort(a,a+n,cmp);等价于sort(&a[0],&a[0]+n,cmp);。
数组元素<可以直接用就可以直接sort了,不用写cmp,比如直接对整型int、字符串string排序
当然他的运行速度也更快,通过不了TLE的题目可以试试调用sort函数。

还有什么题目可以练练手呢,可以去体会下struct里我们需要存储什么(有些题目并不需要使用),cmp函数需要怎么实现
1.TZOJ1090: 绝对值排序
2.TZOJ5142: QQ群成员排名
3.TZOJ3148: 按1的个数排序
4.TZOJ5009: 成绩排名
5.TZOJ6149: 因子个数排序
6.TZOJ4851: 奖学金
7.TZOJ4794: 谁拿了最多奖学金
终极扩展
1.十大排序都是怎么实现的,难点在哪里
2.冒泡排序交换总次数和什么有关

posted @ 2020-03-05 08:23  暴力都不会的蒟蒻  阅读(392)  评论(0编辑  收藏  举报