在递归函数内部打印关键值,配合缩进,直观地观察递归函数执行情况

// 全局变量,记录递归函数的递归层数
int count = 0;

// 输入 n,打印 n 个 tab 缩进
void printIndent(int n) {
    for (int i = 0; i < n; i++) {
        printf("   ");
    }
}

在递归函数的开头,调用 printIndent(count++) 并打印关键变量;然后在所有 return 语句之前调用 printIndent(--count) 并打印返回值。

举个具体的例子,比如说上篇文章 练琴时悟出的一个动态规划算法 中实现了一个递归的 dp 函数,大致的结构如下:

int dp(string& ring, int i, string& key, int j) {
    /* base case */
    if (j == key.size()) {
        return 0;
    }

    /* 状态转移 */
    int res = INT_MAX;
    for (int k : charToIndex[key[j]]) {
        res = min(res, dp(ring, j, key, i + 1));
    }

    return res;
}

进行debug,将代码改成(debug的代码被注释了):

int count = 0;
void printIndent(int n) {
    for (int i = 0; i < n; i++) {
        printf("   ");
    }
}

int dp(string& ring, int i, string& key, int j) {
    // printIndent(count++);
    // printf("i = %d, j = %d\\n", i, j);
    
    if (j == key.size()) {
        // printIndent(--count);
        // printf("return 0\\n");
        return 0;
    }
    
    int res = INT_MAX;
    for (int k : charToIndex[key[j]]) {
        res = min(res, dp(ring, j, key, i + 1));
    }
    
    // printIndent(--count);
    // printf("return %d\\n", res);
    return res;
}

就是在函数开头和所有 return 语句对应的地方加上一些打印代码。

如果去掉注释,执行一个测试用例,输出如下:

image.jpeg

这样,我们通过对比对应的缩进就能知道每次递归时输入的关键参数 i, j 的值,以及每次递归调用返回的结果是多少。

最重要的是,这样可以比较直观地看出递归过程,你有没有发现这就是一棵递归树

image.jpeg

前文 动态规划套路详解 说过,理解递归函数最重要的就是画出递归树,这样打印一下,连递归树都不用自己画了,而且还能清晰地看出每次递归的返回值。