谈到算法竞赛中使用Java,那么有一个绕不开的点就是如何快速地输入输出。通常来说,Scanner类固然可以帮助我们顺利地完成各种输入要求,而syso(System.out.print)也能够满足一般的输出要求,但是在内存以及时间的消耗上却不尽人意。同时,我发现国内博客似乎对Java在算法竞赛方面的技巧讨论得比较少,即使有也相对分散,不够深入。
那么今天我就想分享一下自己在网上所看到的一些Java在算法竞赛方面的小技巧,希望对以Java作为算法竞赛主要语言的人有所帮助。
算法竞赛的输入模式中,最难处理的莫过于“输入包括多行,读到文件末尾为止”这种输入要求,那今天就以这种输入要求为例。
利用StreamTokenizer
//从标准输入流输入
StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
比如经典的A+B问题,就可以这么去写
StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
while(st.nextToken()!=StreamTokenizer.TT_EOF) {
int a,b;
a=(int)st.nval;
st.nextToken();
b=(int)st.nval;
pw.println(a+b);
}
如果处理的是字符串,则应该这么写
StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
st.nextToken();
String str = st.sval;
即每次先使用st.nextToken(),再调用nval或sval。
代码量少,便于记忆。
不能将12345这样的纯数字字符串通过sval读入,StreamTokenizer会将其识别为数字Token,只能通过nval读入,再利用String.valueOf()等方法转化为字符串。
2. 利用InputReader(非内建类)
//本人有少许改动
static class InputReader{
BufferedReader br;
StringTokenizer st;
public InputReader(){
br = new BufferedReader(new InputStreamReader(System.in));
}
String next() {
while(st==null||!st.hasMoreTokens()) {
try {
st = new StringTokenizer(br.readLine());
}catch(Exception e) {
break;
}
}
return st.nextToken();
}
int nextInt() {
return Integer.parseInt(next());
}
String nextLine(){
String str = "";
try {
str = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
BigInteger nextBigInteger() {
BigInteger bi = null;
try {
bi = new BigInteger(br.readLine());
}catch(Exception e) {
}
return bi;
}
}
InputReader应该是Java快读方面用得比较多的一个类了,它是由Petr大神设计的一个类,具有适用面广泛,相对好记的特点。(虽然其中使用到的StringTokenizer在官方文档中被评价道:"StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code." 但是借用同学的话,这是宝贵的遗产,233)
使用的话非常简单,他的API名称跟Scanner保持了一致,也能直接“顾名思义”。
那么如何处理多行输入呢,我是这么写的:
InputReader ir = new InputReader();
int a,b;
try {
while((a = ir.nextInt())!= Float.NaN && ( b = ir.nextInt())!= Float.NaN ) {
System.out.println(a+b);
}
}catch(Exception e) {
}
通用性强,易用性高
速度上也许是本文三种快速读中相对最慢的(相差不大),相比第一种方法相对要记多一些代码
3. 自定义读取和处理字节的函数
private byte[] inbuf = new byte[1024];
public int lenbuf = 0, ptrbuf = 0;
private int readByte()
{
if(lenbuf == -1)throw new InputMismatchException();
if(ptrbuf >= lenbuf){
ptrbuf = 0;
try { lenbuf = System.in.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); }
if(lenbuf <= 0)return -1;
}
return inbuf[ptrbuf++];
}
private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); }
private int skip() { int b; while((b = readByte()) != -1 && isSpaceChar(b)); return b; }
private double nd() { return Double.parseDouble(ns()); }
private char nc() { return (char)skip(); }
private String ns()
{
int b = skip();
StringBuilder sb = new StringBuilder();
while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ')
sb.appendCodePoint(b);
b = readByte();
}
return sb.toString();
}
private char[] ns(int n)
{
char[] buf = new char[n];
int b = skip(), p = 0;
while(p < n && !(isSpaceChar(b))){
buf[p++] = (char)b;
b = readByte();
}
return n == p ? buf : Arrays.copyOf(buf, p);
}
private char[][] nm(int n, int m)
{
char[][] map = new char[n][];
for(int i = 0;i < n;i++)map[i] = ns(m);
return map;
}
private int[] na(int n)
{
int[] a = new int[n];
for(int i = 0;i < n;i++)a[i] = ni();
return a;
}
private int ni()
{
int num = 0, b;
boolean minus = false;
while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
if(b == '-'){
minus = true;
b = readByte();
}
while(true){
if(b >= '0' && b <= '9'){
num = num * 10 + (b - '0');
}else{
return minus ? -num : num;
}
b = readByte();
}
}
private long nl()
{
long num = 0;
int b;
boolean minus = false;
while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
if(b == '-'){
minus = true;
b = readByte();
}
while(true){
if(b >= '0' && b <= '9'){
num = num * 10 + (b - '0');
}else{
return minus ? -num : num;
}
b = readByte();
}
}
上述相关函数由uwi大神设计,orz,自己亲自处理字节,速度当然是杠杠的。函数名符合一贯竞赛er的抽象惯例 在处理多行A+B问题时,我的写法和方法二类似,即:
int a,b;
try {
while((a = ni())!= Float.NaN && ( b = ni())!= Float.NaN ) {
System.out.println(a+b);
}
}catch(Exception e) {
}
速度快 够装逼
emmm,应该不太可能现场背下来,比较适合网络赛和平时练习之类的
相比于快速输入的各种神仙写法,快速输出可以说是非常友好了,不仅便于记忆,而且能在很短的时间内写出来。
PrintWriter pw = new PrintWriter(System.out);
pw.println(//和System.out.println用法一样);
pw.close();//这句话在最后必须有,不然不会将结果输出
由于PrintWriter自带Buffer,所以不需要再对System.out再套(娃)其他的缓冲输出流。简直不要太好用有木有?
快速输入输出这套组合拳,如果你拿来跟之前的Scanner和System.out输入输出相比,你就会发现时间和空间耗费大大减少了,以前A不过的题再也不是问题了。并不
还有一个问题,爆栈了怎么办?特别是遇上数据量大的题目,爆栈简直不要太容易,当然我们可以通过改递归为遍历等方法来规避这个问题,但是有没有什么奇技淫巧呢?当然有:
//还是以A+B问题为例
public class Main implements Runnable {
public static void main(String[] args) {
//whatever可替换为任何内容
new Thread(null, new Main(), "whatever", 1<<26).start();
}
public void run() {
Scanner cin = new Scanner(System.in);
int a, b;
while (cin.hasNext()){
a = cin.nextInt(); b = cin.nextInt();
System.out.println(a + b);
}
}
}
没错,方法就是开一个线程,通过为线程申请空间从而解决这个问题。但是注意,一是start方法执行后也许不会立即执行run方法,二是申请的空间大小是平台相关的,不保证一定有效。
后记:虽然本文介绍了快速读写和加栈的方法,但是归根结底,算法竞赛重点还是希望大家写出高效的算法,这些只是一些辅助的内容,时间复杂度不对,单凭这些“优化”仍是无法解决问题的。(此外,遇到腹黑的出题人,即便你的时间复杂度没问题,也用了快速输入输出,仍然是无法AC的,这就要谈到Java在算法竞赛圈中的卑微地位了(划掉),所以最好的优化就是使用C++(狗头)。而关于如何转向C++,可以参考我的第一篇博客。)
要是觉得不错,就点个“推荐”吧 φ(゜▽゜*)♪
如有错误,还望不吝指出。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章