评论

收藏

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

编程语言 编程语言 发布于:2021-07-05 21:57 | 阅读数:568 | 评论:0

  【算法竞赛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.while,for循环是导致死循环的原因吗?
  • 9.二分法时,如果涉及到比较精度的问题,可能会因为精度而导致死循环。
  • 10.可能在有些变量没有初始化时,导致本地运行和评判机运行出现偏差。从而出现明明“对的程序”无法AC
  • 11.二分法的边界一定要搞清楚!十分重要
  • 12.一些常数的取法是否有问题?比如说 π 是取成3,还是3.14,还是3.1415926?如果题意没有说明,那么这些就是容易出现问题的地方。
  • 13.贪心算法能证明出来是正确的吗?如果不能,请谨慎使用,否则在你不仅WA了答案,还浪费了时间。
    给我这样的感受的题有很多,比如背包问题(让你求出平均价值最大的放法,如果是直接按照 价值/体积 形成的权重来从大到小放,是会出现问题的。);再比如络谷 P1514 引水入城 问题,这里是我的关于此题的题解,我最开始的想法是: dfs+贪心(失败)。直到WA之后才恍然大悟自己是多么幼稚!
  • 14.--y 与 y-- 是有很大区别的。
    第一种写法是先判断下一个,第二个是先判断当前的情况,再将y的值减一。即使是高手,有时候也会在这个简单的问题上犯糊涂,一定要注意!
  • 15.如果数组没有初始化,很有可能得到错误的答案。如果数组初始化不够(意思就是只初始化了一部分的数组),也会导致错误的答案。
    比如,有如下语句:
const int maxN = 105;
const int maxV = 100005;
int f[maxV];
fill(f,f+maxN,0);
  当计算 f 的值的时候,就很容易出现问题。这是一个很隐蔽的错误,而且不易察觉,大多数时候,我们都以为是自己写的代码或者是思想错了,哪知道问题在于这个细节问题!因为我们申请的是 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函数中可能会多次输出同一个答案,从而导致输出不对。
    DSC0000.png 因为一份电话名单可能会出现多个重复前缀的情况,如果这种输出放在build()函数里,就会出现错误!这个bug 我找了1h+。
  •   21.今天【20200621】在写洛谷的一道题【P3879 [TJOI2010]阅读理解】时,碰到一个bug,我觉得我写trie树应该是很熟练的了,但就是死活过不了…就是下面这种情况:
    DSC0001.png 于是,我想啊想,难道是因为我数组开的范围不够大? 代码内容如下:
    DSC0002.png
    在蹦出这个念头之后,我细想了一下,好像真有问题。而且对于这种二维数组,这种越界问题,就会导致**“修改数据”** 的情况。将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");
  }
}
  执行结果如下:
DSC0003.png
---------------------------------智慧分割线-------------------------------
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
DSC0004.png 需要针对具体的报错信息添加头文件。比如在上面的这个报错信息中,就需要添加#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";
}
DSC0005.png

4.2 long long 的范围大小
  同样的道理,我们可以获取到long long类型的最大、小值。
DSC0006.png
4.3 其它
  这个库里 (应该) 还有很多其它好东西,值得研究。
4.4 关于cpp中的一个整数是什么类型的讨论
  使用如下代码测试:
int main(){
cout << sizeof(1)<<"\n";
cout << sizeof(1ll)<<"\n";
}
  执行结果如下:
DSC0007.png
可以看到所占内存大小分别是4B和8B。
我就是因为这个问题没搞清楚,在写一道程序题时就出现了问题。本来可以用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 ;//一定不要少了这个边界条件
}

关注下面的标签,发现更多相似文章