【算法竞赛bug经验谈】编程经验总结【C/C++】

【算法竞赛bug经验谈】编程经验总结【C/C++】


0.总结

Get to the points first. The article comes from LawsonAbs!
  • 记录我自己编写算法题时犯过的错误

1.踩过的坑

写算法题时,悲催不是你的wrong answer,而是在你写完代码之后,你却发现你的思路存在纰漏。这里总结了一些我写题时的一些常犯错误,供大家编程时参考。

  • 1.见到大批的WA,想想题意搞清楚了吗?
  • 2.写<>号的时候,需要考虑会用到=号吗?
    上面这种问题,经常出现在二分法排序等情况下。
  • 3.写 if 条件的时候,要考虑后面的 else 是什么样的情况?是二选一的吗?if内的代码块有必要二选一执行吗?
  • 4.if中的条件是什么样的情况成立?&&||符号正确吗?
  • 5.数组下标越界了吗?
    如果出现Runtime Error,通常就是出现了数组越界的情况,这时,就需要考虑是否在判断数组边界是否越界。
  • 6.数组开的够大吗?是导致Runtime Error的原因吗?
  • 7.递归有明确的返回条件吗?递归哪里可能会出现死循环?
  • 8.whilefor循环是导致死循环的原因吗?
  • 9.二分法时,如果涉及到比较精度的问题,可能会因为精度而导致死循环。
  • 10.可能在有些变量没有初始化时,导致本地运行和评判机运行出现偏差。从而出现明明“对的程序”无法AC
  • 11.二分法的边界一定要搞清楚!十分重要
  • 12.一些常数的取法是否有问题?比如说 π 是取成3,还是3.14,还是3.1415926?如果题意没有说明,那么这些就是容易出现问题的地方。
  • 13.贪心算法能证明出来是正确的吗?如果不能,请谨慎使用,否则在你不仅WA了答案,还浪费了时间。
    给我这样的感受的题有很多,比如揹包问题(让你求出平均价值最大的放法,如果是直接按照 价值/体积 形成的权重来从大到小放,是会出现问题的。);再比如络谷 P1514 引水入城 问题,这里是我的关于此题的题解,我最开始的想法是: dfs+贪心(失败)。直到WA之后才恍然大悟自己是多么幼稚!
  • 14.--yy--有很大区别的。
    第一种写法是先判断下一个,第二个是先判断当前的情况,再将y的值减一。即使是高手,有时候也会在这个简单的问题上犯糊涂,一定要注意!
  • 15.如果数组没有初始化,很有可能得到错误的答案。如果数组初始化不够(意思就是只初始化了一部分的数组),也会导致错误的答案。
    比如,有如下语句:
const int maxN = 105;
const int maxV = 100005;
int f[maxV];
fill(f,f+maxN,0);

当计算 f[i] 的值的时候,就很容易出现问题。这是一个很隐蔽的错误,而且不易察觉,大多数时候,我们都以为是自己写的代码或者是思想错了,哪知道问题在于这个细节问题!因为我们申请的是 maxV大小的 int 型数组,结果只初始化了一部分(maxN大小),导致WA!我就曾因为这个问题浪费了30min 来找bug!【蒟蒻实证!】

  • 16.复制得到的代码一定要对具体的条件做修改,否则很难发现这种隐蔽的问题。常见的复制代码的情况有:
    01.朝不同的方向【N,S,E,W】行驶,比如迷宫问题,棋盘问题等等。
    02.switch...case语句中的条件
    03.if...else if...else中语句的条件

  • 17.代码顺序的颠倒可能会导致严重的错误。
    【20200410】我在写一道 dijkstra 算法题时,计算最短路径的个数。但是因为搞反了两条语句,就几乎WA了大半的结果。编码十分钟,调试2小时!!血的教训。写代码时一定要全神贯注,切莫开小差!

  • 18.不要拿到一道题就开始编代码, 一定要先动脑,再动手!否则就是编码10分钟,调试10小时。

  • 19.做题有大致的步骤:
    (1)抽象题目。对应于学过的什么知识点,主要考什么?
    (2)分析时间复杂度,该用什么样的算法解决这个问题?【这一步骤很关键,我经常把贪心的题搞出dp,然后怎么也推导不出来dp的转移方程,后来才知道应该是O(N)复杂度的贪心。-_-||

  • 20.昨天【20200620】在写洛谷的一道题【SP4033 PHONELST - Phone List 】的时候,我因为把输出放到main外的void函数之中,导致在调用这个void函数中可能会多次输出同一个答案,从而导致输出不对。
    在这里插入图片描述因为一份电话名单可能会出现多个重复前缀的情况,如果这种输出放在build()函数里,就会出现错误!这个bug 我找了1h+。

  • 21.今天【20200621】在写洛谷的一道题【P3879 [TJOI2010]阅读理解】时,碰到一个bug,我觉得我写trie树应该是很熟练的了,但就是死活过不了…就是下面这种情况:
    在这里插入图片描述于是,我想啊想,难道是因为我数组开的范围不够大? 代码内容如下:
    在这里插入图片描述
    在蹦出这个念头之后,我细想了一下,好像真有问题。而且对于这种二维数组,这种越界问题,就会导致**“修改数据”** 的情况。将endF[maxN]maxN] 修改为 endF[maxN][maxM] 就可以一下全部AC了。【因为题目中说一篇短文的字数<103,而且每个单词的长度小于20。所以在去掉部分空格之后,即使没有任何公共的节点,都可以在20000内将所有节点全部容纳!所以可以这么定义一个二维数组。】

