title: 递归
date: 2019-03-13 23:44:00
categories: 代码
tags: 算法学习
mathjax: true

递归从入门到精通

递归入门

编写一个递归函数

  • 这个递归函数的功能是什么,怎样调用这个函数,即设计好递归函数的返回值和参数列表
  • 什么时候应该结束这个递归,它的边界条件(出口)是什么 (边界条件)
  • 在非边界情况时,怎样从第n层转变成第n+1层 (递推公式)

计算阶乘(factorial)

递归原理详解 - 图1!%20%26%20n%3E0%0A%5Cend%7Bcases%7D%0A#card=math&code=%0An%21%3D%5Cbegin%7Bcases%7D%0A1%20%26%20n%3D0%20%5C%5C%0An%2A%28n-1%29%21%20%26%20n%3E0%0A%5Cend%7Bcases%7D%0A&id=mQs4R)

  1. int fact(int n){
  2. if (n == 0) return 1;
  3. return n * fact(n - 1);
  4. }
  5. int ans = fact(10); //调用(递归)函数

计算最大公约数(辗转相除法)

  1. gcd(12, 32) = 4
  2. gcd(a, b)
  3. gcd(32, 12)
  4. gcd(12, 8)
  5. gcd(8, 4)
  6. gcd(4, 0)

递归原理详解 - 图2%3D%5Cbegin%7Bcases%7D%0Aa%20%26%20b%3D0%20%5C%5C%0Agcd(b%2Ca%20%5C%25%20b)%20%26%20b%5Cneq%200%0A%5Cend%7Bcases%7D%0A#card=math&code=%0Agcd%28a%2Cb%29%3D%5Cbegin%7Bcases%7D%0Aa%20%26%20b%3D0%20%5C%5C%0Agcd%28b%2Ca%20%5C%25%20b%29%20%26%20b%5Cneq%200%0A%5Cend%7Bcases%7D%0A&id=BLBUt)

  1. int gcd(int a, int b){
  2. if (b == 0) return a;
  3. return gcd(b, a % b);
  4. }
  5. int ans = gcd(12, 32);

分治算法

分治法的设计思想:

  1. 分–将问题分解为规模更小的子问题;
  2. 治–将这些规模更小的子问题逐个击破;
  3. 合–将已解决的子问题合并,最终得出“母”问题的解;
  • 减而治之(每次让问题的规模减1)
  • 分而治之(每次让问题的规模减半)(归并排序的思想)

例题:走楼梯

  1. 题目描述:
  2. 一个台阶总共有n级,如果一次可以跳1级,也可以跳2级。求总共有多少总跳法。
  3. 第一行输入T,表示有多少个测试数据。接下来T行,每行输入一个数n,表示台阶的阶数。
  4. 输出时每一行对应一个输出。
  5. 样例输入:
  6. 3
  7. 5
  8. 8
  9. 10
  10. 样例输出:
  11. 8
  12. 34
  13. 89

解析:

递归原理详解 - 图3%3D%5Cbegin%7Bcases%7D%0A1%20%26%20n%3D1%20%5C%5C%0A2%20%26%20n%3D2%20%5C%5C%0Af(n-1)%2Bf(n-2)%20%26%20n%3E2%0A%5Cend%7Bcases%7D%0A#card=math&code=%0Af%28n%29%3D%5Cbegin%7Bcases%7D%0A1%20%26%20n%3D1%20%5C%5C%0A2%20%26%20n%3D2%20%5C%5C%0Af%28n-1%29%2Bf%28n-2%29%20%26%20n%3E2%0A%5Cend%7Bcases%7D%0A&id=UWn9A)

参考代码:

  1. #include <stdio.h>
  2. int solve(int n) {
  3. if (n == 1) return 1;
  4. if (n == 2) return 2;
  5. return solve(n - 1) + solve(n - 2);
  6. }
  7. int main() {
  8. int T;
  9. scanf("%d", &T);
  10. while (T--) {
  11. int n;
  12. scanf("%d", &n);
  13. int ans = solve(n);
  14. printf("%d\n", ans);
  15. }
  16. return 0;
  17. }

归并排序

