一篇文章讲明白———递归实现枚举算法

1.指数型枚举

实际问题:1-n个数字中随机选取多个,每种方案按照里的数字按照从小到大的顺序排列,按照字典序输出。

输入 :3

输出:1,12,123,2,23,3

解决问题三步骤:

1.如何枚举每个位置的数字?

2.每个位置的数字都要大于前面位置的数字?

3.如何输出这些结果?

解决:

1.想象成 n 个格子需要填,每个格子都从小到大的枚举数字。

2.传入一个变量,可以标记当前位置最小可以选取的数字

3.用函数输出结果

代码如下:

import java.util.Scanner;

public class Main {
    // 用于存储每个位置的数字
    private static int[] arr;

    public Main(int maxNum) {
        arr = new int[maxNum];
    }
    /**
     *递归函数实现指数型枚举
     * @param currentIndex 枚举第i位的数字,从0开始
     * @param currentMinNum 当前位置最小选择的数字,从1开始
     * @param maxNum 最大可以选取的数字,用户输入
     */
    public void function(int currentIndex, int currentMinNum, int maxNum) {
        // 如果当前可以选取的最小数字大于n,则返回
        if (currentMinNum > maxNum) return;

        for (int i = currentMinNum; i <= maxNum; i++) {
            // 将当前位置可以填的数字赋值给当前位置
            arr[currentIndex] = i;
            // 打印结果
            printResult(currentIndex);
            // 递归调用
            function(currentIndex + 1, i + 1, maxNum);
        }
    }

    /**
     * 打印结果
     * @param rightLimit 打印数组的右边界
     */
    private void printResult(int rightLimit) {
        for(int i = 0; i <= rightLimit; i++) {
                System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
    public static void main(String[] args) {
        // 读入一个可以读取的最大数字
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入边界数字:");
        int maxNum = sc.nextInt();
        Main main = new Main(maxNum);
        main.function(0, 1, maxNum);
    }
}

2.组合型枚举

实际问题:1-n个数字中随机选取 m 个,每种方案按照里的数字按照从小到大的顺序排列,按照字典序输出。

例如:

输入:

5 3

输出:

123

124

125

134......

345

和指数型枚举很相似,只不过指数型是全枚举,而组合型枚举是有限制的枚举,我们枚举到第 m 位才进行输出。

代码如下:

import java.util.Scanner;

public class Main {
    // 用于存储每个位置的数字
    private static int[] arr;

    public Main(int rightLimit) {
        arr = new int[rightLimit];
    }
    /**
     *递归函数实现组合型枚举
     * @param currentIndex 枚举第i位的数字,从0开始
     * @param currentMinNum 当前位置最小选择的数字,从1开始
     * @param maxNum 最大可以选取的数字,用户输入
     * @param indexLimit numLimit 当前已经选取的数字数量
     */
    public void function(int currentIndex, int currentMinNum, int maxNum, int indexLimit) {
        // 如果已经选取的数字数量等于numLimit,则返回
        if (currentIndex == indexLimit) {
            printResult(indexLimit);
            return;
        }

        // 当枚举到第i位置时,剩余没有枚举的数字数量要大于等于要枚举的位置数量,进行剪枝策略,去掉不必要的枚举
        for (int i = currentMinNum; i <= maxNum && indexLimit - currentIndex - 1 <= maxNum - i; i++) {
            // 将当前位置可以填的数字赋值给当前位置
            arr[currentIndex] = i;
            // 递归调用
            function(currentIndex + 1, i + 1, maxNum, indexLimit);
        }
    }

    /**
     * 打印结果
     * @param rightLimit 需要打印的右边界
     */
    private void printResult(int rightLimit) {
        for(int i = 0; i < rightLimit; i++) {
                System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
    public static void main(String[] args) {
        // 读入一个可以读取的最大数字
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入边界数字:");
        int maxNum = sc.nextInt();
        System.out.println("请输入选取枚举数字数量:");
        int indexLimit = sc.nextInt();
        Main main = new Main(indexLimit);
        main.function(0, 1, maxNum, indexLimit);
    }
}

3.排列型枚举

实际问题:1-n个这 n 个整数拍成一排并打乱次序,按照字典序输出所有可以选择的方案。

例如:

输入:

3

输出:

123

213

231

312

321

关键:

(1)全排列

(2)排列型枚举中 枚举位置的数字 必须是 前面没有出现过的 数字。

(3)后面的数字不必比前面的数字小

组合数量计算,第一个位置有 n 种可能,第二为位置有n-1种可能(因为第一个位置一定要使用一个数字)...第n个位置只有1种可能(只剩一个数字可以填)

因为后面的数字 不必 比前面的数字大,所以每个位置的数字都是全排列的,但是这样数字会重复;要求不能重复,所以要在全排列中 剔除 重复的组合,所以要额外建立一个数组,数组的下标是对应的数字,下标对应的值是 0 或 1,对应该下标的数字 是否 被使用过。指数型枚举 和 组合型枚举 因为 后面的数字 要比 前面的数字 大,所以数字一定不会重复,因此不需要构建这种数组。

代码如下:

import java.util.Scanner;

public class Main {
    // 用于存储每个位置的数字
    private int[] arr1;
    // 用于标记数字是否使用过,当arr2[i]为1时,表示i这个数字被使用过了
    private int[] arr2;

    public Main(int rightLimit) {
        arr1 = new int[rightLimit];
        arr2 = new int[rightLimit +1];
    }
    /**
     *递归函数实现排列型枚举
     * @param currentIndex 枚举第i位的数字,从0开始
     * @param maxNum 最大可以选取的数字,用户输入
     */
    public void function(int currentIndex, int maxNum) {
        // 如果已经选取的数字数量等于numLimit,则返回
        if (currentIndex == maxNum) {
            printResult(maxNum);
            return;
        }

        for (int i = 1; i <= maxNum; i++) {
            // 如果数字i已经使用过,则跳过
            if (arr2[i] == 1) {
                continue;
            }
            // 将当前位置可以填的数字赋值给当前位置
            arr1[currentIndex] = i;
            // 标记i这个数字已经使用过
            arr2[i] = 1;
            // 递归调用
            function(currentIndex + 1, maxNum);
            // 当前位置数字为i的所有组合已经枚举完毕,回收标记
            arr2[i] = 0;
        }
    }

    /**
     * 打印结果
     * @param rightLimit 需要打印的右边界
     */
    private void printResult(int rightLimit) {
        for(int i = 0; i < rightLimit; i++) {
                System.out.print(arr1[i] + " ");
        }
        System.out.println();
    }
    public static void main(String[] args) {
        // 读入一个可以读取的最大数字
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入边界数字:");
        int maxNum = sc.nextInt();
        Main main = new Main(maxNum);
        main.function(0, maxNum);
    }
}

总结:

相同思维:

枚举函数的构造方式:

第i个位置枚举的全可能性数字 + 后面的枚举数字的全可能性

不同点:

指数型枚举: 全排列,从第一个位置开始,每次多枚举一个位置的数字,每次枚举都输出,每次后面枚举的数字 比 前面的数字 大(所以指数型枚举的数字一定不会重复)

组合型枚举:不是全排列,枚举到指定 大小的 位置时才输出

排列型枚举:是 全排列,但是 枚举的数字 前后没有大小关系,如果不限制数字会重复,所以需要一个记录数字是否使用过的数组,来实现数字不能重复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YTC_040518

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值