组合算法

网上已经能够搜索到比较多的资料,大部分都是递归实现,因为递归实现是最优解,而且代码易于理解,

但是递归实现的风险——基数大的话可能出现栈溢出,所以这里使用循环实现;

假设现有 M=5 个数:5,9,12,50,45,从中取 N=3 个数做组合,将这 M 个数看做数组中的元素,提取成索引值,分别为:[0, 1, 2, 3, 4],首先穷举寻找规律,存在的组合情况如下:

[0, 1, 2]    // 末尾自增
[0, 1, 3]
[0, 1, 4]
[0, 2, 3]    // 类似于“逢10进1”,末尾自增
[0, 2, 4]
[0, 3, 4]    // 类似于“逢10进1”,末尾自增
[1, 2, 3]    // 类似于“逢10进1”,末尾自增
[1, 2, 4]
[1, 3, 4]
[2, 3, 4]    // 类似于“逢10进1”,末尾自增

规律基本明了,只是每个位置上的进位判断值不一样。代码实现如下:

import org.junit.jupiter.api.Test;

/**
 * 组合算法
 */
public class ZuHeTest {

    @Test
    public void zuhe() {
        int m = 6;
        int n = 3;
        // select用于存放组合的索引,初始化, 初始化后的就是1组
        int[] select = new int[n];
        for (int i = 0; i < n; i++) {
            select[i] = i;
        }
        printArr(select); // 输出一种情况

        // lastIndex 指针,指向哪个元素,该元素就做自增
        int lastIndex = select.length - 1;
        // 进位判断值
        int judgeVal = m;
        // 触发进位标记
        boolean trigger = false;
        while (lastIndex >= 0) {
            // 指针指向的元素索引自增
            int val = select[lastIndex] + 1;
            if (val < judgeVal) {
                select[lastIndex] = val;
                while (trigger && lastIndex < select.length - 1) {
                    // 上依次循环已经触发进位机制(上一行代码完成的进位),并将后面的数字初始化
                    // 例如:[0,1,4],进位后为 [0,2,4],需要将 4 初始化为 3,变为:[0,2,3]
                    lastIndex++;
                    val++;
                    select[lastIndex] = val;
                    judgeVal++;
                }
                trigger = false;
                printArr(select); // 输出
            } else {
                // 自增值到达边界,指针和判断值往前挪
                lastIndex--;
                judgeVal--;
                // 触发进位标记
                trigger = true;
            }
        }
    }

    private void printArr(int[] arr) {
        for (int i : arr) {
            System.out.print(i);
            System.out.print(',');
        }
        System.out.println();
    }
}

循环实现确实没有递归的好理解。

(排列算法后续添加)