{% asset_img 图02-19.归并排序实例.png 归并排序实例 %}
图02-19.归并排序实例.png

  1. void mergeSort(int A[], int lo, int hi) {
  2. if (lo >= hi) return;
  3. int mid = lo + (hi - lo) / 2;
  4. mergeSort(A, lo, mid); //左半区间[lo, mid] 排好序
  5. mergeSort(A, mid + 1, hi); //右半区间[mid + 1, hi] 排好序
  6. mergeArray(A, lo, mid, hi); //进行合并
  7. }

{% asset_img 图02-18.有序向量的二路归并实例.png 有序向量的二路归并 %}
图02-18.有序向量的二路归并实例.png

  1. #include <iostream>
  2. using namespace std;
  3. void mergeArray(int A[], int lo, int mid, int hi) {
  4. int* temp = new int[hi - lo + 1];
  5. int i = lo, j = mid + 1;
  6. int k = 0;
  7. while (i <= mid && j <= hi) {
  8. if (A[i] <= A[j]) temp[k++] = A[i++];
  9. else temp[k++] = A[j++];
  10. }
  11. while (i <= mid) temp[k++] = A[i++];
  12. while (j <= hi) temp[k++] = A[j++];
  13. for (int i = lo, k = 0; i <= hi; i++, k++) {
  14. A[i] = temp[k];
  15. }
  16. delete[] temp;
  17. }
  18. void mergeSort(int A[], int lo, int hi) {
  19. if (lo >= hi) return;
  20. int mid = lo + (hi - lo) / 2;
  21. mergeSort(A, lo, mid); //左半区间[lo, mid] 排好序
  22. mergeSort(A, mid + 1, hi); //右半区间[mid + 1, hi] 排好序
  23. mergeArray(A, lo, mid, hi); //进行合并
  24. }
  25. int main() {
  26. int A[] = { 6, 1, 2, 9, 7, 3 };
  27. int N = sizeof(A) / sizeof(int);
  28. mergeSort(A, 0, N - 1);
  29. for (int x : A) {
  30. cout << x << " ";
  31. }
  32. cout << endl;
  33. return 0;
  34. }

二叉树、树

二叉树的遍历

  1. struct Node { //C++版本
  2. int data;
  3. Node *lchild, *rchild;
  4. Node(int x = 0) { data = x; lchild = rchild = NULL; }
  5. };

先序遍历

{% asset_img 图05-28.二叉树先序遍历序列-1549953653371.png 二叉树先序遍历序列 %}
图05-28.二叉树先序遍历序列.png

  1. void preorderTrav(Node* root) { //先序遍历
  2. if (root == NULL) return;
  3. printf("%d ", root->data); //最先访问根节点
  4. preorderTrav(root->lchild);
  5. preorderTrav(root->rchild);
  6. }
  7. void preorderTrav(Node* root) { //写法2
  8. if (root != NULL) {
  9. printf("%d ", root->data);
  10. preorderTrav(root->lchild);
  11. preorderTrav(root->rchild);
  12. }
  13. }
  14. void preorderTrav(Node* root) { //写法3
  15. //if (root == NULL) return; //只要能保证这棵树不是空树,这行代码就可以省略
  16. printf("%d ", root->data);
  17. if (root->lchild != NULL) {
  18. preorderTrav(root->lchild);
  19. }
  20. if (root->rchild != NULL) {
  21. preorderTrav(root->rchild);
  22. }
  23. }

中序遍历

{% asset_img 图05-30.二叉树的中序遍历序列.png 二叉树的中序遍历序列 %}
图05-30.二叉树的中序遍历序列.png

  1. void inorderTrav(Node* root) { //中序遍历
  2. if (root == NULL) return;
  3. inorderTrav(root->lchild);
  4. printf("%d ", root->data); //中间的时候访问根节点
  5. inorderTrav(root->rchild);
  6. }

二叉树节点在水平方向上的投影顺序即为中序遍历的顺序。

后序遍历

{% asset_img 图05-29.二叉树的后序遍历序列.png 二叉树的后序遍历序列 %}
图05-29.二叉树的后序遍历序列.png

  1. void postorderTrav(Node* root) { //后序遍历
  2. if (root == NULL) return;
  3. postorderTrav(root->lchild);
  4. postorderTrav(root->rchild);
  5. printf("%d ", root->data); //最后访问根节点
  6. }

求二叉树的高度

  1. int getTreeHigh(Node* root) {
  2. if (root == NULL) return 0;
  3. int left_high = getTreeHigh(root->lchild);
  4. int right_high = getTreeHigh(root->rchild);
  5. return max(left_high, right_high) + 1;
  6. }

完整实现代码

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. struct Node {
  5. int data;
  6. Node *lchild, *rchild;
  7. Node(int x = 0) { data = x; lchild = rchild = NULL; }
  8. void setChildNode(Node* l, Node* r) {
  9. lchild = l;
  10. rchild = r;
  11. }
  12. };
  13. void preorderTrav(Node* root) { //先序遍历
  14. if (root == NULL) return;
  15. printf("%d ", root->data); //最先访问根节点
  16. preorderTrav(root->lchild);
  17. preorderTrav(root->rchild);
  18. }
  19. void inorderTrav(Node* root) { //中序遍历
  20. if (root == NULL) return;
  21. inorderTrav(root->lchild);
  22. printf("%d ", root->data); //中间的时候访问根节点
  23. inorderTrav(root->rchild);
  24. }
  25. void postorderTrav(Node* root) { //后序遍历
  26. if (root == NULL) return;
  27. postorderTrav(root->lchild);
  28. postorderTrav(root->rchild);
  29. printf("%d ", root->data); //最后访问根节点
  30. }
  31. int getTreeHeight(Node* root) {
  32. if (root == NULL) return 0;
  33. int left_high = getTreeHeight(root->lchild);
  34. int right_high = getTreeHeight(root->rchild);
  35. return max(left_high, right_high) + 1;
  36. }
  37. int main() {
  38. //完全可以用一个 Node* nodes[100] 数组来存放这些节点
  39. Node* root = new Node(1);
  40. Node* node_2 = new Node(2);
  41. Node* node_3 = new Node(3);
  42. Node* node_4 = new Node(4);
  43. Node* node_5 = new Node(5);
  44. Node* node_6 = new Node(6);
  45. Node* node_7 = new Node(7);
  46. //Node* node_8 = new Node(8);
  47. //root->lchild = node_2;
  48. //root->rchild = node_3;
  49. root->setChildNode(node_2, node_3);
  50. node_2->setChildNode(node_4, node_5);
  51. node_3->setChildNode(NULL, node_7);
  52. node_5->setChildNode(node_6, NULL);
  53. node_4->rchild = new Node(8);
  54. preorderTrav(root);
  55. printf("\n");
  56. inorderTrav(root);
  57. printf("\n");
  58. postorderTrav(root);
  59. printf("\n");
  60. printf("Tree Height: %d\n", getTreeHeight(root));
  61. return 0;
  62. }

递归原理详解 - 图9
下载-1533545465415.png

表达式树的输出与求值

表达式树的特征:叶节点是运算数,非叶节点一定是运算符

递归原理详解 - 图11
20170603205711071.jpg

输入格式:

第一行给出节点的个数N,每个节点的编号为0 ~ N-1

接下来N行每行分别给出:

     该节点的编号、该节点的操作数/操作符、该节点的左孩子编号、右孩子编号(-1表示NULL)

输出格式:

第一行输出该表达式树的中缀表达式,该用括号的地方需要用括号括起来。

第二行输出该表达式树的前缀表达式。

第二行输出该表达式树的后缀表达式。

第四行输出该表达式树的计算结果,保留两位小数。

样例输入:

11
0 - 1 2
1 + 3 4
2 / 5 6
3 4 -1 -1
4 * 7 8
5 6 -1 -1
6 3 -1 -1
7 1 -1 -1
8 - 9 10
9 5 -1 -1
10 2 -1 -1

样例输出:

(4+(1*(5-2)))-(6/3)
- + 4 * 1 - 5 2 / 6 3
4 1 5 2 - * + 6 3 / -
5.00

完整代码:

#include <iostream>
using namespace std;

struct Node {
    char data;
    Node *lchild, *rchild;
};

void preOrder(Node* root) {                //前缀表达式
    if (root == NULL) return;
    printf("%c ", root->data);
    preOrder(root->lchild);
    preOrder(root->rchild);
}

void postOrder(Node* root) {            //后缀表达式
    if (root == NULL) return;
    postOrder(root->lchild);
    postOrder(root->rchild);
    printf("%c ", root->data);
}

void inOrder(Node* root, int layer) {    //中缀表达式
    if (root == NULL) return;
    if (root->lchild == NULL && root->rchild == NULL) {
        //叶结点是操作数,直接输出,不加括号
        printf("%c", root->data);
    } else {
        //非叶节点是操作符,需加括号(第0层根节点除外)
        if (layer > 0) printf("(");
        inOrder(root->lchild, layer + 1);
        printf("%c", root->data);
        inOrder(root->rchild, layer + 1);
        if (layer > 0) printf(")");
    }
}

double calc(double a, double b, char op) {
    switch (op) {
    case '+': return a + b;
    case '-': return a - b;
    case '*': return a * b;
    case '/': return a / b;
    }
}

double calculateExprTree(Node* root) {
    if (root == NULL) return 0;
    if (root->lchild == NULL && root->rchild == NULL) {
        //叶节点,节点存放的是 操作数
        return root->data - '0';
    }
    //非叶结点,节点存放的是 操作符
    double a = calculateExprTree(root->lchild);
    double b = calculateExprTree(root->rchild);
    return calc(a, b, root->data);
}

int main() {
    int N;
    scanf("%d\n", &N);
    Node* nodes = new Node[N];
    for (int i = 0; i < N; i++) {
        int index;
        char data;
        int l, r;
        scanf("%d %c %d %d\n", &index, &data, &l, &r);
        nodes[index].data = data;
        nodes[index].lchild = (l != -1 ? &nodes[l] : NULL);
        nodes[index].rchild = (r != -1 ? &nodes[r] : NULL);
    }
    Node* root = &nodes[0];

    inOrder(root, 0);
    printf("\n");

    preOrder(root);
    printf("\n");

    postOrder(root);
    printf("\n");

    double ans = calculateExprTree(root);
    printf("%.2f\n", ans);

    return 0;
}
enum Operator { Add, Subtract, Multiply, Divide };

struct Node {
    double num;
    Operator op;
    int type;    //0-运算数,1-二元运算符
    Node *lchild, *rchild;
};

求某节点到根节点的路径

对于如下二叉树,节点7位于第4层,其到跟节点的路径为1 2 5 7

递归原理详解 - 图13
1532752744356.png

求某节点所在层数

先把问题简化一下,求二叉树指定节点所在层数(假设根节点的层数为1)

为了记录当前访问节点的层号,对于层号,可以采用以下两种方式:

  1. 使用全局变量
int layer = 0;
bool flag1 = false;    //flag标记可用于提前快速结束递归的执行
void getNodeLayer(Node* root, int x) {
    if (root == NULL) return;
    if (flag1) return;
    layer++;
    if (root->data == x) {
        printf("%d\n", layer);
        flag1 = true;
        return;
    }
    getNodeLayer(root->lchild, x);
    getNodeLayer(root->rchild, x);
    layer--;
}

int main(){
    //......
    getNodeLayer(root, 7);
}
  1. 使用函数传参(值传递)
bool flag1 = false;    //flag标记可用于提前快速结束递归的执行
void getNodeLayer(Node* root, int x, int layer) {
    if (root == NULL) return;
    if (flag1) return;
    if (root->data == x) {
        printf("%d\n", layer);
        flag1 = true;
        return;
    }
    getNodeLayer(root->lchild, x, layer + 1);
    getNodeLayer(root->rchild, x, layer + 1);
}

int main(){
    //......
    getNodeLayer(root, 7, 1);
}
  1. 使用函数传参(传指针/引用)
bool flag1 = false;    //flag标记可用于提前快速结束递归的执行
void getNodeLayer(Node* root, int x, int &layer) {
    if (root == NULL) return;
    if (flag1) return;
    layer++;
    if (root->data == x) {
        printf("%d\n", layer);
        flag1 = true;
        return;
    }
    getNodeLayer(root->lchild, x, layer);
    getNodeLayer(root->rchild, x, layer);
    layer--;
}

int main() {
    //.......
    int layer = 0;
    getNodeLayer(root, 7, layer);
}
  1. 将递归函数封装
void getNodeLayer(Node* root, int x, int &layer, int &ans, bool &flag) {
    if (root == NULL) return;
    if (flag) return;
    layer++;
    if (root->data == x) {
        ans = layer;
        flag = true;
        return;
    }
    getNodeLayer(root->lchild, x, layer, ans, flag);
    getNodeLayer(root->rchild, x, layer, ans, flag);
    layer--;
}

int getNodeLayer(Node* root, int x) {
    int ans;
    int layer = 0;
    bool flag = false;
    getNodeLayer(root, x, layer, ans, flag);
    return ans;
}

求节点路径

递归原理详解 - 图15
1532752744356.png

  1. 使用全局数组
vector<int> path;
bool flag2 = false;
void getNodePath(Node* root, int x) {
    if (root == NULL) return;
    if (flag2) return;
    path.push_back(root->data);
    if (root->data == x) {
        for (int x : path) {    //输出栈的内容
            printf("%d ", x);
        }
        flag2 = true;
        return;
    }
    getNodePath(root->lchild, x);
    getNodePath(root->rchild, x);
    path.pop_back();
}
vector<int> vec {1,2,3,4,5,6,7,8,9,10};
for (int i = 0; i < vec.size(); i++){
    printf("%d ", vec[i]);
}
//C++11特性:范围的For循环(range-based-for)
for (int x : vec) {    //这样写对于容器内的每个元素是只读的
    printf("%d ", x);
}
  1. 使用函数传参(传指针/引用)
bool flag = false;
void getNodePath(Node* root, int x, vector<int> &path) {
    if (root == NULL) return;
    if (flag) return;
    path.push_back(root->data);
    if (root->data == x) {
        for (int x : path) {    //输出栈path的内容
            printf("%d ", x);
        }
        flag = true;
        return;
    }
    getNodePath(root->lchild, x, path);
    getNodePath(root->rchild, x, path);
    path.pop_back();
}

int main() {
    //......
    int x = 7;
    vector<int> path;
    getNodePath(root, x, path);
}
  1. 使用函数传参(值传递)

不推荐,每次递归调用时,都会对path数组进行复制,时间和空间效率都很低

递归原理详解 - 图17
1532752744356.png

bool flag = false;
void getNodePath(Node* root, int x, vector<int> path) {
    if (root == NULL) return;
    if (flag) return;
    path.push_back(root->data);
    if (root->data == x) {
        for (int x : path) {    //输出栈path的内容
            printf("%d ", x);
        }
        flag = true;
        return;
    }
    getNodePath(root->lchild, x, path);
    getNodePath(root->rchild, x, path);
    //注意这里没有path.pop_back()
}

int main() {
    //......
    int x = 7;
    vector<int> path;
    getNodePath(root, x, path);
}

完整代码

对于如下二叉树,节点7位于第4层,其到跟节点的路径为1 2 5 7

递归原理详解 - 图19
1532752744356.png

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

struct Node {
    int data;
    Node *lchild, *rchild;

    Node(int x = 0) { data = x; lchild = rchild = NULL; }

    void setChildNode(Node* l, Node* r) {
        lchild = l;
        rchild = r;
    }
};

int layer = 0;
bool flag1 = false;    //flag标记可用于提前快速结束递归的执行
void getNodeLayer(Node* root, int x) {
    if (root == NULL) return;
    if (flag1) return;
    layer++;
    if (root->data == x) {
        printf("%d\n", layer);
        flag1 = true;
        return;
    }
    getNodeLayer(root->lchild, x);
    getNodeLayer(root->rchild, x);
    layer--;
}

vector<int> path;
bool flag2 = false;
void getNodePath(Node* root, int x) {
    if (root == NULL) return;
    if (flag2) return;
    path.push_back(root->data);
    if (root->data == x) {
        for (int x : path) {    //输出栈path的内容
            printf("%d ", x);
        }
        flag2 = true;
        return;
    }
    getNodePath(root->lchild, x);
    getNodePath(root->rchild, x);
    path.pop_back();
}

