一、背景
这是某道题目的状态:
我的代码:
大牛的代码:
我瞬间萌币了!
我有这么慢?Are you kidding me?
点进去一看:
void Read(int & p)
{
p=0;
int flag=1;
char c=getchar();
while(c<'0' or c>'9')
{
if(c=='-') flag=-1;
c=getchar();
}
while(c>='0' and c<='9')
p=p*10+(c-'0'),c=getchar();
p*=flag;
}
这是什么?
原来,这是读入优化啊!
二、读入优化的原理与实现
C语言和C++库里有很多种输入方式,我们最常用的是scanf和cin。除此之外,还有:
char c[]; gets(c);//读入一行字符串,但是需要注意的是,它只会在遇到'\n'后停止,不会因为超过c的内存上限而停止。所以容易溢出。
char c[]; fgets(c,len,stdin);//读入一行字符串。和gets不同的是,它如果读入了len-1个字符,就会停止。
char c=getchar();//就像scanf一样,只读入一个字符,并返回这个字符,需要#include
char c=getch();//直接读入一个字符,而不会回显。也就是说,你读入了一个字符,它不会在界面里显现而直接读入getch,getch也会直接返回这个字符。比如说你写了一个程序小游戏,你肯定不希望看到wasdwasdwasd满天飞,所以就用getch。需要#include
等等。
可以看到,getchar比scanf更快。我们可不可以用getchar来改进我们的输入呢?
下面以整数的读入优化为例。其实我也只会整数的读入优化。。。
让我们随便写一个整数:
令p=123456789
如果用getchar,它会读入一个字符‘1’,然后读入'2','3','4'......
怎么产生数字呢?我们用'1'-'0'(实质是ASCII码的运算),结果就是数字1。
同理,char c=getchar(); int k=c-'0'; 就可以得到这个数字k。
现在要把所有的k加到一起,得到p。
让我们一步一步地来:
因为我们一次只读入一位,所以要把这个数拆成一位一位的形式。
最高位,1=1;
前两位,12=1*10+2;
前三位,123=1*100+2*10+3=12*10+3;
前四位,1234=1*1000+2*100+3*10+4=123*10+4;
.......
规律已经很明显了。
每读入一个代表数字的字符c,p=p*10+c-'0';
我们只需要不停地while(c>='0' and c<='9'),并且处理p即可。
问题来了,如果这个c不代表数字,比如说:
123 456
789
123和456中间有空格,456和789之间有换行,怎么处理呢?
因为这里是三个整数,读入了123以后,还剩下“ 456”,前两个c=‘ ’肯定不能让c-‘0’算在p里面。因此,我们需要跳过不是代表数字的字符。
我们就可以写出一个基本的整数读入优化:
void Read(int & p)
{
p=0;
char c=getchar();
while(c<'0' or c>'9') c=getchar();
while(c>='0' and c<='9')
p=p*10+(c-'0'),c=getchar();
}
要读入整数p,只需要调用Read(p)即可。
除了这种,比较常用的是
int Read()
{
int p=0;
char c=getchar();
while(c<'0' or c>'9') c=getchar();
while(c>='0' and c<='9')
p=p*10+(c-'0'),c=getchar();
return p;
}
调用p=Read();即可。
其实(c>='0' and c<='9')也是一个函数isdigit(c),如果c是代表数字的字符,就返回1,否则返回0,需要#include
所以也可以写成:
void Read(int & p)
{
p=0;
char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c))
p=p*10+(c-'0'),c=getchar();
}
的形式。
三、整数读入优化的特殊处理——负数
对于一个负数,读入优化只会读入数字,负号并不会被读入。
所以需要读入负数,要特判这个字符是不是负号,如果是,那么这个值要改成负数。
这就是文章开头的代码:
void Read(int & p)
{
p=0;
int flag=1;
char c=getchar();
while(c<'0' or c>'9')
{
if(c=='-') flag=-1;
c=getchar();
}
while(c>='0' and c<='9')
p=p*10+(c-'0'),c=getchar();
p*=flag;
}
但是,这个代码有一个弊端。所有的‘-’都必须代表负号。如果是减号,比如4-3,就会读错。
四、验证读入优化的效率
用freopen生成五百万个数的文本,并分别用scanf,cin和读入优化读入。
文本生成:
#include
int main()
{
freopen("test.txt","w",stdout);
const int N=5000000;
for(int i=1;i<=N;i++)
printf("%d ",N);
}
验证程序:
#include
#include
#include
#include
#include
#include
using namespace std;
void s(int & p)
{
scanf("%d",&p);
}
void c(int & p)
{
cin>>p;
}
void Read(int & p)
{
p=0;
char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c))
p=p*10+(c-'0'),c=getchar();
}
double t1,t2;
int main()
{
freopen("test.txt","r",stdin);
int p;
t1=clock();
//for(int i=1;i<=5000000;i++) s(p);
//for(int i=1;i<=5000000;i++) c(p);
for(int i=1;i<=5000000;i++) Read(p);
t2=clock();
//printf("scanf took %.2lf second",(t2-t1)/1000);
//printf("cin took %.2lf second",(t2-t1)/1000);
printf("Read took %.2lf second",(t2-t1)/1000);
}
结果:
scanf took 0.85 second
cin took 14.63 second
Read took 0.35 second
所以说读入优化还是很快的。