1. 程式人生 > >【原創】整數讀入優化 C++

【原創】整數讀入優化 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不代表數字,比如說:

123456

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

所以說讀入優化還是很快的。