原題如下:用1、2、2、3、4、5這六個數字,用java寫一個main函數,打印出所有不同的排列,如:512234、412345等,要求:"4 "不能在第三位, "3 "與 "5 "不能相連.
我看了回貼都沒有很好解決,主要是沒有排除重複。
解決思路:強化題目,用1、2、2、3、4、5這六個數字排列“遞增”序列。其他要求不變。
算法思路:顯然是遞歸,初始序列122345,先從末兩位(45)變化(45,54),然後末三位(345) ... 直到最後六位.怎樣解決重複問題?很簡單,由於是遞增序列,每生成新序列可與前一生成序列比較,如<放棄當前序列。當然有更好效率,如預先預測。代碼如下:
class test
{
// 當前固定部分
private String CurFixPart;
private String PreGenNum;
public static void main(String[] args)
{
test t=new test();
t.GenControll( "122345 ");
}
// 調整字符串s位置pos字符到最前
private String shift(String s, int pos)
{
String newStr;
if (s.length()> pos+1)
newStr=s.substring(pos, pos+1)
+s.substring(0, pos)
+s.substring(pos+1);
else
newStr=s.substring(pos)
+s.substring(0, pos);
return newStr;
}
protected int Validate(String newNum)
{
String newGenNum=CurFixPart+newNum;
if (Integer.valueOf(newGenNum)<=Integer.valueOf(PreGenNum))
return 0;
if (newGenNum.substring(2,3).equals( "4 ") ||
(newGenNum.indexOf( "35 ")!=-1) || (newGenNum.indexOf( "53")!=-1))
return 0;
PreGenNum=newGenNum;
System.out.println(newGenNum);
return 0;
}
public void GenControll(String Base)
{
PreGenNum= "0 ";
CurFixPart= " ";
GenNext(Base, 0);
}
void GenNext(String varPart, int curPos)
{
if (varPart.length()==2)
{
Validate(varPart);
Validate(shift(varPart, 1));
return;
}
// Next Layer
String newGen=shift(varPart, curPos);
String SavedFixPart=CurFixPart;
CurFixPart=CurFixPart+newGen.substring(0,1);
GenNext(newGen.substring(1), 0);
CurFixPart=SavedFixPart;
// 同層遞增
if (curPos==varPart.length()-1)
return;
GenNext(varPart, curPos+1);
}
}
序列122345測試通過。
1 把問題歸結爲圖結構的遍歷問題。實際上6個數字就是六個結點,把六個結點連接成無向連通圖,對於每一個結點求這個圖形的遍歷路徑,所有結點的遍歷路徑就是最後對這6個數字的排列組合結果集。
2 顯然這個結果集還未達到題目的要求。從以下幾個方面考慮:
1. 3,5不能相連:實際要求這個連通圖的結點3,5之間不能連通, 可在構造圖結構時就滿足改條件,然後再遍歷圖。
2. 不能有重複: 考慮到有兩個2,明顯會存在重複結果,可以把結果集放在TreeSet中過濾重複結果
3. 4不能在第三位: 仍舊在結果集中去除滿足此條件的結果。
採用二維數組定義圖結構,最後的代碼是:
import java.util.Iterator;
import java.util.TreeSet;
public class TestQuestion {
private String[] b = new String[]{ "1 ", "2 ", "2 ", "3 ", "4 ", "5 "};
private int n = b.length;
private boolean[] visited = new boolean[n];
private int[][] a = new int[n][n];
private String result = " ";
private TreeSet set = new TreeSet();
public static void main(String[] args) {
new TestQuestion().start();
}
private void start() {
// Initial the map a[][]
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) {
a[i][j] = 0;
} else {
a[i][j] = 1;
}
}
}
// 3 and 5 can not be the neighbor.
a[3][5] = 0;
a[5][3] = 0;
// Begin to depth search.
for (int i = 0; i < n; i++) {
this.depthFirstSearch(i);
}
// Print result treeset.
Iterator it = set.iterator();
while (it.hasNext()) {
String string = (String) it.next();
// "4 " can not be the third position.
if (string.indexOf( "4 ") != 2) {
System.out.println(string);
}
}
}
private void depthFirstSearch(int startIndex) {
visited[startIndex] = true;
result = result + b[startIndex];
if (result.length() == n) {
// Filt the duplicate value.
set.add(result);
}
for(int j = 0; j < n; j++) {
if (a[startIndex][j] == 1 && visited[j] == false) {
depthFirstSearch(j);
} else {
continue;
}
}
// restore the result value and visited value after listing a node.
result = result.substring(0, result.length() -1);
visited[startIndex] = false;
}
}
3,5不能相連:實際要求這個連通圖的結點3,5之間不能連通, 可在構造圖結構時就滿足改條件,然後再遍歷圖。
代碼中請注意這幾行:
// 3 and 5 can not be the neighbor.
a[3][5] = 0;
a[5][3] = 0;
只要這樣定義圖,根本不用在代碼中寫IF ELSE語句。
實際上基於圖的算法好處在於,只要你能定義好滿足題目要求的圖結構,遍歷的結果就是你要的結果,不用任何對遍歷結果做任何處理。包括本題中的:4不能在第三位置,3,5不能相連,唯一
性要求,其實都可以在體現在構造的圖形結構裏,然後直接遍歷圖取得自己要的結果。而不用再次處理結果集。
只是說這裏實際上對其它要求要體現在圖結構裏有困難(理論上是可以的),但起碼3,5不能相接是很好構造的,就是上面的代碼段來解釋的。
關於圖形數據結構建議先看看數據結構的書,主要是將如何利用二維數組描述圖結構,再看看圖的深度遍歷實現原理。最後再應用到這個問題上來,自然就不難明白了。
補充:由於時間的關係,上面給的代碼沒有多做考慮。
其實有兩方面可以優化一下:
1。Validate 可以學習Struts,利用抽象Validate集去掉If/Else語句。在此不多說。
2。在同層遞增前增加“重複預測“減少不必要的重複排序,提高效率。
改進代碼如下,其中“//********”處爲修改地方
class test
{
// 當前固定部分
private String CurFixPart;
private String PreGenNum;
public static void main(String[] args)
{
test t=new test();
t.GenControll( "1111111111 ");
}
// 調整字符串s位置pos字符到最前
private String shift(String s, int pos)
{
String newStr;
if (s.length()> pos+1)
newStr=s.substring(pos, pos+1)
+s.substring(0, pos)
+s.substring(pos+1);
else
newStr=s.substring(pos)
+s.substring(0, pos);
return newStr;
}
protected int Validate(String newNum)
{
String newGenNum=CurFixPart+newNum;
if (Integer.valueOf(newGenNum)<=Integer.valueOf(PreGenNum))
return 0;
if (newGenNum.substring(2,3).equals( "4 ") ||
(newGenNum.indexOf( "35 ")!=-1) || (newGenNum.indexOf( "53")!=-1))
return 0;
PreGenNum=newGenNum;
System.out.println(newGenNum);
return 0;
}
public void GenControll(String Base)
{
PreGenNum= "0 ";
CurFixPart= " ";
GenNext(Base, 0);
}
void GenNext(String varPart, int curPos)
{
if (varPart.length()==2)
{
Validate(varPart);
Validate(shift(varPart, 1));
return;
}
// Next Layer
String newGen=shift(varPart, curPos);
String SavedFixPart=CurFixPart;
CurFixPart=CurFixPart+newGen.substring(0,1);
GenNext(newGen.substring(1), 0);
CurFixPart=SavedFixPart;
//*************
// 同層遞增
while (true)
{
if (curPos==varPart.length()-1)
break;
// 預測是否重複
curPos++;
if (Integer.valueOf(CurFixPart+shift(varPart, curPos))<=Integer.valueOf(PreGenNum))
continue;
GenNext(varPart, curPos);
break;
}
//*************
}
}
本來以爲很簡單,用遞歸一下就搞定的問題,結果按照遞歸的常規思路,在打印的時候碰到了很麻煩的問題,費了我不少腦筋,結果答案還是很簡單,弄得我做出來了過後都準備把結果放到博客商保存了。其實我做的是字符串排列顯示的問題,其中打印時的條件判斷語句是根據樓主的特殊要求添加的,諸位參考:
import java.util.*;
public class test {
public static void main(String[] arg) {
Scanner r=new Scanner(System.in);
String s=r.nextLine();
Pailie(s, " ");
}
static void Pailie(String s, String p) {
if(s.length() <1) {
String t=p+s;
if(t.charAt(2)!= '4 ' && t.contains("35 ")==false)
System.out.println(t);
}
else {
for(int i=0; i<s.length(); i++) {
Pailie(s.substring(1),p+s.substring(0,1));
s=s.substring(1)+s.substring(0,1);
}
}
}
}
提醒一下,大家都沒理解,其實這題難點是如何有效排除重複的排列。
JianZhiZG(健之) 你可以試一下1223,你的算法沒有排除重複
對算法的最終改進,這次應該是比較完整的版本了吧。
思路是這樣的,對於任意一個串利用遞歸進行排列時,我們是循環串中的每個字符到第一個字符進行遞歸。如果串中字符出現重複的話,則重複的字符只可以利用遞歸算法一次,即只要與前面相同的字符循環到第一個字符時不調用遞歸就可以避免重複,爲此,我們只需要按如下方式修改算法:
import java.util.*;
public class test {
static int count=0;
public static void main(String[] arg) {
Scanner r=new Scanner(System.in);
String s=r.nextLine();
Pailie(s, " ");
System.out.println( "Total:"+count);
}
static void Pailie(String s,String p) {
if(s.length()<1) {
System.out.println(p);//字符串長度小於1,換行
count++;
}
else {
int index[]=new int[s.length()];
for(int i=0; i <s.length(); i++)//該循環將所有字符的第一次出現的位置記錄在數組index中
index[i]=s.indexOf(s.charAt(i));
for(int i=0; i <s.length(); i++) {
if(i==index[i])//只有當循環數與第一次記錄數相等時才遞歸,保證相同字符中的第一個調用
Pailie(s.substring(1),p+s.substring(0,1));//遞歸,打印其它字符
s=s.substring(1)+s.substring(0,1);//循環移位
}
}
}
}
這樣,由於相同的字符只遞歸調用了一次,則避免了重複串的排列。下面是幾個典型的運算結果:
2222222(輸入。當串中的所有字符相同時,應該遞歸調用1次)
2222222
Total:1
122222(輸入。當串中只有一個字符不同時,該字符應該循環所有不同位置)
122222
222221
222212
222122
221222
212222
Total:6
1223(輸入。szlhj的例子)
1223
1232
1322
2231
2213
2312
2321
2123
2132
3122
3221
3212
Total:12
122345(輸入。本題的要求,最後結果爲360個,其它的我就不列出來了,大家可以自己測試)
122345
122354
122453
122435
122534
……
543221
543212
Total:360
貼個C語言版的:
#include
<stdio.h>
#define MAXN 6
int a[MAXN], o[MAXN], used[256] = { 0 }, count = 0;
void print()
{
int i;
for (i = 0; i < MAXN; ++i)
printf( "%d ", o[i]);
printf( "\n ");
}
void solve(const int depth)
{
int i;
for (i = 0; i < MAXN; ++i)
{
if (used[i] > 0)
{
--used[i];
if (
!(i + 1 == 4 && depth == 2) && // 4 at position 3
!(i + 1 == 3 && depth > 0 && o[depth - 1] == 5) &&
// 53
!(i + 1 == 5 && depth > 0 && o[depth - 1] == 3)
// 35
)
{
o[depth] = i + 1;