快速读写(以C语言为例)
参考:快读攻略(详) - 学委 的博客 - 洛谷博客 (luogu.com.cn)
C语言原本的输入输出函数
scanf
、printf
由于考虑了很多复杂情况(例如①格式化字符串解析②错误处理③缓冲区刷新等),导致在读写位数较大的数据时速度较慢,故自己写一些速读、速写函数增加特定程序的运行速度
1. 快读
1.1 快读原理
-
数字之间用
空格
或者换行符
来分隔。 -
关于负数,负号后面是它的绝对值。
-
由于数字正向给出,所以要读入 256 可以这样:
- 读入 2,记录 2。
- 读入 5,先把之前的记录乘以 10,然后加上 5。现在记录了 25。
- 读入 6,先把之前的记录乘以 10,然后加上 6。现在记录了 256。
- 读到空格、换行符或者 EOF(读不到东西),就可以返回结果:256。
-
我们用
getchar()
逐个读进数字字符,再减去字符 ‘0’(ASCII码为 48) 即得对应的数字。
1.2 基础实现
#include
#include
int getint()
{
int res = 0, neg = 0, ch = getchar();
while(!(isdigit(ch) or ch == '-') and ch != EOF)
ch = getchar();
if(ch == '-')
neg = 1, ch = getchar();
while( isdigit(ch) )
res = res * 10 + (ch - '0'), ch = getchar();
return neg ? -res : res;
}
程序分析:现在该段程序对时间复杂度影响最大的为getchar()
函数
1.3 利用fread
优化普通快读(优化getchar()
函数)
1.3.1 fread()
函数详解
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
fread
函数的作用是从指定的文件流 stream
中读取数据,并将读取的数据存储到 ptr
指向的内存块中。它的参数含义如下:
-
ptr
:指向存储读取数据的内存块的指针。 -
size
:每个元素的大小(字节数)。 -
count
:要读取的元素个数。 -
stream
:要读取数据的文件流。
1.3.2 利用fread()
函数替代基础实现中的getchar()
函数
#include
#define BUFSIZE (1 << 20) // 1048576
char tmp[BUFSIZE];
int cnt = 0, Max = 0;
char getch()
{
if (cnt == Max) //第一次输入的情况
{
cnt = 0;
Max = fread(tmp, 1, BUFSIZE, stdin);
}
return tmp[cnt++];
}
1.3.3 嵌入getch()
的指针写法快读
#include
#include
#define BUFSIZE (1 << 20)
char ibuf[BUFSIZE], *is = *ibuf[0], *it = *ibuf[0];
char getch() {
if (is == it) {
it = (is = ibuf) + fread(ibuf, 1, BUFSIZE, stdin);
}
return (is == it) ? EOF : *is++;
}
int getint() {
int res = 0;
int neg = 0;
int ch = getch();
while (!(isdigit(ch) || ch == '-') && ch != EOF) {
ch = getch();
}
if (ch == '-') {
neg = 1;
ch = getch();
}
while (isdigit(ch)) {
res = res * 10 + (ch - '0');
ch = getch();
}
return neg ? -res : res;
}
编译运行、键盘输入数据时 fread()
无法停止?因为它期望得到太多字符,键盘输入时它不知道什么时候是末尾,所以你得告诉它“已经结束输入”。按 Ctrl + Z
后再回车就好了。
2. 快输
2.1 快输原理
-
关于负数,在负号后方输出绝对值。
-
正向输出一个数字。通过模 10 的方式我们可以直接获得它的最后一位,然后整个数除以 10 使原来最后第二位变成最后一位。但是注意这是获取了最后一位,因此不能直接逐位输出,要暂存后倒序输出。
-
第2步中,我们不断循环获取最后一位,退出条件是当前数剩余 0,所以,如果开始时这个数就是 0,就要特判后输出一个 0。0 确实是一个特殊的数,人为规定了用一个字符表示它。
-
用
putchar()
逐位输出数字。要输出的是数字字符,而我们模 10 后得到的是数,因此输出时要加上字符 ‘0’(或 48)。“当同一行中有多于一个元素时,须用单个空格符以作分隔。” -
间隔符号,如空格、换行,要另外输出。
2.2 基础实现
inline void putint(int res)
{
char q[10];
if(res == 0)
putchar('0');
else
if(res < 0)
{
putchar('-');
res = -res;
}
int top = 0;
while(res)
{
q[top++] = res % 10 + '0';
res /= 10;
}
while(top--)
putchar(q[top]);
}
2.3 利用fwrite
优化普通快输(优化putchar()
函数)
2.3.1 fwrite()
函数详解
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
fwrite
函数接受四个参数:
-
ptr
:指向要写入数据的内存块的指针。 -
size
:要写入的每个数据项的大小(以字节为单位)。 -
count
:要写入的数据项的数量。 -
stream
:指向目标文件的指针。
fwrite
函数将count
个数据项(每个大小为size
字节)从ptr
指向的内存块写入到由stream
指向的文件中。它返回成功写入的数据项数量。如果返回的数量与count
不一致,可能表示写入过程中发生了错误。
2.3.2 利用fwrite()
函数替代基础实现中的putchar()
函数
#include
#define BUFSIZE (1 << 20)
char tmp[BUFSIZE];
int cnt = 0;
void flush()
{
fwrite(tmp, 1, cnt, stdout);
cnt = 0;
}
void putch(char ch)
{
tmp[cnt++] = ch;
if (cnt == BUFSIZE)
flush();
}