递归算法实例应用(一)

简介: 递归算法实例应用(一):简单Hanoi 问题、N Queens问题。

递归算法实例应用(一)

递归简笔

递归和普通函数调用一样,都是通过函数栈实现。

以斐波那契数列递归调用为例

斐波那契数列函数递归次序.png

递归时函数调用栈的进栈、出栈过程可以由上述图示直观的体现出来,

因此可以得出递归的几个作用:

​ 1)替代多重循环

​ 2)解决以递归形式或潜在以递归形式定义的问题

​ 3)将问题规模分解,分解为规模更小的子问题进行求解

​ ... ... ...


Hanoi (Simple)

Description

古代有一座汉诺塔,塔内有3个座A、B、C,A座上有n个盘子,盘子大小不等,大的在下,小的在上。有一个和尚想把这n个盘子从A座移到C座,但每次只能移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。在移动过程中可以利用B座来放盘子。要求输出移动的步骤。

Input

汉诺塔内的盘子个数n(1 ≤ n ≤ 64)。

Output

输出移动的步骤,每行一步,如从A座移动到C座,输出“A->C”。

Sample Input

3

Sample Output

A->C
A->B
C->B
A->C
B->A
B->C
A->C

这是最简单的Hanoi问题,其余进阶版的Hanoi问题均由此发展而来。

该题是一个非常明显的以递归形式定义的问题,即:

要把A座上的盘子移动到C座的问题分解为三步:

  1. 把A座上的n-1个盘子移动到B座作为中转,且以C座作为中转;
  2. 把A座剩余的一个盘子移动到C座;
  3. 再把B座的n-1个盘子移动到C座,且以A做作为中转。

这样就把原问题的规模减小至n-1,通过逐层递归可以将问题规模减少至1,此时就可以通过直接将A座盘子移动到C座完成,在逐步向上返回至上一层调用函数,直至问题规模n完成求解。

由上述分析显然可知,当问题规模减少至1时为终止条件,此时应将A座盘子移动到C座上,即输出A->C,同时结束本次递归,返回至上一层递归函数处。

所以主函数及递归段代码为:

void Hanoi(int n, char A, char B, char C) {
    if (n == 1) {//问题规模到达1时为递归终止条件
        printf("%c->%c\n", A, C);
        return;
    }
    //将A座n-1个盘子移动至B座,同时以C座作为中转
    Hanoi(n - 1, A, C, B);
    //将A座第n个盘子移动至C座
    printf("%c->%c\n", A, C);
    //将B座的n-1个盘子移动至C座,同时以A座作为中转
    Hanoi(n - 1, B, A, C);
}

int main() {
    int n;
    scanf("%d",&n);
    //将n层汉诺塔中A座移动到C座,同时以B座作为中转
    Hanoi(n, 'A', 'B', 'C');
}

N Queens

Description

The eight queens puzzle is the problem of putting eight chess queens on an 8 × 8 chessboard such that none of them is able to capture any other. The puzzle has been generalized to arbitrary n × n boards. Given n, you are to find a solution to the n queens puzzle.

Input

The input contains multiple test cases. Each test case consists of a single integer n between 8 and 300 (inclusive). A zero indicates the end of input.

Output

For each test case, output your solution on one line. The solution is a permutation of {1,2,...,n }. The number in the ith place means the ith-column queen in placed in the row with that number.

Sample Input

4

Sample Output

2 4 1 3
3 1 4 2

为方便理解题意,依样例输入输出可以画出四皇后图例:

img

题中要求皇后不能在同一行、同一列、同一对角线。

不难想到可以通过枚举所有可能的皇后状态来暴力求解,比如当 N = 4 时,可以通过4重循环嵌套来实现枚举。

但是该问题的问题规模N是一个不确定是整数,N重循环嵌套就无法实现,所以可以考虑采用递归来替代多重循环。

分析至此,本题的递归又自然而然的引出了回溯法这一基本算法理念,当不断的枚举每一种可能的状态时,若发生了与规则矛盾的错误,则应立即终止本层递归,回溯至上层,进行下一状态的枚举。

准备工作:

而如何枚举全每一种可能的状态,有多种方法,如按行枚举、按列枚举、按对角线枚举等,本文采用按行枚举进行操作。所以递归函数的形参就呼之欲出:int n,因为我们假定递归模型是按行进行枚举的,所以n用于表示0~n-1行皇后已经摆好,当前递归函数为处理第n行皇后的位置。

所以应再设置一个PosOfQueen的一维数组,其下标表示棋盘的第i行,其值PosOfQueen[i]表示第i行皇后摆放在第几列,如PosOfQueen[2]=4,表明第2行(棋盘的第3行)皇后摆放第4列(棋盘的第五列)。


算法逻辑:

其中,NofQueen表示N皇后中的规模N

  1. 若本层递归参数n==NofQueen,即表明N皇后已经摆放正确,所以此时为该问题的递归终止条件,应输出问题的一个解。

    代码描述为:

    if (n == NofQueen) {//N个皇后已经摆好
        for (i = 0; i < NofQueen; ++i) {
            printf("%d ", PosOfQueen[i] + 1);//输出每一行皇后摆在哪一列
        }
        printf("\n");
        return;
    }
  2. 若本层递归不为第N行,则应处理第n行的皇后,即确定第n行皇后的摆放位置。所以应从0~NofQueen-1枚举所有可能的摆放位置。假设以i枚举时行递增变量

    枚举时,首先,与前0~n-1行已经确定位置的n个元素进行比较,判断是否同行、同列、同对角线

    • 同行判定:由于是按行遍历每行确定一个元素的位置,所以不可能有元素是同行。
    • 同列判定:PosOfQueen[j]数组的值表明第j行皇后摆放在第几列,所以同列判断较为简单,代码描述为:

      PosOfQueen[j] == i
    • 同对角线判定:

      j=1 j=2 j=3 j=4
      i=1 (1,1) (1,2) (1,3) (1,4)
      i=2 (2,1) (2,2) (2,3) (2,4)
      i=3 (3,1) (3,2) (3,3) (3,4)
      i=4 (4,1) (4,2) (4,3) (4,4)

      上表描述为4*4的棋盘,其中

      1. 主对角线

        • 主对角线1:(1,1)、(2,2)、(3,3)、(4,4)
        • 主对角线2:(1,2)、(2,3)、(3,4)
        • 主对角线3:(2,1)、(3,2)、(4,3)
        • 主对角线4:(1,3)、(2,4)
        • 主对角线x: ...、...

          观察发现,每一个主对角线上各个元素的行列下标差的绝对值均相等,即|i-j|均相等。

          如:主对角线2中,|1-2|=|2-3|=|3-4|=1

          ​ 主对角线3中,|2-1|=|3-2|=|4-3|=1

      2. 副对角线

        • 副对角线1:(1,4)、(2,3)、(3,2)、(4,1)
        • 副对角线2:(1,3)、(2,2)、(3,1)
        • 副对角线3:(2,4)、(3,3)、(4,2)
        • 副对角线4:(1,2)、(2,1)
        • 副对角线x: ...、...

          观察发现,每一个副对角线上各个元素的行列下标之和均相等。即i+j均相等。

          如:副对角线2中,1+3=2+2=3+1=4。

          ​ 副对角线3中,2+4=3+3=4+2=6。

    综上所述,可将同行(忽略)、同列、同主副对角线判定代码合并为:

    if (PosOfQueen[j] == i || abs(PosOfQueen[j] - i) == abs(n - j)) {
        //同行不必判断,判断是否同列、同主对角线、同副对角线。
        break;//规则冲突,测试下一可能位置,再通过剪枝来减少不必要的开销
    }

    其次,若与前0~n-1行判断是否同行、同列、同对角线完成后,与题目规则不冲突,则将第n行皇后摆放在当前枚举状态下的的可能位置i处,随后进行下一行皇后位置的确定,即进入下一层递归中。

    代码描述如下:

    if (j == n) {//当前轮次枚举中位置i与前0~n-1行合法性判断均通过,当前位置合法
        PosOfQueen[n] = i;//将第n个皇后摆放在位置i
        NQueen(n + 1);//递归调用函数,处理n+1行皇后位置摆放问题
    }
  3. 至此,递归函数已经完成,在递归函数中,第一步中的if语句为递归终止点,同时也是回溯法成功找到解时的判定条件。在第二步中,通过逐步尝试每一个可能位置的,并通过回溯保证本次递归是在上一层递归函数正确的条件下进行的枚举尝试,不断穷尽解集二叉树中的所有分支。

代码整合:

综上,代码可整合为:

void NQueen(int n) {//在0~n-1行的皇后已经摆好的情况下,摆第n行及其之后的皇后
    int i, j;
    if (n == NofQueen) {//N个皇后已经摆好
        for (i = 0; i < NofQueen; ++i) {
            printf("%d ", PosOfQueen[i] + 1);//输出每一行皇后摆在哪一列
        }
        printf("\n");
        return;
    }
    for (i = 0; i < NofQueen; ++i) {
        for (j = 0; j < n; ++j) {//每一轮for循环均回溯至i行处,因此可以穷尽所有可能解的二叉树的叶子结点
            if (PosOfQueen[j] == i || abs(PosOfQueen[j] - i) == abs(n - j)) {
                //同行不必判断,判断是否同列、同主对角线、同副对角线。
                break;//规则冲突,test next pos  再通过剪枝来减少不必要的开销
            }
        }
        if (j == n) {//与前0~n-1行合法性判断均通过,当前位置合法
            PosOfQueen[n] = i;//将第n个皇后摆放在位置i
            NQueen(n + 1);//递归调用函数,处理n+1行皇后位置摆放问题
        }
    }
}

int NofQueen;           //N皇后的N
int PosOfQueen[100];    //棋盘数组,下标表示行号,值表示列号

int main() {
    scanf("%d", &NofQueen);
    NQueen(0);//从棋盘第0层开始逐层递归解决
}
                          by Ss1Two 2023/01/12
目录
相关文章
|
2月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
54 3
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的优化算法及其应用
【10月更文挑战第8天】 本文将探讨深度学习中常用的优化算法,包括梯度下降法、Adam和RMSProp等,介绍这些算法的基本原理与应用场景。通过实例分析,帮助读者更好地理解和应用这些优化算法,提高深度学习模型的训练效率与性能。
193 63
|
21天前
|
机器学习/深度学习 人工智能 算法
探索人工智能中的强化学习:原理、算法与应用
探索人工智能中的强化学习:原理、算法与应用
|
20天前
|
机器学习/深度学习 算法 数据挖掘
C语言在机器学习中的应用及其重要性。C语言以其高效性、灵活性和可移植性,适合开发高性能的机器学习算法,尤其在底层算法实现、嵌入式系统和高性能计算中表现突出
本文探讨了C语言在机器学习中的应用及其重要性。C语言以其高效性、灵活性和可移植性,适合开发高性能的机器学习算法,尤其在底层算法实现、嵌入式系统和高性能计算中表现突出。文章还介绍了C语言在知名机器学习库中的作用,以及与Python等语言结合使用的案例,展望了其未来发展的挑战与机遇。
39 1
|
20天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
49 1
|
1月前
|
缓存 算法 网络协议
OSPF的路由计算算法:原理与应用
OSPF的路由计算算法:原理与应用
42 4
|
27天前
|
机器学习/深度学习 监控 算法
基于反光衣和检测算法的应用探索
本文探讨了利用机器学习和计算机视觉技术进行反光衣检测的方法,涵盖图像预处理、目标检测与分类、特征提取等关键技术。通过YOLOv5等模型的训练与优化,展示了实现高效反光衣识别的完整流程,旨在提升智能检测系统的性能,应用于交通安全、工地监控等领域。
|
1月前
|
存储 算法 网络协议
OSPF的SPF算法介绍:原理、实现与应用
OSPF的SPF算法介绍:原理、实现与应用
76 3
|
1月前
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
40 0
|
21天前
|
机器学习/深度学习 人工智能 算法
探索人工智能中的强化学习:原理、算法及应用
探索人工智能中的强化学习:原理、算法及应用
下一篇
DataWorks