《从入门到精通:蓝桥杯编程大赛知识点全攻略》(一)-递归实现指数型枚举、递归实现排列型枚举

本篇博客将聚焦于通过递归来实现两种经典的枚举方法:指数型枚举排列型枚举。这两种枚举方式在计算机科学和算法竞赛中都有广泛应用,无论是在解题中,还是在实际工作中都极具价值。

目录

前言

斐波那契数列递归

递归实现指数型枚举

算法思路

代码如下

递归实现排列型枚举

算法思路

代码如下

总结


前言

在编程的世界里,递归是一种优雅且强大的技术,它能让复杂问题变得更加简洁和易于理解。无论是数学中的公式推导,还是计算机科学中的算法设计,递归都扮演着不可或缺的角色。在数据结构与算法中,递归不仅能帮助我们高效地解决问题,还能展现出代码的简洁性和表达力。

本篇博客将聚焦于通过递归来实现两种经典的枚举方法:指数型枚举排列型枚举。这两种枚举方式在计算机科学和算法竞赛中都有广泛应用,无论是在解题中,还是在实际工作中都极具价值。


斐波那契数列递归

递归最经典的就是斐波那契数列,其中第1个数是1,第2个数是2,第3个数是前两个数字之和。

java代码如下:

java 复制代码
package AcWingLanQiao;

import java.util.*;

public class 递归 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        System.out.println(f(n));
    }
    public static int f(int n){
        if(n==1){ return 1;}
        if(n==2){ return 2;}
        return f(n-1)+f(n-2);
    }
}

递归实现指数型枚举

从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式

输入一个整数 n。

输出格式

每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。

对于没有选任何数的方案,输出空行。

数据范围

1≤n≤15

输入样例

3

输出样例

3
2
2 3
1
1 3
1 2
1 2 3

算法思路

每一个位置都有两种情况,分别是选和不选。

当有n个数时,结果就有中情况。

当n为对的时候,上述对应的是递归搜索树。从第一个位置开始分两种情况,选和不选,后续每个位置一次类推。

我们用一个数组flag,数组下标表示从1到n,flag[i]表示该值在每个位置的状态,0表示还未考虑,1表示选,2表示这个位置不选;通过dfs深度优先搜索,用u来表示当前在哪个位置,先思考递归的出口,当当前位置u要大于n时(即u > n),就说明n个位置每个位置的情况都处理好了,就说明最后flag[i] = 1对应的下标就是结果所需的序列。

当u <= n时,我们只需处理两种情况,一种是选,一种是不选。当选时,将flag[u] = 1,然后递归的处理下一个位置dfs(u+1),最后再恢复现场flag[u] = 0,即相当于是当前的位置都处理完了,将位置恢复为未处理,然后再走另一种情况;当不选时,将flag[u] = 2,后递归的处理下一个位置dfs(u+1),最后再恢复现场flag[u] = 0。

代码如下

java 复制代码
package AcWingLanQiao;
import java.io.*;
import java.util.*;

public class 递归实现指数型枚举 {
    static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StreamTokenizer st = new StreamTokenizer(br);
    static int N = 16;
    /*
    状态数据,记录每个位置的当前状态
    0表示还未考虑
    1表示选它
    2表示不选它
     */
    static int[] flag = new int[N];
    static int n;
    public static void main(String[] args)throws Exception {
        n = nextInt();
        dfs(1);
        pw.flush();
    }

    public static void dfs(int u){
        if(u > n){
            for(int i = 1;i <= n;i++){
                if(flag[i] == 1){
                    pw.print(i+" ");
                }
            }
            pw.println();
            pw.flush();
            return;
        }
        flag[u] = 2;
        dfs(u+1);  //第一个分支不选
        flag[u] = 0;//恢复现场

        flag[u] = 1;
        dfs(u+1); //第二个分支选
        flag[u] = 0;
    }

    public static int nextInt() throws Exception {
        st.nextToken();
        return (int)st.nval;
    }
}

递归实现排列型枚举

把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式

一个整数 n。

输出格式

按照从小到大的顺序输出所有方案,每行 1 个。

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

数据范围

1≤n≤9

输入样例

java 复制代码
3

输出样例

java 复制代码
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

