题目描述
学校要举办校庆晚宴,要求学生登记就餐时间,以确定在哪个时间段内就餐的学生数最多,从而调食品的供应量。就餐时间被分为了 N 个时间段,其中 N 可能会非常大,可以假 设 N 为 1 亿,也就是 100000000(这要求程序不能声明长度为 N 的数组或定义 N 个变量), 若未考虑此情况则不能得分。
要求:排序算法的平均时间复杂度不得大于 O(MlogM)。
先输入两个数字 N,M,表示就餐时间被分为了 N 个时间段,一共有 M 个学生。 之后程序输入 M 行,每行两个数字,表示第 i 个学生期望的就餐起始时间段和结束时间段。 程序输出若干行,每行两个数字,表示就餐人数最多时间段的起始时间和终止时间。
测试样例:
输入
5 5
1 2
2 3
1 5
3 5
3 4
输出
3 3
样例解释:
就餐人数最多的时间段的起始时间是第三个时间段,终止时间是第三个时间段。 因为在第三个时间段到第三个时间段,2、3、4、5 号学生都在就餐,这个时间段是就餐 人数最多的时间段。
题目分析
- 由于就餐时间段可能很多,故这里不可能开一个数组从第一个就餐时间到最后一个可能的就餐时间,故想到可以设立两个数组,一个存储就餐开始的时间,一个存储就餐结束的时间。
- 再将start和finish合并后排序,经处理得到start和finish中所有出现的时间点。
- 通过for循环,计算每个时间点应该有的人数。再结合finish数组对其计算哪些时间点之间的人数不变。
- 得到人数最多的时间端数组。再通过处理后合并相邻时间段,得到了哪些人数时间段最多的结果。
在这里,我把题目分成三个部分
- 一个是输入就餐的起始时间和就餐的结束时间
- 一个是归并排序
- 一个是通过计算得到就餐人数最多的那个时间段,并输出
就餐时间段的登记
没什么好说的,直接上代码
//录入学生登记的时间段,并判断输入是否合法
void registration(int start[], int finish[], int M, int N)
{
int i;
printf("Please enter the expected time period for each student in turn: \n");
for (i = 0; i < M; i++)//for循环进行输入
{
printf("The expected period of time for %d students: \n", i + 1);
scanf("%d", &start[i]);
scanf("%d", &finish[i]);
if(start[i]<0||finish[i]>N)//排除非法输入
{
assert(0);
}
}
}
归并排序
这个我会在我的其他博客中详解,这里直接上代码
void merge(int a[],int b[],int min,int max)//归并排序
{
int mid,p;//mid为数组的中间位置,p为从中间位置开始的指针
int min2;//记录了数组最小的位置
int i,j;
min2=min;//min为从最小位置开始的指针
if((max-min)<=1)//如果数组为1则return
{
return;
}
mid=(max+min)/2;//计算中间位置
p=mid;
merge(a,b,min,mid);//向中间位置的左边递归
merge(a,b,mid,max);//向中间位置的右边递归
for(i=min;i<max;)//开始归并排序
{
if(a[min]>a[p])//左边大于右边
{
b[i]=a[p];
p++;
if(p==max)//判断是否排序完成
{
i++;
for(j=min;j<mid;j++,i++)
{
b[i]=a[j];
}
}
else
{
i++;
}
}
if(a[min]<=a[p])//右边大于左边
{
b[i]=a[min];
min++;
if(min==mid)//判断是否排序完成
{
i++;
for(j=p;j<max;j++,i++)
{
b[i]=a[j];
}
}
else
{
i++;
}
}
}
for(i=min2;i<max;i++)//整理排序后的数组
{
a[i]=b[i];
}
}
通过计算得到人数最多的那个时间段,并输出
重要参数
- start【】:就餐开始的时间点
- finish【】:就餐结束的时间点
- M:学生人数
- N:就餐时间段的最大值
- min:数组头的下标
- c【】:出现的所有时间点的有序集合
- len:出现的所有时间点的有序集合的长度
- num【】:所有时间点,对应每个时间点进出的人数
- data【】:计算每个时间点的人数
- maxtime【】【】:将时间点化为一个时间段进行存储的数组
- len2:maxtime的界限值(即个数)
- maxtime2【】【】:存储人数最多的最大时间段
算法思路 - 首先复制一下start【】和finish【】数组,并对其进行排序,并且合并start【】和 finish【】数组到一个新的数组里,为其排序,之后再将对合并后的数组去重。
for(i=0;i<M;i++)//复制start和finish数组
{
a1[i]=start[i];
a2[i]=finish[i];
}
merge(a1,start,min,M);//排序start数组
merge(a2,finish,min,M);//排序finish数组
for(j=0,i=M;j<M;i++,j++)//将start数组和finish数组合并
{
a1[i]=finish[j];
}
merge(a1,a2,min,2*M);//排序得到后的数组
i=0;
j=0;
c[j]=a2[i];//第一位赋值
i++;
while(i<2*M)//得到start和finish中出现的所有时间点,且不重复出现,并且有序
{
if(c[j]!=a2[i])
{
j++;
c[j]=a2[i];
}
i++;
}
len=j+1;
c[len]=c[len-1]+1;//多腾一个空间为后续计算时间段人数准备
len++;
- 计算哪些时间点有人进出,并且每个时间点进出的人数为多少,存在num【】数组里,代码中有体现。
for(i=0,j=0,k=0;i<2*M;i++)//计算哪个时间点有人进出
{
while(c[i]==start[j]&&j<M)
{
num[i]++;
j++;
}
while(c[i]==finish[k]&&k<M)
{
num[i+1]--;
k++;
}
}
- 计算每个时间点的人数并寻找时间点人数最多的时间点,并记录
for(i=1;i<len;i++)//计算每个时间点的人数
{
data[i]=num[i]+data[i-1];
}
for(i=0;i<len;i++)//寻找时间点人数最多的是多少人
{
if(maxnum<=data[i])
{
maxnum=data[i];
}
}
for(i=0,j=0;i<len;i++)//记录时间点人数最多的时间点
{
if(data[i]==maxnum)
{
maxtime[j][0]=c[i];
maxtime[j][1]=c[i];
j++;
}
}
- 对最大的时间点进行合并简化,这里要考虑多种情况
while(j+1<len2&&i<M)//判断是否在一个时间点到另一个时间点都是最大人数
{
if(len2