【算法競賽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中的判斷是要有意義的,不是白判斷的。

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