本文目录
1 情景
排列:从n个不同元素中任取m(m<=n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。
全排列:当m=n时,所有的排列情况叫全排列。全排列情况的数量 = n! = n * (n-1) * (n-2) * ... * 1
假设:现在要对[1,2,3,4,5]进行全排列,会有5!=5*4*3*2*1=120种情况。
例如:[1,2,3,4,5] 或 [5,4,3,2,1] 或 [1,3,5,2,4] 等等。
为了寻找所有可能的排列情况,本文先通过最简单的循环方式求解,随后,通过递归方式求解。
2 循环方法
首先,从最简单的循环方法入手。假设要求解[1,2,3,4,5]的全排列,则可以嵌套循环5次,每个循环对应一个位置,从而遍历出所有的情况。
public static void main(String[] args) {
permutate(5);
}
public static void permutate(int size) {
int count = 0; // 用于计算排列情况的总数量
for (int i1 = 1; i1 <= size; i1++) {
for (int i2 = 1; i2 <= size; i2++) {
if (i2 == i1) { // 若出现重复数字,则跳过该数字。
continue;
}
for (int i3 = 1; i3 <= size; i3++) {
if (i3 == i1 || i3 == i2) { // 若出现重复数字,则跳过该数字。
continue;
}
for (int i4 = 1; i4 <= size; i4++) {
if (i4 == i1 || i4 == i2 || i4 == i3) { // 若出现重复数字,则跳过该数字。
continue;
}
for (int i5 = 1; i5 <= size; i5++) {
if (i5 == i1 || i5 == i2 || i5 == i3 || i5 == i4) { // 若出现重复数字,则跳过该数字。
continue;
}
count++; // 排列数量加一
System.out.println(i1 + " " + i2 + " " + i3 + " " + i4 + " " + i5);
}
}
}
}
}
System.out.println(count);
}
3 递归方法
对于循环方法,在编写代码的时候,就要确定问题的空间大小,从而确定嵌套多少个循环。
很明显,这种循环方法比较死板,但可以基于其思路,改变成递归方法。
public static void main(String[] args) {
permutate(new int[5], 0, 5);
}
/**
* 递归方法
*
* @param matrix
* 用于表示排列情况
* @param index
* 表示当前递归的位置
* @param size
* 表示排列问题的空间大小
*/
public static void permutate(int[] matrix, int index, int size) {
// 遍历当前位置(index)的所有情况
for (int num = 1; num <= size; num++) {
// 检验是否有重复数字出现
boolean isOverlap = false;
for (int i = 0; i < index; i++) {
if (num == matrix[i]) {
isOverlap = true;
break;
}
}
if (isOverlap) {
continue;
}
// 设置当前位置的值
matrix[index] = num;
// 递归结束条件:达到最后一个位置
if (index == size - 1) {
System.out.println(Arrays.toString(matrix));
} else {
// 递归:确定下一个位置(index+1)的值
permutate(matrix, index + 1, size);
}
}
}
4 实际应用
虽然,很多时候,并不是简单的对一组自然数进行排列。但是,无论排列目标是什么,都可以将其转化为对数组下标的排列,从而,将问题变成对一组自然数的排列。
例如:对一组名字["小明","小红","小刚","小雷"]进行排列,可以转化为对String[]数组的下标[0,1,2,3]进行排列,得到了下标的排列情况,即得到了名字的排列情况。
将递归算法进行改进,将排列情况存入某个数组,作为备用。
public static void main(String[] args) {
int size = 4;
int rowSize = 4 * 3 * 2 * 1;
int[][] array = new int[rowSize][size];
int[] row = new int[1];
permutate(new int[size], 0, size, array, row);
// 显示结果
String[] names = { "小明", "小红", "小刚", "小雷" };
for (int rowIndex = 0; rowIndex < array.length; rowIndex++) {
System.out.print(names[array[rowIndex][0]] + ",");
System.out.print(names[array[rowIndex][1]] + ",");
System.out.print(names[array[rowIndex][2]] + ",");
System.out.println(names[array[rowIndex][3]]);
}
}
/**
* 递归方法
*
* @param matrix
* 用于表示排列情况
* @param index
* 表示当前递归的位置
* @param size
* 表示排列问题的空间大小
* @param array
* 用于存储排列情况
* @param row
* 用作array的下标,表示当前要存储的位置。
* 由于row要作为全局通用的数据,因此采用单一元素数组new int[1]作为参数,实际传递的是引用地址(数组首地址),从而达到全局通用的效果。
* 可以删除该参数,并通过定义一个全局变量达到相同效果。
*/
public static void permutate(int[] matrix, int index, int size, int[][] array, int[] row) {
// 遍历当前位置(index)的所有情况
for (int num = 0; num < size; num++) {
// 检验是否有重复数字出现
boolean isOverlap = false;
for (int i = 0; i < index; i++) {
if (num == matrix[i]) {
isOverlap = true;
break;
}
}
if (isOverlap) {
continue;
}
// 设置当前位置的值
matrix[index] = num;
// 递归结束条件:达到最后一个位置
if (index == size - 1) {
// 此处必须通过clone方法整行赋值,否则,两者将指向同一引用地址,当matrix的值再次改变时,会影响array中的值。
// 也可以不用clone方法,而通过for循环,逐位赋值。
array[row[0]] = matrix.clone();
// 存储下标位置递增
row[0]++;
} else {
// 递归求解
permutate(matrix, index + 1, size, array, row);
}
}
}