跟随上面这个问题,我们来看一个很好玩儿的东西。

#include<iostream>
using  namespace std;
const int maxN = 10;
int arr[maxN][maxN]; //

int tot = 0; //记住tot不要超过100就行
int main(){
    for(int i = 0;i<10;i++)
        for(int j = 0;j< 10;j++)
            arr[i][j] = 1;//全部赋值为1

    //下面见证一下“奇迹”
    int cnt = 1;
    for(int i = 0;i<5;i++){
        /*主要j的取值 =>
         01.因为这里j的取值越界了,但是整个二维数组的值
         却没有越界,所以你会得到Wrong Answer的界面,但是得不到Runtime Error
         的提醒。
         02.同时,可以看到这里的赋值手法有点儿“一维”状态空间的意思。
         03.在起初的位置上,往后推20个。比如arr[4][20]就是在arr[4][0]后推20个,
         这样得到的结果就是arr[4][0] = 81,…… arr[4][20] = arr[5][10] = 100;
          */
        for(int j = 0;j<20;j++){
            arr[i][j] = cnt ;
            cnt ++;
        }
    }

    for(int i = 0;i< 10;i++){
        for(int j = 0;j< 10;j++){
            printf("%4d",arr[i][j]);
        }printf("\n");
    }
}

执行结果如下:
在这里插入图片描述

---------------------------------智慧分割线-------------------------------

2.define 使用不当

#define N 100000000001
define 定义的值不能过大,否则会到值编译错误,停止工作。

#define的用法不仅仅局限于对数据,同样对于字符串,以及其他的都是适用的,比如以下的程序。

#include <stdio.h>
#define P printf("I Love programming\n");
 
int main()
{
    P;
    return 0;
}

如果觉得define不好使,也可以使用const来定义静态变量。

3. 未引用正确的方法库

报错:[Error] 'strcmp' was not declared in this scope
需要针对具体的报错信息添加头文件。比如在上面的这个报错信息中,就需要添加#include<cstring>【c++】或者是#include<string.h>【c】。

4.整型的范围【非常重要!】

算法题中经常会出现整数范围的考察,这是一个非常基础但重要的知识点,务必掌握。这里我讲解一些最常用的整型。

4.1 int范围大小

int声明的数据是带正负号的整数,一般情况其占内存大小是4B,即32位,有一位是符号位,故最大正整数是2^31-1,最大负数是2^31【这与计算机的存储方式有关,因为是二进制补码存储】。在 #include<climits> 头文件中可以看到int的最大值和最小值。代码如下所示:

#include<iostream>
#include<climits>
using namespace std;

int main(){
	cout << "INT_MAX="<< INT_MAX<<"\n";
	cout << "INT_MIN="<< INT_MIN<<"\n";
}

在这里插入图片描述

4.2 long long 的范围大小

同样的道理,我们可以获取到long long类型的最大、小值。
在这里插入图片描述

4.3 其它

这个库里 (应该) 还有很多其它好东西,值得研究。

4.4 关于cpp中的一个整数是什么类型的讨论

使用如下代码测试:

int main(){
	cout << sizeof(1)<<"\n";
	cout << sizeof(1ll)<<"\n";	
}

执行结果如下:
在这里插入图片描述
可以看到所占内存大小分别是4B8B
我就是因为这个问题没搞清楚,在写一道程序题时就出现了问题。本来可以用long long存储的数,我自己硬是写了一个高精!

5.二叉树相关常见错误

5.1 对指针理解不够

如下代码是我在为每个节点设置树高。但是其中有一处很明显的错误。

void btHigh(node* root)
{
    if (root == NULL){
    	root->height = 0;
    	return ;//一定不要少了这个边界条件 
	}
    btHigh(root->lchild);
    btHigh(root->rchild);
    //二叉树的高度为左子树和右子树中高的那个高度加1
    int ret1 = root->lchild->height;    
    int ret2 = root->rchild->height;    
    root->height = ret1 > ret2 ? ret1 + 1 : ret2 + 1;
}

里面存在的错误代码是:

if (root == NULL){
    	root->height = 0;
    	return ;//一定不要少了这个边界条件 
}

root->height = 0是典型的低级错误,怎么能在root=NULL的条件下给root->height 赋值呢? 也就是说,if中的判断是要有意义的,不是白判断的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章