注:字典序排序是一种按照字典中单词出现的顺序来排列元素的方法。在计算机科学中,它被用来比较两个字符串的大小关系,即比较它们从左到右第一个不同字符的ASCII值的大小关系。这种排序方式不仅适用于英文单词,也适用于任意字符串的比较。

算法思路

用整型数组arr来存储数列的结果,布尔类型数组flag,其中flag[i]若为true表示数字i已被使用,若为false表示数字i未被使用。使用深度优先搜索dfs来解决此问题。我们可以通过每个位置放置哪个数字来进行思考。

我们先来思考递归的出口,当当前位置u大于n时(即u > n),说明所有的位置上数字都以排好,所以此时只需打印arr数组,就可以得到结果。当n为3时,说明一个位置有3种情况,所以要有一个外层循环,然后来判断flag[i]是否被使用,未被使用则将当前位置复制为i即arr[u] = i,还需将该数字对应的flage数组设置为已使用即flag[i] = true,然后递归的处理下一个位置即dfs(u+1),最后再进行回溯操作flag[i] = false。如果flag[i]已经被使用,则接着进行下一次数字进行判断,n个数字都被使用,也可说明数列已被排完序。

算法时间复杂度为O(n*n!)

代码如下

java 复制代码
import java.io.*;

public class Main {
    static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StreamTokenizer st = new StreamTokenizer(br);
    static int N = 10;
    static int[] arr = new int[N];
    static boolean[] flag = new boolean[N]; //true表示用过 false表示未被用过
    static int n;
    public static void main(String[] args) throws IOException {
        n = nextInt();
        dfs(1);

        pw.flush();
    }
    public static void dfs(int u) {
        if(u > n){ //边界
            for(int i = 1; i<= n;i++){
                pw.print(arr[i]+" ");
            }
            pw.println();
            return;
        }

        //枚举每个分支
        for(int i = 1; i<= n;i++){
            if(!flag[i]){
                arr[u] = i;
                flag[i] = true;
                dfs(u+1);
                //恢复现场
                flag[i] = false;
            }
        }
    }
    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }
}

代码输入

java 复制代码
4

代码输出结果

java 复制代码
1 2 3 4 
1 2 4 3 
1 3 2 4 
1 3 4 2 
1 4 2 3 
1 4 3 2 
2 1 3 4 
2 1 4 3 
2 3 1 4 
2 3 4 1 
2 4 1 3 
2 4 3 1 
3 1 2 4 
3 1 4 2 
3 2 1 4 
3 2 4 1 
3 4 1 2 
3 4 2 1 
4 1 2 3 
4 1 3 2 
4 2 1 3 
4 2 3 1 
4 3 1 2 
4 3 2 1 

总结

通过本篇博客的学习,我们可以看到递归作为一种经典的编程技巧,不仅能够简化问题的解决过程,还能提高代码的可读性和执行效率。递归在指数型枚举和排列型枚举中的应用,展示了它在不同场景中的灵活性与强大功能。

无论是初学者还是有一定编程经验的开发者,理解并掌握递归的精髓对于解决实际问题都有很大的帮助。希望通过这篇博客,能够带给大家一些启发,并鼓励大家在实际编程过程中善于运用递归,解锁更多的编程奥秘。

相关推荐
23级二本计科1 天前
综合练习dfs_1
算法·leetcode·深度优先
阿松のblog2 天前
蓝桥杯JAVA--003
java·职场和发展·蓝桥杯
逝灮2 天前
【蓝桥杯——物联网设计与开发】Part2:OLED
驱动开发·stm32·单片机·嵌入式硬件·物联网·蓝桥杯·oled
Dream it possible!2 天前
LeetCode 热题 100_将有序数组转换为二叉搜索树(42_108_简单_C++)(二叉树;递归)
c++·算法·leetcode·深度优先
XiaoLeisj2 天前
【优选算法 & 分治】深入理解分治算法:分治算法入门小专题详解
算法·leetcode·决策树·深度优先·哈希算法·剪枝·推荐算法
阿松のblog3 天前
蓝桥杯JAVA刷题--001
android·java·蓝桥杯
岸榕.4 天前
19712 数字接龙
算法·深度优先·图论
刘婉晴4 天前
【蓝桥杯研究生组】第15届Java试题答案整理
java·蓝桥杯
小冯的编程学习之路5 天前
【蓝桥杯】:蓝桥杯之路径之谜
职场和发展·蓝桥杯