全排列演算法【非遞迴活動數實現】
阿新 • • 發佈:2019-01-07
求解一個問題,有很多種演算法/方法,一旦遇到比較有趣的思想/演算法,就忍不住記錄下來。
題:求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());
}
}
執行結果: