c++讀入優化(整數)
一、背景
這是某道題目的狀態:
我的程式碼:
大牛的程式碼:
我瞬間萌幣了!
我有這麼慢?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<cstdio>標頭檔案。與scanf不同的是,它沒有緩衝,也就意味著它會更快。
char c=getch();//直接讀入一個字元,而不會回顯。也就是說,你讀入了一個字元,它不會在介面裡顯現而直接讀入getch,getch也會直接返回這個字元。比如說你寫了一個程式小遊戲,你肯定不希望看到wasdwasdwasd滿天飛,所以就用getch。需要#include<conio.h>標頭檔案。同樣,它沒有緩衝。
等等。
可以看到,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<cctype>標頭檔案。
所以也可以寫成:
-
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<cstdio>
-
int main()
-
{
-
freopen("test.txt","w",stdout);
-
const int N=5000000;
-
for(int i=1;i<=N;i++)
-
printf("%d ",N);
-
}
驗證程式:
-
#include<cstdio>
-
#include<ctime>
-
#include<algorithm>
-
#include<windows.h>
-
#include<iostream>
-
#include<cctype>
-
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
所以說讀入優化還是很快的。