身体力行费曼学习法,今天来总结一下这两天学习的排序知识点和常见题型。与大家共勉!!!
首先,排序问题,按照排序内容主要分为三种题型:
1.对基本数据类型(如int,char,string,char数组等)变量组成的数组的排序问题;
2.对结构体(如自定义结构体学生,包含名字和分数)变量组成的数组排序问题;
3.对STL标准容器的排序。
机试排序方法可简单分为三种:
1.简单选择排序。
2.简单插入排序。
3.使用sort函数。
———————————————————————————————————————————
其中第一种和第二种方法是具体写排序函数的较为简单的基本思想方法。分开阐述:
1.简单选择排序,就是对n个数,进行n次选择,每次选择最小或最大的数放在指定位置。考虑该算法所需的数据结构有用于存储原数据的数组,用于暂存所选择的数的变量min、minIdx等。
2.简单插入排序,就是将n个数看作两个前后两个数组,前面为有序数组,后面为无序数组,最开始,有序数组默认只有第一个数,其余数都归入无序数组中,然后将无序数组中的n-1个数逐个插入有序数组中,最终整个数组有序。考虑该算法所需的数据结构有用于存储原数据的数组,用于暂存所需插入的数的变量temp等。
3.一般情况下,机试无需自己去写具体的排序函数,使用sort就能解决大部分问题。
首先,使用sort函数的前提:C++环境且引入<algorithm>头文件,使用标准命名空间。即加入下列语句:
#include <algorithm>
using namespace std;
然后,sort的使用范围对应上面提到的三种排序内容分别为:
1.所有基本数据类型数组;
2.所有结构体数组;
3.STL标准容器中只有vector,string,deque可以使用sort进行排序。(实际上,set,map容器使用红黑树实现,元素本身就有序,故不可以使用sort排序)。
再者,sort函数的使用方法为:函数包含3个参数,依次为首元素地址、尾元素的下一个地址、比较函数(比较函数非必填,如果没有比较函数,默认从小到大排序,如int型按数值从小到大排序,string型按字典序从小到大排序等)。
比较函数(函数名可以自己取)返回值类型为bool型,参数类型为参与比较的元素类型,有两个参数,该函数用于定义排序规则。举个例子,我需要将int数组从大到小排序,因此用sort函数必须要写比较函数作为参数,可以写作如下:
bool cmp(int a,int b){
return a > b;
}
sort(a,a+n,cmp);
其中cmp可以简单记忆,参数a,b从左到右,需要从左到右元素从大到小排列,那么就是左(a) > 右(b)。
结构体排序非常类似,只需在比较函数中改动两个地方,1是参数,改为结构体类型;2是返回值的表达式,替换为结构体中需排序的值。以包含名字和分数的学生结构体为例,将学生按分数由高到低排列:
struct Student{
char name[20];
int score;
}
bool cmp(Student a, Student b) //注意C中结构体类型为struct Student,C++中则为Student(更简便)
{
return a.score > b.score;
}
若需要将学生先按分数由高到低,再由名字字典序由小到大排列,只需使用if,else语句改进一些cmp函数即可,如下:
#include <cstring> //为了使用strcmp函数比较字符数组的字典序大小而引入该头文件
#include <algorithm> //为了使用sort函数而引入该头文件
using namespace std;
struct Student{
char name[20];
int score;
}
bool cmp(Student a, Student b) //注意C中结构体类型为struct Student,C++中则为Student(更简便)
{
if(a.score != b.score) return a.score > b.score;
else return strcmp(a.name,b.name) < 0;
}
_____________________________________________________________________________
以上排序思想很简单,但是实际考查更复杂的问题时,我们需要学会灵活运用上面的基本知识,使用更复杂的逻辑组合来解决具体的问题。
对一些经典排序问题的易错点总结如下:
1.字符串排序:基本思想是使用sort函数。考虑所需的数据结构类型,使用string数组来存储字符串更方便排序,输入输出字符串时使用C++头文件<iostream>中的cin和cout语句输入、输出字符串是最简单且合适的选择。升序可以省略比较函数,降序则按上述方法自行添加一个比较函数即可。
2.结构体排序:这里有两类考生结构体排序的经典问题:这里都先设考生有名字、分数两个字段。
<1>考生按分数由高到低排序,其中分数相同,排名相同,且排名等于分数高于该考生的人数+1;
如:
Li 100 1
Hong 100 1
Fang 99 3
Cici 99 3
Coco 98 5
对该种问题的排序,首先可以先使用sort函数对考生按照分数进行排序,再在输出排名时进行计算。(当然,也可以存入一个排名数组或者在考生结构体中定义排名字段用于存储排名,如添加int rank1; 注意这里写排名最好不要使用英文单词rank,这会与rank函数重名,可以在后面加个1就不会重名导致含义模糊了)
这里的难点和关键就是如何得到正确的排名(分数高于该考生的人数+1),考虑将sort后的有序数组遍历,若当前元素不等于前面一个元素,则该元素的排名为当前元素的下标+1;否则,当前元素的排名等于前面一个元素的排名。(这里需要积累一个技巧:我们从前往后遍历时,使用一个int变量temp存储排名即可不需要一整个数组,每次循环temp就存放了前面元素的排名)
<2>考生按分数由高到低排序,其中分数相同,排名相同,且排名等于不低于该考生分数的人数;
如:
Li 100 2
Hong 100 2
Fang 99 4
Cici 99 4
Coco 98 5
对该类问题,基本思路和前面一样,都是先用sort对考生按分数进行排序,再根据有序数组计算满足题意的排名值。
这里的难点依然是如何得到正确的排名。想想在前一题遍历时,我们每跑到一个元素,就可以直接计算出它的排名了,因为它的排名和它前面我们已经访问过的元素个数有关。而该题,我们每跑到一个元素时,还不能立即计算出它的排名,因为它的排名还和我们后面还未访问过的相同分数的元素个数有关。只有相同分数的元素都访问完了,才能得出该分数的排名。也就是说我们可以每访问一组分数相同的元素,计算后再一起输出它们的共同排名,排名等于当前访问的所有元素个数。
来看看样例和代码:
#include <cstdio> //****************
#include<algorithm> //*非
using namespace std; //*关
const int maxnum = 1010; //*键
int score[maxnum]; //*代
int rank1[maxnum]; //*码
int sum = 1; //*************
bool cmp(int a, int b){
return a > b;
}
int main(){
int n;
scanf("%d",&n);
for(int i=0; i < n; i++){
scanf("%d",&score[i]);
}
sort(score,score+n,cmp);
int j = 1;
for(int i=0; i < n;){ //***********************************************关键
while(j < n){
if(score[i] == score[j]){
j++;
sum ++;
}else{
break;
}
};
for(int k = i; k < j; k++){
rank1[k] = sum;
}
i = j;
}//****************************************************************关键
for(int i=0; i < n; i++){
if(i){ printf("\n");}
printf("%d %d",score[i],rank1[i]);
}
return 0;
}
上面是我写的代码,虽然费了九牛二虎之力缝缝补补运行通过了,但依然存在诸多问题,最直观的问题就是看不太懂,(没错我自己看着都有点迷糊),这些问题在我写代码解题时也给我带来了很大的困扰:
1.是for循环使用不当。每次写循环就习惯型使用for循环,其实简单循环使用for循环很简洁且方便,但是对于需要嵌套while,if,else等诸多逻辑组合的复杂循环,建议一定不要学我,都去用while写!!!我今后也要注意这个问题,复杂组合逻辑用while写。
2.是变量混乱。有的变量一开始定义在了for循环里面,后来发现跳出循环还要使用,又给它定义出来。有的变量在一开始压根没想到要定义,后来写着写着发现需要些辅助变量又增加定义。当然这不是最致命的,最致命的是在遍历时,有时候一个逻辑不清晰,将变量+1或-1的位置写错地方了或者变量多+了1或者少+了1之类的,有时候逻辑一复杂就容易卡住,想题要想很久,或者想着想着忘了自己的逻辑走到哪了或者还需要走哪些步骤。(这是真的非常致命!!!)建议大家不要学我,可以一开始就要计算机的计算步骤简单模拟一下计算过程,如需要什么循环呀,循环里面要嵌套什么if语句或者嵌套循环之类的,以及需要哪些辅助变量,这一点很重要,最好写一个功能代码块之前就把需要用到的辅助变量定义好,而且最好把变量名写成有助于思考,让逻辑看起来更清晰的名字,而不是什么i,j,k之类的,我也是饱尝了一循环就用i,j,k的苦。)
按照我上面的反省,我将代码改进如下,你们可以对比一下看看这样是不是更清晰易懂:
#include <cstdio> //****************
#include<algorithm> //*非
using namespace std; //*关
const int maxnum = 1010; //*键
int score[maxnum]; //*代
int rank1[maxnum]; //*码
bool cmp(int a, int b){
return a > b;
}
int main(){
int n;
scanf("%d",&n);
for(int i=0; i < n; i++){
scanf("%d",&score[i]);
}
sort(score,score+n,cmp);
int sum = 0; //*************
int sameStartIdx = 0; //idx为index缩写,意为下标,这里设置sameStartIdx 是为了由下标遍历数组
int sameEndIdx = 1;//这里sameIdx意为相同分数的下标,用于每访问完一组相同分数的元素后计算排名
while(sameStartIdx < n){
while(sameEndIdx < n){
if(score[sameEndIdx] == score[sameStartIdx]) sameEndIdx++;
else break;
} //跳出循环时sameRndIdx为下一组分数的首元素,与当前分数不同
sum += sameEndIdx - sameStartIdx;
for(int k = sameStartIdx; k < sameEndIdx; k++) rank1[k] = sum;
sameStartIdx = sameEndIdx;
sameEndIdx ++;
}
for(int i=0; i < n; i++){
if(i){ printf("\n");}
printf("%d %d",score[i],rank1[i]);
}
return 0;
}
这篇文章就先写到这里啦~以前遇到复杂的循环条件逻辑和很多辅助变量时就头大,以后还是用多思考,基本的是要用计算机模拟路线思考需要使用的逻辑块,由脑海中的自然语言翻译为计算机C++语言来用计算机实现,然后要注意变量名设置的更直观易懂。好啦,希望下次我们写代码时思路更清晰哦!