int main() {
    Node* root = new Node(1);
    Node* node_2 = new Node(2);
    Node* node_3 = new Node(3);
    Node* node_4 = new Node(4);
    Node* node_5 = new Node(5);
    Node* node_6 = new Node(6);
    Node* node_7 = new Node(7);
    Node* node_8 = new Node(8);

    root->setChildNode(node_2, node_3);
    node_2->setChildNode(node_4, node_5);
    node_3->setChildNode(NULL, node_6);
    node_5->setChildNode(node_7, NULL);
    node_7->setChildNode(NULL, node_8);

    int x = 7;
    getNodeLayer(root, x);
    getNodePath(root, x);
    printf("\n");

    return 0;
}

普通树的遍历

递归原理详解 - 图21
20170325215438522.png

#include <iostream>
#include <vector>
#include <queue>
#include <initializer_list>
using namespace std;

struct Node {
    int data;
    vector<Node*> child;

    Node(int x = 0) { data = x; }

    void setChildNode(initializer_list<Node*> il) {
        //C++11特性:initializer_list
        for (Node* x : il) {
            child.push_back(x);
        }
    }
};

void preOrder(Node* root) {
    if (root == NULL) return;
    printf("%d ", root->data);
    for (Node* x : root->child) {
        preOrder(x);
    }
}

void postOrder(Node* root) {
    if (root == NULL) return;
    for (Node* x : root->child) {
        postOrder(x);
    }
    printf("%d ", root->data);
}

void levelOrder(Node* root) {
    if (root == NULL) return;
    queue<Node*> Q;
    Q.push(root);
    while (Q.empty() == false) {
        Node* t = Q.front(); Q.pop();
        printf("%d ", t->data);
        for (Node* x : t->child) {
            Q.push(x);
        }
    }
    printf("\n");
}

int main() {
    Node* root = new Node(1);
    Node* node_2 = new Node(2);
    Node* node_3 = new Node(3);
    Node* node_4 = new Node(4);
    Node* node_5 = new Node(5);
    Node* node_6 = new Node(6);
    Node* node_7 = new Node(7);
    Node* node_8 = new Node(8);
    Node* node_9 = new Node(9);
    Node* node_10 = new Node(10);

    root->setChildNode({ node_2,node_3,node_4 });
    node_2->setChildNode({ node_5,node_6,node_7 });
    node_4->setChildNode({ node_8 });
    node_8->setChildNode({ node_9,node_10 });

    preOrder(root);
    printf("\n");

    postOrder(root);
    printf("\n");

    levelOrder(root);

    return 0;
}

递归调用原理

函数调用栈实例:主函数main()调用funcA()funcA()调用funcB()funcB()再自我调用(递归)

递归原理详解 - 图23
函数调用栈实例.png

函数调用栈的基本单位是帧(frame)。每次函数调用时,都会相应地创建一帧, 记录该函数实例在二进制程序中的返回地址(return address),以及局部变量、传入参数等, 并将该帧压入调用栈。若在该函数返回之前又发生新的调用,则同样地要将与新函数对应的一帧压入栈中,成为新的栈顶。函数一旦运行完毕,对应的帧随即弹出,运行控制权将被交还给该函 数的上层调用函数,并按照该帧中记录的返回地址确定在二进制程序中继续执行的位置。

在任一时刻,调用栈中的各帧,依次对应于那些尚未返回的调用实例,亦即当时的活跃函数实例(active function instance)。特别地,位于栈底的那帧必然对应于入口主函数main(), 若它从调用栈中弹出,则意味着整个程序的运行结束,此后控制权将交还给操作系统。

此外,调用栈中各帧还需存放其它内容。比如,因各帧规模不一,它们还需记录前一帧的起始地址,以保证其出栈之后前一帧能正确地恢复。

作为函数调用的特殊形式,递归也可借助上述调用栈得以实现。比如在上图中,对应于 funcB()的自我调用,也会新压入一帧。可见,同一函数可能同时拥有多个实例,并在调用栈中 各自占有一帧。这些帧的结构完全相同,但其中同名的参数或变量,都是独立的副本。比如在 funcB()的两个实例中,入口参数m和内部变量i各有一个副本。

DFS/回溯算法

