求解一个问题,有很多种算法/方法,一旦遇到比较有趣的思想/算法,就忍不住记录下来。
题:求n=4时的全排列(当n=4时,序列为:{1, 2, 3, 4})
算法的思想:
1. 给排列中的每个元素均赋予一个向左或向右的箭头。
2. 如果元素k的箭头指向的是与其相邻但小于k的元素,则称元素k是活动的。
3. 从排列 1 2 3 … n 开始,找其中的最大活动元素k,将该元素k与它所指向的相邻元素交换位置,并改变所有大于k的元素的方向。
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* 生成全排列
*
* 算法的思想:
* 1. 给排列中的每个元素均赋予一个向左或向右的箭头。
* 2. 如果元素k的箭头指向的是与其相邻但小于k的元素,则称元素k是活动的。
* 3. 从排列 1 2 3 … n 开始,找其中的最大活动元素k,将该元素k与它
* 所指向的相邻元素交换位置,并改变所有大于k的元素的方向。
*/
public class Perm {
enum Direction {
LEFT, RIGHT //方向有左右
}
/**
* 把每个数看成一个元素,有数值有方向
*/
static class Element {
int data; //数值
Direction direction; //方向
public Element(int data, Direction direction) {
this.data = data;
this.direction = direction;
}
}
/**
* 生成全排列
* @param list 需要生成全排列的序列集合
*/
private static void perm(List<Element> list) {
int count = 1; //统计全排列的数目
if (list == null)
return;
//首先打印第一种情况
printAllElement(list, true);
int index; //活动数的下标
while (true) {
index = findMaxActiveNum(list); //找到最大活动数下标
if(index == -1) {
System.out.println("全排列总数为:" + count);
return;
}
//改变所有大于最大活动数的元素的方向
changeDirection(list, index);
//交换最大活动数与它所指向的相邻元素
if (list.get(index).direction == Direction.LEFT) {
swapElement(list, index-1, index);
} else {
swapElement(list, index, index+1);
}
count++;
printAllElement(list, true);
}
}
/**
* 找到最大活动数
* @param list 需要生成全排列的序列集合
*/
private static int findMaxActiveNum(List<Element> list) {
if(list == null)
return -1;
int length = list.size();
int index = -1;
//找出最大活动数的下标
for (int i = 0; i < length; i++) {
int data = list.get(i).data;
boolean isLeft = list.get(i).direction == Direction.LEFT;
//当不是活动数时,跳出此次循环-
if (i == 0 && isLeft || i == length-1 && !isLeft || //这个数的箭头所指的下一个元素为空
isLeft && data < list.get(i-1).data ||
!isLeft && data < list.get(i+1).data) { //这个数比箭头所指的下一个数小
continue;
} else {
if(index == -1) {
index = i;
} else {
index = list.get(i).data > list.get(index).data ? i : index; //记录最大活动数的下标
}
}
}
return index;
}
/**
* 交换两个元素的值和箭头
* @param list 需要生成全排列的序列集合
* @param index1 下标1
* @param index2 下标2
*/
private static void swapElement(List<Element> list, int index1, int index2) {
if(list == null)
return;
//交换两个对象的引用,达到交换值的目的
Element temp = list.get(index1);
list.set(index1, list.get(index2));
list.set(index2, temp);
}
/**
* 改变所有大于list.get(index)的元素的方向
* @param list 需要生成全排列的序列集合
* @param index 下标
*/
private static void changeDirection(List<Element> list, int index) {
if (list == null)
return;
int data = list.get(index).data;
for (int i = 0; i < list.size(); i++) {
if (list.get(i).data > data) {
list.get(i).direction = list.get(i).direction == Direction.LEFT ?
Direction.RIGHT :
Direction.LEFT;
}
}
}
/**
* 打印全部元素
* @param list 需要生成全排列的序列集合
* @param arrowFlag 是否打印箭头
*/
private static void printAllElement(List<Element> list, boolean arrowFlag) {
if (list == null)
return;
if(arrowFlag) {
for (Element element: list) {
switch (element.direction) {
case LEFT:
System.out.print("← ");
break;
case RIGHT:
System.out.print("→ ");
break;
}
}
}
System.out.println("");
for (Element element: list) {
System.out.print(element.data + " ");
}
System.out.println("");
}
/**
* 创建一个序列
* @param n 序列最大的数
* @return n为最大值的集合
*/
private static List<Element> createDataList(int n) {
List<Element> list = new ArrayList<>();
for (int i = 1; i <= n; i++) {
list.add(new Element(i, Direction.LEFT)); //初始化一个最大元素为n的序列,并且每个元素拥有一个向左的箭头
}
return list;
}
public static void main(String[] args) {
List<Element> elementList;
Scanner scanner = new Scanner(System.in);
int n; //读取n
System.out.println("【功能】非递归求全排列(活动数实现)");
System.out.println("请输入n:");
n = scanner.nextInt();
elementList = createDataList(n);
perm1(elementList, 0, elementList.size());
}
}
运行结果: