Sort 排序算法
1. 概要
在头文件 < a l g o r i t h m > \color{Orange}<algorithm> <algorithm>中 声明,采用的是成熟的 ”快速排序“ ,默认从小到大排序,时间复杂度为 O (nlogn)
sort(起始位置,终止位置,比较函数(可以没有))
对无序整形数组排序如下
// 对无序整型数组排序
int a[N];
int len = sizeof(a)/sizeof(int);
sort(a, a + len); // 从小到大排序
sort(a, a + len, greater<int>());// 从大到小排序
对字符排序如下
char a[N];
int len = sizeof(a)/sizeof(char);
sort(a, a + len); // 按 ASCII值 从小到大排序
sort(a, a + len, greater<char>()); // 从大到小排序
对结构体排序,由于结构体是没有比较函数的,故我们需要自己写比较函数,或者重载比较运算符
/*比较函数版本*/
struct Node{
int x, y;
}a[N];
bool cmp(int a, int b)
{
if(a.x != b.x)
return a.x < b.x;// 如果 a.x 不等于 b.x 就按照 x 从小到大排序
return a.y < b.y;// 如果 x 相等就 按 y 从小到大排序
}
sort(p, p + len, cmp);// 排序,比较函数为 cmp()
/*重载版本*/
struct Node{
int x, y;
bool operator <(const Node & w)const
{
if(x != w.x)
return x > w.x;
return y > w.y;
}
}p[N];
sort(p, p + len);
2. 题目
2.1 单词排序
【题目描述】
输入一行单词,相邻单词之间由一个或多个空格间隔,请按照字典序输出这些单词,要求重复的单词只输出一次(区分大小写)
【输入格式】
第一行为一个整数n,表示有n(1 <= n <= 100
)个单词。随后一行为n个单词,每个单词长度不超过50,单词之间用至少一个空格间隔。数据不含除字母、空格外的其他字符
【输出格式】
按字典序输出这些单词,重复的单词只输出一次
【输入样例】
3
Keep on going
【输出样例】
Keep
going
on
思路
用 string 数组存下来,然后进行 sort 排序
Code
const int N = 110;
string s[N];
int n;
void solve()
{
cin >> n;
for(int i = 0; i < n; i ++ ) cin >>s[i];
sort(s, s + n);
n = unique(s, s + n) - s;
for(int i = 0; i < n; i ++ )
cout << s[i] << "\n";
}
2.2 志愿者选拔
【题目描述】
学院选拔志愿者,面试分数线根据计划录取人数的150%划定,即如果计划录取m名志愿者,则面试分数线为排名第m×150%(向下取整)名的选手的分数,而最终进入面试的选手为笔试成绩不低面试分数线的所有选手
请编写程序划定面试分数线,并输出所有进入面试的选手的报名号和笔试成绩
【输入格式】
第一行为两个整数 n和m ( 5 <= b <= 5000, 3 <= m <= n
),其中n表示报名参加笔试的选手总数,m表示计划录取的志愿者人数。输入数据保证 m×150%
向下取整后小于等于n
【输出格式】
第一行为两个整数,分别表示面试分数线和进入面试的选手的实际人数
从第二行开始,每行两个整数,分别表示进入面试的选手的报名号和笔试成绩,按照笔试成绩从高到低输出,如果成绩相同则按报名号由小到大的顺序输出
【输入样例】
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
【样例说明】
m×150%=3×150%=4.5,向下取整后为4。保证4个人进入面试的分数线为88,但因为88有重分,所以所有成绩大于等于88的选手都可以进入面试,故进入面试的选手的实际人数为5
思路
读入学生信息,按分数从高到低排序,若分数相同则按照学号从小到大排,计算出录取的人数后,统计学生中分数大于等于录取分数的人数,根据题目格式输出即可
const int N = 5010;
struct Node{
int num, score;
}a[N];
bool cmp(struct Node a, struct Node b)
{
if(a.score != b.score)
return a.score > b.score;
return a.num < b.num;
}
int n, m;// 学生人数,应该录取的人数
int sum, s; // 录取的实际人数,录取分数线
void solve()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++ )
{
scanf("%d%d", &a[i].score, &a[i].num);
}
sort(a + 1, a + n + 1, cmp); // 排序
m = m * 3/2;
for(int i = 1; i <= n; i ++ )
{
if(i == m)
s = a[i].score;
if(a[i].score >= s)
sum ++;
}
printf("%d %d\n", m, s);// 录取的实际人数, 录取分数线
for(int i = 1; i <= n; i ++ )
{
if(a[i].score >= s) // 大于等于录取分数的学生信息
printf("%d %d\n", a[i].num, a[i].score);
}
}
2.3 奖学金
【题目描述】
学校打算为学习成绩优秀的前5名学生发奖学金。每个学生都有3门课的成绩:语文、数学及英语。先按总分从高到低排序,如果两个学生总分相同,再按语文成绩从高到低排序;如果两个学生总分和语文成绩都相同,那么规定学号小的学生排在前面。这样每个学生的排序是唯一确定的,试按排名顺序输出前5名学生的学号和总分
【输入格式】
第一行为一个正整数n( 6 <= n <= 300
),表示该校参加评选的学生人数。第二行到第n+1行,每行有3个以空格间隔的数字,每个数字都为0~100。第j行的3个数字依次表示学号为j-1的学生的语文、数学及英语的成绩。每个学生的学号按照输入顺序编号为 l~n (恰好是输入数据的行号减1)
【输出格式】
输出共有5行,每行两个正整数(以空格间隔),依次表示前5名学生的学号和总分
【输入样例】
6
90 67 80
87 66 91
78 89 91
88 99 77
67 89 64
78 89 98
【输出样例】
6 265
4 264
3 258
2 244
1 237
思路
读入学生信息,按照题目要求排序后,输出前 5 名即可
Code
struct student{
int num;
int chinese, math, english;
int score;
}a[N];
bool cmp(struct student a, struct student b)
{
if(a.score != b.score)
return a.score > b.score;
if(a.chinese != b.chinese)
return a.chinese > b.chinese;
return a.num < b.num;
}
int n;
void solve()
{
scanf("%d",&n);
for(int i = 1; i <= n ; i ++ )
{
scanf("%d%d%d", &a[i].chinese, &a[i].math, &a[i].english);
a[i].score = a[i].chinese + a[i].math + a[i].english;
a[i].num = i;
}
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= n && i <= 5; i ++ )
{
printf("%d %d\n", a[i].num, a[i].score);
}
}
2.4 导弹拦截系统
【题目描述】
有一种新的导弹拦截系统,凡是与它的距离不超过其工作半径的导弹都能够被它成功拦截。当工作半径为0时,则能够拦截与它位置恰好相同的导弹。但每套导弹拦截系统每天只能设定一次工作半径,而当天的使用代价,就是所有系统工作半径的平方和
某天,雷达捕捉到敌国的导弹来袭。由于该系统尚处于试验阶段,因此只有两套系统投入工作。如果现在的要求是拦截所有的导弹,请计算这一天的最小使用代价
【输入格式】
第一行包含4个整数 x1 、y1 、x2 、y2 ,每两个整数之间以一个空格间隔,表示这两套导弹拦截系统的坐标分别为(x1 ,y1 )、(x2 ,y2 )
第二行包含一个整数N,表示有N 颗导弹。接下来为N 行,每行有两个整数x、y,中间以一个空格间隔,表示一颗导弹的坐标(x,y),不同导弹的坐标可能相同
【输出格式】
输出只有一行,包含一个整数,即当天的最小使用代价
【数据范围】
对于100%的数据,1 <= n <= 100000
,且所有坐标分量的绝对值都不超过1 000
【算法提示】
两个点(x1 ,y1 )、(x2 ,y2 )之间距离的平方是
(
x
1
-
x
2
)
2
+
(
y
1
-
y
2
)
2
(x1 -x2 )^2 + (y1 -y2 )^2
(x1-x2)2+(y1-y2)2
两套系统工作半径 r1 、r2 的平方和,是指 r1 、r2 分别取平方后再求和,即
r
1
2
+
r
2
2
r_1^ 2 +r_2^2
r12+r22
【输入样例1】
0 0 10 0
2
-3 3
10 0
【输出样例1】
18
【样例说明1】
样例1中要拦截所有导弹,在满足最小使用代价的前提下,两套系统工作半径的平方分别为18和0
【输入样例2】
0 0 6 0
5
-4 -2
-2 3
4 0
6 -2
9 1
【输出样例2】
30
思路
如下图所示,设导弹拦截系统为 a 和 b, 计算出所有导弹到 a 和 b 的距离 ,并按照到 a 的距离从小到大排序。假如选择一点 k到 a 的距离作为 a的半径,那么排在k点之后的点都能被 a击落,而k点之前的点只能被b击落,则b的半径即是前 k -1 个点到 b 的最大半径
Code
const int N = 10010;
struct Node{
int da, db;
}a[N];
bool cmp(struct Node a , struct Node b)
{
return a.da > b.da;
}
int n;// 导弹的数量
void solve()
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
scanf("%d", &n);
for(int i = 1; i <= n; i ++ )
{
int x, y;
scanf("%d%d", &x, &y);
a[i].da = pow(x1 - x, 2) + pow(y1 - y, 2);
a[i].db = pow(x2 - x, 2) + pow(y2 - y, 2);
}
sort(a + 1, a + n + 1, cmp);
// 枚举半径
int r_b = -0x3f3f3f;
int ans = 0x3f3f3f;
for(int i = 1; i <= n; i ++ )
{
int r_a = a[i].da;// 选择当前到 a 的距离作为半径
r_b = max(r_b, a[i - 1].db);// 前 i - 1 个到 b 的最大半径
int r2 = r_a + r_b;// 代价为 两者半径平方之和
ans = min(ans, r2);
}
printf("%d\n", ans);
}
【参考资料】
【1】 《编程竞赛宝典:C++语言和算法入门》张新华著,人民邮电出版社,ISBN:978-7-115-55461-1