本文描述全排列問題的遞歸算法與排序算法。
遞歸算法
遞歸算法的原理是,對於給定的集合{1, 2, 3, 4},首先以{1}作爲開頭,求解集合{ 2, 3, 4}的排列。求解集合{2, 3, 4}的排列時,首先以{2}作爲開頭,求解集合{3, 4}的排列。求解集合{3, 4}的排列時,先求解以{3}開頭的排列,然後求解以{4}開頭的排列。至此可以求得排列1 2 3 4和1 2 4 3。以此回朔便可求得以{1}開頭的六個排列。
再求得以{1}開頭的排列之後,可以依次求解分別以{2},{3},{4}開頭的排列。可參考下圖求解
Java代碼參考:
static void swap(int[] a, int i, int j)
{
if (i != j)
{
int t = a[j];
a[j] = a[i];
a[i] = t;
}
}
static void permutation(int[] a, int start, int end)
{
if (start >= end)
{
for (int i = 0; i < end; i++)
{
System.out.printf("%d ", a[i]);
}
System.out.printf("\n");
return;
}
for (int i = start; i < end; i++)
{
swap(a, i, start); // 交換元素,使得每一個元素都有放在第一位的機會。
permutation(a, start + 1, end); // 遞歸調用
swap(a, i, start); // 恢復原始的list,不影響下次遞歸調用。
}
}
字典序算法
字典序算法使用字典序的思想來計算排列。具體來說,對於給定的一個排列,計算該排列是否具有下一個排列,如果存在則將下一個排列計算出來。例如,對於排列 1 2 3 4,下一個排列將是 1 2 4 3,這個排列是比 1 2 3 4大的排列中最小的一個。然而,對於排列 4 3 2 1來說就沒有下一個排列了,因爲該排列本身是所有排列中最大的一個。全排列的排序算法可以生成去除重複的所有排列,但是要求解所有排列時,必須先求出最小的排列,即該算法需要對集合先進行排序。
求解下一個排列的算法如下:
對於給定的數組a,從後往前掃描數組,找到位置i滿足a[i]>a[i-1],記錄位置i-1爲j。然後再次從後往前掃描數組a,找到位置k滿足a[k]>a[j]。交換a[j]與a[k]並逆序a[i...n)。對於排列 1 2 4 3的下一個排列1 3 2 4的求解如下圖所示:
Java參考代碼如下:
static boolean next_permutation(int list[])
{
int i, k;
int n = list.length;
// 步驟1:得到i。
for (i = n - 1; i > 0; i--)
{
if (list[i] > list[i - 1])
{
break; // 記下下標i。
}
}
// 表示當前排列已經是字典序中的最後一個序列,沒有下一個了。
if (i <= 0)
{
return false;
}
// 步驟2:得到k。
for (k = n - 1; k > 0; k--)
{
if (list[k] > list[i - 1])
{
break; // 記下下標j。
}
}
// 步驟3:互換list[i-1]和list[j]。
int temp = list[i - 1];
list[i - 1] = list[k];
list[k] = temp;
// 步驟4:逆置list[i...n]。
int start, end;
for (start = i, end = n - 1; start < end; start++, end--)
{
temp = list[start];
list[start] = list[end];
list[end] = temp;
}
return true;
}
c++的stl庫中有函數next_permutation()可以生成下一個排列。
對於給定序列 1 2 3 3,使用字典序方法生成的全排列如下:
1 2 3 3
1 3 2 3
1 3 3 2
2 1 3 3
2 3 1 3
2 3 3 1
3 1 2 3
3 1 3 2
3 2 1 3
3 2 3 1
3 3 1 2
3 3 2 1
參考資料: