package com.example.esdemo;import java.util.*;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.stream.Collectors;/** * 穷举排列组合原理 * 示例:123456 * 算法1的示例: * 当我们需要找到长度为4的排列组合时,可以理解为有4个格子,每个格子都有6种可能,都可能是123456 * 代码里面可以忽略值,直接使用index来代表每个位置,则每个格子都可能是index为 0 - 5 * 所有的可能组合数量为 6 * 6 * 6 * 6,即6的4次冥 * <p> * 现在就是将每个格子,每次拨动一个,确保每个格子都有和其他格子每个数字配合的机会,简单的话就是用嵌套for循环,比如上面就用4层for循环 * 但代码怎么动态生成for循环呢,比较难,下面使用单层for循环,使用indexArr来记录每层的i * 嵌套for循环是最内层每次都在递增,越往外层,则相邻内层每满6次,外层递增一次 */public class QJ { public static Set<String> resultSet = Collections.synchronizedSet(new HashSet<>()); public static void main(String[] args) { //字符串长度小于等于9时,有明显的性能优势,之后时间会急速增加到不可接受 String sourceString = "0123456789"; //结果集合,这里其实用set更好,可以防止出现str里面有重复元素导致重复排列 if (sourceString.length() <= 9) { System.out.println("使用算法1"); findStr1(sourceString); } else { System.out.println("使用算法2"); findStr2(sourceString); } } /** * 算法1,使用单层for循环来查找可能的值,在源数据长度小于等于9时,具备出色的性能表现 * * @param sourceString */ private static void findStr1(String sourceString) { int sourceStrLength = sourceString.length(); long startTime = System.currentTimeMillis(); int availableProcessors = Runtime.getRuntime().availableProcessors(); System.out.println(String.format("CPU核心数:%s", availableProcessors)); ExecutorService executorService = Executors.newFixedThreadPool(availableProcessors); boolean useParallel = true; //这层循环,控制当前的数字长度,所以从1开始,最大为字符长度 for (int resultStrLength = 1; resultStrLength <= sourceStrLength; resultStrLength++) { int finalResultStrLength = resultStrLength; //这里需要根据i的大小,嵌套for循环,每层for循环都是length次数 long forCount = (long) Math.pow(sourceStrLength, resultStrLength); //当总循环次数达到 long groupCount = forCount / availableProcessors; if (useParallel && groupCount > 100000) { System.out.println(String.format("长度[%s]存在[%s]种组合,使用多线程处理", resultStrLength, forCount)); long startIndex = 0; for (long i = 0; i < availableProcessors; i++) { long finalStartIndex = startIndex; if (i != availableProcessors - 1) { executorService.submit(() -> { findStr(finalStartIndex, groupCount, sourceString, sourceStrLength, resultSet, finalResultStrLength); }); } else { executorService.submit(() -> { findStr(finalStartIndex, forCount - ((availableProcessors - 1) * groupCount), sourceString, sourceStrLength, resultSet, finalResultStrLength); }); } startIndex += groupCount; if (startIndex >= forCount) { break; } } } else { findStr(0, forCount, sourceString, sourceStrLength, resultSet, resultStrLength); } } //确认所有可能性均以找到 executorService.shutdown(); while (!executorService.isTerminated()) { try { Thread.sleep(1L); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); } } System.out.println(String.format("共找到%s种组合,耗时%sms", resultSet.size(), System.currentTimeMillis() - startTime)); } /** * findStr1的核心方法 * * @param startIndex * @param forCount * @param sourceString * @param sourceStrLength * @param resultSet * @param resultStrLength */ private static void findStr(long startIndex, long forCount, String sourceString, int sourceStrLength, Set<String> resultSet, int resultStrLength) { //创建一个数组,记录每个for循环当前的位置,所以集合长度为当前for循环的层数 int[] currentIndexArr = null; for (long l = startIndex; l < startIndex + forCount; l++) { //每次仅拨动一个位置 //l为当前拨动的总次数,对应调整index集合的所有index值 if (l == startIndex) { currentIndexArr = setIndexArr(resultStrLength, l + 1, sourceStrLength); } else { currentIndexArr = setIndexArr(currentIndexArr, sourceStrLength); } //查询是否存在重复的index if (existsRepeatIndex(currentIndexArr)) { continue; } //根据当前个位置的index,获取字符串 getResultStr(sourceString, resultSet, currentIndexArr); } } /** * 根据当前循环次数,变更每个index的值,原理为:嵌套for循环是最内层每次都在递增,越往外层,则相邻内层每满6次,外层递增一次 * 默认原始indexArr全部都是0 * * @param strLength 当前字符串的长度 * @param count 循环次数 * @param length 进制数 * @return */ private static int[] setIndexArr(int strLength, long count, int length) { int[] newIndexArr = new int[strLength]; long highBitIncrement = count; for (int i = strLength - 1; i >= 0; i--) { //取余,计算当前索引位的步进 newIndexArr[i] = (int) (highBitIncrement % length); if (i != 0) { highBitIncrement = highBitIncrement / length; } } return newIndexArr; } /** * 基于上一次的index集合,从尾部进行递增 * * @param beforeIndexArr 上一次的index集合 * @param length 进制数 * @return */ private static int[] setIndexArr(int[] beforeIndexArr, int length) { for (int i = beforeIndexArr.length - 1; i >= 0; i--) { int beforeIndex = beforeIndexArr[i]; int newIndex = (beforeIndex + 1) % length; beforeIndexArr[i] = newIndex; if (newIndex != 0) { return beforeIndexArr; } } return beforeIndexArr; } /** * 根据index集合,找到当前字符串 * * @param str * @param set * @param indexArr */ private static void getResultStr(String str, Set<String> set, int[] indexArr) { char[] chars = new char[indexArr.length]; for (int i = 0; i < indexArr.length; i++) { chars[i] = str.charAt(indexArr[i]); } String strTemp = new String(chars); if (set.add(strTemp)) {// System.out.println(strTemp); } } /** * 是否存在重复index * * @param indexArr * @return */ private static boolean existsRepeatIndex(int[] indexArr) { for (int i = 0; i < indexArr.length; i++) { for (int j = 0; j < indexArr.length; j++) { if (i == j) { continue; } if (indexArr[i] == indexArr[j]) { return true; } } } return false; } /** * 算法2,使用递归组合获取元素,在源数据长度大于9以后,相比算法1,具备明显的性能优势,但时间消耗依旧极为可观 * * @param sourceString */ public static void findStr2(String sourceString) { long startTime = System.currentTimeMillis(); List<Character> data = new ArrayList<>(); for (char c : sourceString.toCharArray()) { data.add(c); } for (int i = 1; i <= sourceString.length(); i++) { findStr(data, new ArrayList<Character>(), i); } System.out.println(String.format("共找到%s种组合,耗时%sms", resultSet.size(), System.currentTimeMillis() - startTime)); } /** * findStr2的核心方法 * * @param data * @param target * @param length */ private static void findStr(List<Character> data, List<Character> target, int length) { List<Character> copyData; List<Character> copyTarget; if (target.size() == length) { String strTemp = target.stream().map(String::valueOf).collect(Collectors.joining()); boolean add = resultSet.add(strTemp); } for (int i = 0; i < data.size(); i++) { copyData = new ArrayList<>(data); copyTarget = new ArrayList<>(target); copyTarget.add(copyData.get(i)); copyData.remove(i); findStr(copyData, copyTarget, length); } }}