PAT 1129 C++版【updating…】
1.题意
实现一个简单的推荐系统。这里的推荐规则如下:
- 给出一个总的可推荐的数目n
- 给出一串访问序列,针对该访问序列得到的访问当前商品时的推荐商品。
那么如何生成这个推荐商品序列呢?主要有两点:
- 已经访问过的商品的最高频商品
- 如果频率相同,则优先输出较小下标的商品
2.分析
处理这道题,有直接暴力的思想:
- 直接将所有的已访问商品放到一个结构体数组中,然后每次输入的时候,进行一个排序,然后输出前k个。
上面的思想虽然简单,但是容易出现测试用例超时的结果。所以这个时候就需要使用换另外的方法。还有更好的方法么?
我们需要思考的是:上面这个思想的代码实现主要的耗时在哪里?通过思想可以知道,主要的耗时操作是在执行sort()。因为如果有N个输入,那么其时间复杂度就是N^2。所以就会导致超时。
3.代码
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
struct item{
int index = -1;//表示当前的这个商品的索引
int accNum = 0 ;//每种商品的访问量
int order ;//访问的顺序
int isVisit =0;//表示是否访问过
};
int cmp(item i1,item i2){
if(i1.accNum == i2.accNum) return i1.index < i2.index ;
return i1.accNum > i2.accNum;
}
int main(){
int num,limit;
scanf("%d %d",&num,&limit);
item it[num];
int i,j;
int accItem;
int k;
for(i = 0;i< num;i++){//这里的i只是用作控制输入,不作下标用
scanf("%d",&accItem);
if(i != 0){
k = min(limit,i);
sort(it,it+i,cmp);
printf("%d: ",accItem);
for(j = 0;j< k;j++){
if(j != k - 1) {
printf("%d ",it[j].index);
}
else{
printf("%d",it[j].index);
}
}printf("\n");
}
for(j = 0;j< i;j++){
if(it[j].index == accItem){
it[j].accNum++;
it[j].order = i;//重置order的值
break;
}
}
if(j == i){
it[i].accNum++;
it[i].index = accItem;//表示这个结构体中访问的就是accItem这一项
it[i].order = i;
}
}
}
4.测试用例
12 3
3 5 7 5 5 3 2 1 8 3 8 12
5.执行结果
6.代码优化
6.1 优化1
针对上面的输出,我认为上述的下面这个for()循环去找一个合适的index时出现了问题。
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
struct item{
int index = -1;//表示当前的这个商品的索引
int accNum = 0 ;//每种商品的访问量
};
int cmp(item i1,item i2){
if(i1.accNum == i2.accNum) return i1.index < i2.index ;
return i1.accNum > i2.accNum;
}
int cmp_index(item i1,item i2){
return i1.index < i2.index ;
}
int main(){
int num,limit;
scanf("%d %d",&num,&limit);
item it[num];
int i,j;
int accItem;
int k;
//使用二分法寻找合适的数字
int high ,low ,mid ;
for(i = 0;i< num;i++){//这里的i只是用作控制输入,不作下标用
scanf("%d",&accItem);
if(i != 0){
k = min(limit,i);
sort(it,it+i,cmp);
printf("%d: ",accItem);
for(j = 0;j< k;j++){
if(j != k - 1) {
printf("%d ",it[j].index);
}
else{
printf("%d",it[j].index);
}
}printf("\n");
}
//将如下的代码修改成使用2分法
//但是存在的问题是:如果使用二分法,那么前提得是:it[i].index 呈一个有序的状态,
//如果这个非有序序列,那么就无法使用二分法
sort(it,it+i,cmp_index);
high = i-1;
low = 0;
while(low <= high){
mid = (high + low)/2; //求出mid的值
//printf("mid = %d\n",mid);
if(it[mid].index < accItem){
low = mid + 1;
}
else if(it[mid].index > accItem){
high = mid - 1;
}
else break;
}
if(it[mid].index == accItem){//如果当前输入的值已经存在了
it[mid].accNum++;
}
else{
it[i].accNum++;
it[i].index = accItem;//表示这个结构体中访问的就是accItem这一项
}
}
}
所以我决定使用二分法去解决寻找。修改的代码如下:
sort(it,it+i,cmp_index);
high = i-1;
low = 0;
while(low <= high){
mid = (high + low)/2; //求出mid的值
//printf("mid = %d\n",mid);
if(it[mid].index < accItem){
low = mid + 1;
}
else if(it[mid].index > accItem){
high = mid - 1;
}
else break;
}
但是得到的结果仍然是运行超时。
6.2 优化2
针对上面的运行超时问题,可以大致猜到修改查找index的值不是时间的关键,时间的关键是:sort()。所以转换另外一种思路。使用set + 运算符重载 + 自定义类型 解决这个问题。
- 代码如下:
#include<cstdio>
#include<set>
#include<iostream>
#include<cstring>
using namespace std;
struct node{
int cnt;
int value;
bool operator<(const node &n)const{
if(n.cnt == cnt) return n.value > value;
return n.cnt < cnt;
}
};
int rate[50005];
int main(){
int num,limit;
scanf("%d %d",&num,&limit);
set<node> s;//用于存放浏览记录的 set
int i,j;
int accItem;
int k;
node n;//定义一个临时的node 变量
memset(rate,0,sizeof(rate));//初始化rate数组
for(i = 0;i< num;i++){//这里的i只是用作控制输入,不作下标用
scanf("%d",&accItem);
//遍历这个it,然后得到其中的值
k = min(limit,i);//求出输出的项数
int size = s.size();
//cout << "s.size() = "<<s.size()<<endl ;
k = min(k,size);
if(i > 0){
//cout << "k = "<<k<<endl;
cout<<accItem<<": ";
set<node>::iterator it;
for(it = s.begin(),j = 0;it !=s.end() && j<k; it++,j++){
if(j!=k-1) cout << it->value<<" ";//输出值
else cout<< it->value<<endl;
}
}
n.value = accItem;
n.cnt = rate[accItem];
rate[accItem]++;//现在访问过这个商品了,需要将其访问次数加一
if(s.find(n) != s.end()){//说明该商品已经浏览过
s.erase(n);//删除这个商品
//cout<<"找到了这个商品"<<endl;
//然后再将这个商品添加到set中
}
n.cnt = rate[accItem];
s.insert(n);
}
}
- 测试用例
12 10
3 5 7 5 5 3 2 1 8 3 8 12
- 关键问题
但是现在的问题是,如果使用find()函数,那么这个set查找的是什么元素呢?(是value还是cnt呢?)答案是:既非cnt,也不是value。而是一个node 变量。所以需要定义 一个临时的node变量 n。然后判断 n 是否 存在于set中。
同时需要注意输出的格式问题。这里虽然说是输出的格式问题,但是实际上是因为代码考虑不周导致的错误发生。如下图所示,使用该测试用例得到的结果是:
很明显的可以看到这个输出是有误的,为什么呢?错误的代码如下:
k = min(limit,i);//求出输出的项数
我这里只考虑了limit,i两者的大小关系,而没有考虑到set本身的大小。所以导致输出格式错误。修改代码如下:
int size = s.size();
k = min(limit,size);//求出输出的项数
因为s.size <= i恒成立,所以应该取set 的size和 limit之间的最小值。
- 执行结果