18 获取给定的序列的所有排列, 组合
2016-01-15 10:00
441 查看
前言
本博文部分图片, 思路来自于剑指offer 或者编程珠玑问题描述
思路
这里有两个问题, 一个是求所有的字符的全排列, 一个是求所有字符的组合对于问题一, 书上给了一种解法
思路 : 对于一个原始序列, 第一次交换第一个字符 和第一个字符以及后面的字符, 然后递归进入交换第二个字符阶段[同样是交换第二个字符 以及之后的字符], 这样直到交换完所有的字符之后, 打印出序列, 接着返回上一层递归, 交换回刚才的交换的两个字符, 进入下一个交换对象的交换
对于[a, b, c, d], 这样一个序列来说, 第一个字符存在4种交换可能, 第二个字符存在3种, … 最后共有 “4 * 3 * 2 * 1” 种交换序列
接下来一种思路是我实现的比较传统的思路 : 将备选字符放在一个容器, 从中抽取一个数据[不放回], 然后进行递归, 直到抽出的元素个数达到了我们的需求
同样 对于第一次抽取可能存在4种可能, 第二次抽取可能存在3种可能, … 最后共有 “4 * 3 * 2 * 1” 种序列
对于问题二
思路 : 我们建立一个抽取规约 : 在抽取了一个元素之后, 我们只能抽取这个元素之后的其他元素[相比于上面传统的思路 选择的余地主要是少了抽取的元素之前的元素]
示范一下 : 比如 “a, b, c”, 假设我们第一次抽取了”b”, 对于问题一的思路, 剩下的备选元素为”a, c”, 然而 对于问题二, 剩下的备选元素为”c”, 因为如果在抽取”b”前面的数据”a”的时候, 可能会造成与第一次抽取”a”的某个组合重叠, so 建立这个规约的目的就是为了防止各个组成元素相同的排列的重复
参考代码
/** * file name : Test11CharCompletePermutation.java * created at : 9:23:24 AM Jun 8, 2015 * created by 970655147 */ package com.hx.test05; public class Test11CharCompletePermutation { // 1. 给定一个char[] 要求打印出全排列 // 2. 打印出所有的组合 public static void main(String[] args) { char[] chars = new char[] {'a', 'b', 'c', 'd' }; Deque<Character> deque = new LinkedList<Character>(); charCompletePermutation(chars, deque, chars.length); // charCompletePermutation02(chars, 0); Log.horizon(); deque.clear(); charCombination(chars, deque); } // 思路 : 使用一个栈 [或者一个数组 [如果使用数组的话, 直接在下面push的地方改成响应的索引赋值, 然后去掉pop即可] ]来保存排列 // 每次从chars中拿走一个字符, 然后把剩下的字符传入charCompletePermutation 进行递归 public static void charCompletePermutation(char[] chars, Deque<Character> deque, int getN) { if(chars == null) { return ; } if(getN == 0 ) { Log.log(deque); } for(int i=0; i<chars.length; i++) { deque.push(chars[i]); charCompletePermutation(copyExcept(chars, i), deque, getN-1); deque.pop(); } } // 交换start 和start之后的数据 // 递归交换start+1 和start+1之后的数据 // 第一位有chars.length中可能, 第二位有chars.length-1种可能, ... public static void charCompletePermutation02(char[] chars, int start) { if(start == chars.length) { Log.log(chars); return ; } for(int i=start; i<chars.length; i++) { swap(chars, start, i); charCompletePermutation02(chars, start+1); swap(chars, i, start); } } // 计算字符序列的全组合 // 从getN属于[1, chars.length] 获取chars中getN个字符的组合 // 获取小于(chars.length/2)个数的组合的时候 使用charCombinationForN方法 // 获取大于(chars.length/2)个数的组合的时候 使用charCombinationForHalfLater方法 public static void charCombination(char[] chars, Deque<Character> deque) { for(int i=1; i<=chars.length/ 2; i++ ) { charCombinationForN(chars, deque, i); } for(int i=chars.length/2+1; i<=chars.length; i++) { charCombinationForHalfLater(chars, chars, deque, chars.length-i); } } // 适用于计算sourceChars中的长度小于(sourceChars.length/2)的组合 // 计算从chars中取出getN个字符的序列 // 从chars中取出一个字符, 然后递归在其之后的数据中取出getN-1个字符 private static void charCombinationForN(char[] chars, Deque<Character> deque, int getN) { if(getN > chars.length ) { return ; } // 注意 : 这里应该是退出条件之一, 使用完序列之后 应该return if(getN == 0) { Log.log(deque); return ; } // .. 最多到(chars.length-getN)开始 int end = chars.length - getN; for(int i=0; i<=end; i++) { deque.push(chars[i]); charCombinationForN(copyAfter(chars, i), deque, getN-1); deque.pop(); } } // 适用于计算sourceChars中的长度大于(sourceChars.length/2)的组合 // 计算从chars中取出(chars.length-compeltementyN)个字符的序列 // 从chars中取出一个字符, 然后递归在其之后的数据中取出(compeltementyN-1)个字符 private static void charCombinationForHalfLater(char[] sourceChars, char[] chars, Deque<Character> deque, int compeltementyN) { if(compeltementyN > chars.length ) { return ; } // 注意 : 这里应该是退出条件之一, 使用完序列之后 应该return if(compeltementyN == 0) { // Log.log(deque); Log.logWithoutLn("["); for(char ch : sourceChars) { if(! deque.contains(ch)) { Log.logWithoutLn(ch + ", "); } } Log.logWithoutLn("]"); Log.enter(); return ; } // .. 最多到(chars.length-getN)开始 int end = chars.length - compeltementyN; for(int i=0; i<=end; i++) { deque.push(chars[i]); charCombinationForHalfLater(sourceChars, copyAfter(chars, i), deque, compeltementyN-1); deque.pop(); } } // 复制除了exceptIdx 之外的其他元素 private static char[] copyExcept(char[] chars, int excpetIdx) { if((excpetIdx < 0) || (excpetIdx >= chars.length) ) { return chars; } char[] newChars = new char[chars.length-1]; System.arraycopy(chars, 0, newChars, 0, excpetIdx); System.arraycopy(chars, excpetIdx+1, newChars, excpetIdx, chars.length-excpetIdx-1); return newChars; } // 注意 : 不包括from // 复制chars中from之后的元素 private static char[] copyAfter(char[] chars, int from) { if((from < 0) || (from >= chars.length) ) { return chars; } char[] newChars = new char[chars.length-from-1]; System.arraycopy(chars, from+1, newChars, 0, newChars.length); return newChars; } // 交换数组中给定的两个索引的数据 private static void swap(char[] arr, int idx01, int idx02) { char tmp = arr[idx01]; arr[idx01] = arr[idx02]; arr[idx02] = tmp; } }
效果截图
总结
对于后两者的思路, 更好是使用一个boolean[] 来标记各个索引对应的数据是否可抽取, 但是 这里为了易读, 就这样copyExcept/ copyAfter了注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树