如果某问题的解可以由多个步骤得到,而每个步骤都有若干种选择(这些候选方案集可能会依赖之前做出的选择),且可以用递归枚举法实现,则它的工作方式可以用解答树来描述。

全排列问题

输出数字1~N所能组成的所有全排列

递归原理详解 - 图25
1550062663360.png

#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 10;

bool isUsed[MAXN];
vector<int> num;
int N;

void DFS(int index) {
    if (index >= N) {
        for (int x : num) {
            printf("%d ", x);
        }
        printf("\n");
        return;
    }
    for (int i = 1; i <= N; i++) {
        if (isUsed[i]) continue;
        num.push_back(i);
        isUsed[i] = true;
        DFS(index + 1);
        num.pop_back();
        isUsed[i] = false;
    }
}

int main() {
    N = 3;
    DFS(0);        //从第0层开始搜索
    return 0;
}

素数环问题

将1到n这n个整数围成一个圆环,若其中任意2个相邻的数字相加,结果均为素数,那么这个环就成为素数环。

例如数字1-6所组成的一个素数环,用数组表示是[1, 4, 3, 2, 5, 6](第一位固定为1)

递归原理详解 - 图27
faf2b2119313b07ecb2b3bb80bd7912396dd8cef.jpg

递归原理详解 - 图29
1550062277175.png

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 100;

bool isPrimeNum[MAXN];
vector<int> ans;
bool isUsed[MAXN];
int N;

void getPrimeTable() {    //筛选法求质数
    fill(isPrimeNum, isPrimeNum + MAXN, true);    //先假设都是素数
    isPrimeNum[0] = isPrimeNum[1] = false;
    for (int i = 2; i < MAXN; i++) {    //从2开始,因为2是最小的质素
        if (isPrimeNum[i]) {
            //把i的倍数全部设置成非质数
            //比如i=2,则把4、6、9...设置成非质数
            //若i=3,则把6、9、12、15...设置成非质数
            for (int j = 2 * i; j < MAXN; j += i) {    //注意该for循环的写法,容易出错
                isPrimeNum[j] = false;
            }
        }
    }
}

void DFS(int index) {
    if (index >= N) {
        int temp = ans[0] + ans[index - 1];    //判断第一个数和最后一个数相加后是否是质数
        if (isPrimeNum[temp] == false) return;
        for (int x : ans) {
            printf("%d ", x);
        }
        printf("\n");
        return;
    }
    for (int i = 2; i <= N; i++) {
        if (isUsed[i]) continue;
        int temp = ans[index - 1] + i;
        if (isPrimeNum[temp] == false) {
            continue;    //剪枝
        }
        ans.push_back(i);
        isUsed[i] = true;
        DFS(index + 1);
        ans.pop_back();
        isUsed[i] = false;
    }
}

int main() {
    getPrimeTable();

    N = 4;
    ans.push_back(1);    //素数环第一个数固定是1
    DFS(1);    //从第二个数开始搜索

    return 0;
}

八皇后问题

八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当n = 1或n ≥ 4时问题有解。

递归原理详解 - 图31
1549956052178.png

#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 100;
int solution[MAXN];    //solution[i]=j 表示棋盘的第i行第j列放有皇后
int cnt;
int N;

char chessboard[MAXN][MAXN];
void printSolution() {
    fill(chessboard[0], chessboard[0] + MAXN * MAXN, '*');
    for (int i = 0; i < N; i++) {
        int j = solution[i];
        chessboard[i][j] = '#';
        chessboard[i][N] = '\0';
    }
    printf("solution #%d\n", cnt);
    for (int i = 0; i < N; i++) {
        printf("%s\n", chessboard[i]);
    }
    printf("\n");
}

bool judge(int row, int col) {
    for (int i = 0; i < row; i++) {
        int j = solution[i];
        if (j == col || row + col == i + j || row - col == i - j)
            return false;
    }
    return true;
}

void DFS(int row) {
    if (row >= N) {
        cnt++;
        printSolution();
        return;
    }
    for (int col = 0; col < N; col++) {
        if (judge(row, col) == false) continue;
        solution[row] = col;
        DFS(row + 1);
    }
}

int main() {
    N = 8;
    DFS(0);
    printf("N = %d, total solutions: %d\n", N, cnt);
    return 0;
}