说明:本题取自于牛客竞赛的一道算法题,地址为齐齐排序 (nowcoder.com)。
一.题目条件
1.题目概述: 
2.输入和输出描述
3.备注
二.题目分析
题目涉及到较多的变量,接下来将一个个分析。
1.n表示数字的个数,需要在第一行输入,用于标识第二行输入的整数ai的个数,所以这个数字的输入就可以知道有多少大小的数组需要创建。当然,根据题目所说1<=n<=200000,我们可以直接写上intArr[200000],也可以使用(int*)malloc(sizeof(int)*n)来创建。除此之外,这个n可以用于在后面的for循环来初始化数组。
2.m表示操作的个数,和n同理,用在for循环来不断scanf输入数据。当然也可以用于定义数组的大小,那么这里为什么需要使用两个数组呢?请看下文的解释。
3.ai就是1所说的是使用for来对intArr的每个数据的初始化。
4.ti和xi可以直接for...{scanf("%d %d",...);}进行输入。
看到这里的4之后,貌似也不需要使用数组,传入第二个参数ti就可以代表降序还是升序,到时候调用对应的函数就行了,但是其实并不然,由于在备注里说明了1<=m<=200000,那就说明可以进行200000次操作,此时我们难道需要进行200000次排序吗?
三.算法分析
以牛客官网上的示例2中:
第一行的4 2表示后面创建数组大小为4,进行2步操作。第二行的1 2 4 3说明输入的数组中每个元素是1 2 4 3。第三行的2 3说明进行逆序,从1~3,第四行的1 2说明进行顺序,从1~2。
第一步操作:1 2 4 3中,1~3的元素是1 2 4,进行降序,结果是4 2 1 3
第二步操作:4 2 1 3中。1~2的元素是4 2,进行顺序,结果是2 4 1 3
可见第二步操作覆盖了第一步部分的前两位。
当然这个可能看不出什么,但是假设我们再来一次操作,第三步操作假设是1 4
第三步操作:2 4 1 3中。1~4元素是2 4 1 3,进行顺序,结果是1 2 3 4。
这下是不是就能发现,好像第三步操作了之后,全部就变顺序了,和直接少去第一,二步操作直接进行第三步操作一样的。所以我们可以总结出: 只要后面的操作包括了前面的几步的范围,那前面那几步就不需要进行操作。
那么我们是从前往后看吗,但是上面我们总结的并不是这样,因为我们不知道后面哪个最大,没办法说明到底要不要进行操作。所以我们从最后的操作往前看就可以找到后面较大者,然后标记前面的比这个大值范围小的操作,这些操作就没必要进行操作了,这样就可以省下大部分的代码。
接下来就解释为什么需要再创建两个数组,上面我们说过了,由于我们要从后向前看,所以如果我们不存储每次输入的内容,就没办法对之前的操作是否要进行排序标记了。由于每次输入的类型都一样,所以可以使用数组进行存储。 由于ti标识升降序排列xi标识范围,我们也可以把这下ti和xi存储起来,比较xi的大小,修改ti的内容,由于1已经标识升序,2标识降序,那我们可以多加入一个0来表示不进行操作即可。
对于排序,我们可以使用快速排序,也可以直接使用C语言内的qsort函数,这个函数的实现方法就是快速排序。
qsort的函数使用方法:
void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *));
base标识这个待排序的数组,num标识输入的元素个数,size标识每个元素的大小,后面的compar由于再()小括号内与*先结合,所以它是一个指针,这个指针指向的数据类型是int (const void *, const void *)这说明指向的元素是一个函数,这个函数的返回类型是int,形参是两个const void* 类的变量,总之,compar是一个函数指针,所以我们还需要定义一个函数。
规范:函数的返回值>0就代表两个值要互换。
所以我们可以使用
int cmp1(const void* e1,const void* e2){//顺序
return (*(int*)e1-*(int*)e2);
}
int cmp2(const void* e1,const void* e2){//降序
return (*(int*)e2-*(int*)e1);
}
接下来就简单解释解释两个代码,由于传入两个void*无类型指针,这个指针不能用来存取等操作,因为编译器并不知道这个形参的大小,所以我们可以把它变为(int*)强制类型转换为int指针,然后*解引用获得值,第一个e1的值-e2个值,大于0时就是e1>e2,此时互换,就把较大值换到后面了,所以是升序,第二个就是降序。
四.代码实现
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int cmp1(const void* e1,const void* e2){//顺序
return (*(int*)e1-*(int*)e2);
}
int cmp2(const void* e1,const void* e2){//降序
return (*(int*)e2-*(int*)e1);
}
int main() {
int n, m, t, x;
scanf("%d %d", &n, &m);
int* arr = (int*)malloc(sizeof(int) * n);//n个数据
int* Oarr1 = (int*)malloc(sizeof(int) * m);//m个操作的降/升序
int* Oarr2 = (int*)malloc(sizeof(int) * m);//m个操作的范围
for (int i = 0; i < n; i++) {
//输入n个数
scanf("%d", &arr[i]);
}
for (int i = 0; i < m; i++)//存储每个操作
scanf("%d %d", &Oarr1[i], &Oarr2[i]);
int temp = Oarr2[m - 1];//存储从后向前查找的较大值
for (int i = m - 2; i >= 0; i--) {
if (temp < Oarr2[i])
temp = Oarr2[i];
else
Oarr1[i] = 0;//设置为0表示该次不需要排序
}
for (int i = 0; i < m; i++)
if (Oarr1[i] == 1)
qsort(arr,Oarr2[i],sizeof(int),cmp1);
else if(Oarr1[i] == 2)
qsort(arr,Oarr2[i],sizeof(int),cmp2);
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
free(arr);
free(Oarr1);
free(Oarr2);
return 0;
}
五.思想总结
本题的思想主要是
1.如何处理多个排序,即可以从后往前判断是否有排序范围覆盖
2.qsort的使用(也可以是自己写的排序算法)方法,首先是四个参数从左到右是数组,个数,大小,函数指针。其中函数指针返回大于0就互换位置。