全排列(Java)

本文目录

1 情景

2 循环方法

3 递归方法

4 实际应用

5 相关文章


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);
		}
	}
}

5 相关文章

《位运算:减法与补码》

《异或(^)的性质与应用》

《图解:常用排序算法(冒泡、选择、插入、希尔、快速、归并、堆)》

《回溯算法(试探算法)》

《动态规划:鸡蛋掉落》

《动态规划:单词拆分》

《状态机:只出现一次的数字II》

《最小堆:TopK问题》

《链表:快慢指针》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值