1. package com.example.esdemo;
    2. import java.util.*;
    3. import java.util.concurrent.ExecutorService;
    4. import java.util.concurrent.Executors;
    5. import java.util.stream.Collectors;
    6. /**
    7. * 穷举排列组合原理
    8. * 示例:123456
    9. * 算法1的示例:
    10. * 当我们需要找到长度为4的排列组合时,可以理解为有4个格子,每个格子都有6种可能,都可能是123456
    11. * 代码里面可以忽略值,直接使用index来代表每个位置,则每个格子都可能是index为 0 - 5
    12. * 所有的可能组合数量为 6 * 6 * 6 * 6,即6的4次冥
    13. * <p>
    14. * 现在就是将每个格子,每次拨动一个,确保每个格子都有和其他格子每个数字配合的机会,简单的话就是用嵌套for循环,比如上面就用4层for循环
    15. * 但代码怎么动态生成for循环呢,比较难,下面使用单层for循环,使用indexArr来记录每层的i
    16. * 嵌套for循环是最内层每次都在递增,越往外层,则相邻内层每满6次,外层递增一次
    17. */
    18. public class QJ {
    19. public static Set<String> resultSet = Collections.synchronizedSet(new HashSet<>());
    20. public static void main(String[] args) {
    21. //字符串长度小于等于9时,有明显的性能优势,之后时间会急速增加到不可接受
    22. String sourceString = "0123456789";
    23. //结果集合,这里其实用set更好,可以防止出现str里面有重复元素导致重复排列
    24. if (sourceString.length() <= 9) {
    25. System.out.println("使用算法1");
    26. findStr1(sourceString);
    27. } else {
    28. System.out.println("使用算法2");
    29. findStr2(sourceString);
    30. }
    31. }
    32. /**
    33. * 算法1,使用单层for循环来查找可能的值,在源数据长度小于等于9时,具备出色的性能表现
    34. *
    35. * @param sourceString
    36. */
    37. private static void findStr1(String sourceString) {
    38. int sourceStrLength = sourceString.length();
    39. long startTime = System.currentTimeMillis();
    40. int availableProcessors = Runtime.getRuntime().availableProcessors();
    41. System.out.println(String.format("CPU核心数:%s", availableProcessors));
    42. ExecutorService executorService = Executors.newFixedThreadPool(availableProcessors);
    43. boolean useParallel = true;
    44. //这层循环,控制当前的数字长度,所以从1开始,最大为字符长度
    45. for (int resultStrLength = 1; resultStrLength <= sourceStrLength; resultStrLength++) {
    46. int finalResultStrLength = resultStrLength;
    47. //这里需要根据i的大小,嵌套for循环,每层for循环都是length次数
    48. long forCount = (long) Math.pow(sourceStrLength, resultStrLength);
    49. //当总循环次数达到
    50. long groupCount = forCount / availableProcessors;
    51. if (useParallel && groupCount > 100000) {
    52. System.out.println(String.format("长度[%s]存在[%s]种组合,使用多线程处理", resultStrLength, forCount));
    53. long startIndex = 0;
    54. for (long i = 0; i < availableProcessors; i++) {
    55. long finalStartIndex = startIndex;
    56. if (i != availableProcessors - 1) {
    57. executorService.submit(() -> {
    58. findStr(finalStartIndex, groupCount, sourceString, sourceStrLength, resultSet, finalResultStrLength);
    59. });
    60. } else {
    61. executorService.submit(() -> {
    62. findStr(finalStartIndex, forCount - ((availableProcessors - 1) * groupCount), sourceString, sourceStrLength, resultSet, finalResultStrLength);
    63. });
    64. }
    65. startIndex += groupCount;
    66. if (startIndex >= forCount) {
    67. break;
    68. }
    69. }
    70. } else {
    71. findStr(0, forCount, sourceString, sourceStrLength, resultSet, resultStrLength);
    72. }
    73. }
    74. //确认所有可能性均以找到
    75. executorService.shutdown();
    76. while (!executorService.isTerminated()) {
    77. try {
    78. Thread.sleep(1L);
    79. } catch (InterruptedException interruptedException) {
    80. interruptedException.printStackTrace();
    81. }
    82. }
    83. System.out.println(String.format("共找到%s种组合,耗时%sms", resultSet.size(), System.currentTimeMillis() - startTime));
    84. }
    85. /**
    86. * findStr1的核心方法
    87. *
    88. * @param startIndex
    89. * @param forCount
    90. * @param sourceString
    91. * @param sourceStrLength
    92. * @param resultSet
    93. * @param resultStrLength
    94. */
    95. private static void findStr(long startIndex, long forCount, String sourceString, int sourceStrLength, Set<String> resultSet, int resultStrLength) {
    96. //创建一个数组,记录每个for循环当前的位置,所以集合长度为当前for循环的层数
    97. int[] currentIndexArr = null;
    98. for (long l = startIndex; l < startIndex + forCount; l++) {
    99. //每次仅拨动一个位置
    100. //l为当前拨动的总次数,对应调整index集合的所有index值
    101. if (l == startIndex) {
    102. currentIndexArr = setIndexArr(resultStrLength, l + 1, sourceStrLength);
    103. } else {
    104. currentIndexArr = setIndexArr(currentIndexArr, sourceStrLength);
    105. }
    106. //查询是否存在重复的index
    107. if (existsRepeatIndex(currentIndexArr)) {
    108. continue;
    109. }
    110. //根据当前个位置的index,获取字符串
    111. getResultStr(sourceString, resultSet, currentIndexArr);
    112. }
    113. }
    114. /**
    115. * 根据当前循环次数,变更每个index的值,原理为:嵌套for循环是最内层每次都在递增,越往外层,则相邻内层每满6次,外层递增一次
    116. * 默认原始indexArr全部都是0
    117. *
    118. * @param strLength 当前字符串的长度
    119. * @param count 循环次数
    120. * @param length 进制数
    121. * @return
    122. */
    123. private static int[] setIndexArr(int strLength, long count, int length) {
    124. int[] newIndexArr = new int[strLength];
    125. long highBitIncrement = count;
    126. for (int i = strLength - 1; i >= 0; i--) {
    127. //取余,计算当前索引位的步进
    128. newIndexArr[i] = (int) (highBitIncrement % length);
    129. if (i != 0) {
    130. highBitIncrement = highBitIncrement / length;
    131. }
    132. }
    133. return newIndexArr;
    134. }
    135. /**
    136. * 基于上一次的index集合,从尾部进行递增
    137. *
    138. * @param beforeIndexArr 上一次的index集合
    139. * @param length 进制数
    140. * @return
    141. */
    142. private static int[] setIndexArr(int[] beforeIndexArr, int length) {
    143. for (int i = beforeIndexArr.length - 1; i >= 0; i--) {
    144. int beforeIndex = beforeIndexArr[i];
    145. int newIndex = (beforeIndex + 1) % length;
    146. beforeIndexArr[i] = newIndex;
    147. if (newIndex != 0) {
    148. return beforeIndexArr;
    149. }
    150. }
    151. return beforeIndexArr;
    152. }
    153. /**
    154. * 根据index集合,找到当前字符串
    155. *
    156. * @param str
    157. * @param set
    158. * @param indexArr
    159. */
    160. private static void getResultStr(String str, Set<String> set, int[] indexArr) {
    161. char[] chars = new char[indexArr.length];
    162. for (int i = 0; i < indexArr.length; i++) {
    163. chars[i] = str.charAt(indexArr[i]);
    164. }
    165. String strTemp = new String(chars);
    166. if (set.add(strTemp)) {
    167. // System.out.println(strTemp);
    168. }
    169. }
    170. /**
    171. * 是否存在重复index
    172. *
    173. * @param indexArr
    174. * @return
    175. */
    176. private static boolean existsRepeatIndex(int[] indexArr) {
    177. for (int i = 0; i < indexArr.length; i++) {
    178. for (int j = 0; j < indexArr.length; j++) {
    179. if (i == j) {
    180. continue;
    181. }
    182. if (indexArr[i] == indexArr[j]) {
    183. return true;
    184. }
    185. }
    186. }
    187. return false;
    188. }
    189. /**
    190. * 算法2,使用递归组合获取元素,在源数据长度大于9以后,相比算法1,具备明显的性能优势,但时间消耗依旧极为可观
    191. *
    192. * @param sourceString
    193. */
    194. public static void findStr2(String sourceString) {
    195. long startTime = System.currentTimeMillis();
    196. List<Character> data = new ArrayList<>();
    197. for (char c : sourceString.toCharArray()) {
    198. data.add(c);
    199. }
    200. for (int i = 1; i <= sourceString.length(); i++) {
    201. findStr(data, new ArrayList<Character>(), i);
    202. }
    203. System.out.println(String.format("共找到%s种组合,耗时%sms", resultSet.size(), System.currentTimeMillis() - startTime));
    204. }
    205. /**
    206. * findStr2的核心方法
    207. *
    208. * @param data
    209. * @param target
    210. * @param length
    211. */
    212. private static void findStr(List<Character> data, List<Character> target, int length) {
    213. List<Character> copyData;
    214. List<Character> copyTarget;
    215. if (target.size() == length) {
    216. String strTemp = target.stream().map(String::valueOf).collect(Collectors.joining());
    217. boolean add = resultSet.add(strTemp);
    218. }
    219. for (int i = 0; i < data.size(); i++) {
    220. copyData = new ArrayList<>(data);
    221. copyTarget = new ArrayList<>(target);
    222. copyTarget.add(copyData.get(i));
    223. copyData.remove(i);
    224. findStr(copyData, copyTarget, length);
    225. }
    226. }
    227. }