设有a1+a2+—+aK=N,a1,a2,—,aK为正整数(K>=2),将a[1],a[2],—,a[K]K个数排列至1,2,—N这N个排列位置上,使得a[1],a[2],—,a[K]所占据的排列位置数恰好分别为a1,a2,—,aK,这样占据1,2,—NN个排列位置的a[1],a[2],—,a[K]构成的排列为一个排列位置数为N,排列数数目为K的多重全排列。
现在介绍算法,该算法能够生成符合上述要求的所有多重全排列
说明:以下二种算法仅适用于k>=2的情形,k=1为特殊情形我没有专门处理,不处理问题也不大
算法一(K=3,N=5,a1=2,a2=1,a3=2,a[i]=i):思路简单代码易于理解,代码简单。基本思路为在栈中回溯和向前试探,向前试探寻找满足容量上限的下一个候选数,从而扩大候选解的规模,候选解规模达到N即得到一个多重全排列。无法发现符合容量上限的下一个候选数即回溯
C++代码:
#include <vector>
#include <iostream>
using namespace std;
const int K = 3;
const int N = 5;
int search(int start, const vector<int>& num, const vector<int>& count) //从start开始顺序查找满足容量限制的第一个数字,查找失败返回0,否则返回找到的数字
{
for (; start <= K; ++start)
{
if (num[start - 1] + 1 <= count[start - 1])
return start;
}
return 0;
}
int main()
{
vector<int> count{ 2, 1, 2 }; //a1,---,ak值
vector<int> num(count.size(), 0); //统计循环过程中1,---,k的数目
vector<int> arrange(N, 0); //存放找到的多重全排列的栈结构
int i = 1; //栈顶指针
int number = 0;
bool TF = true; //回溯标志
while (true)
{
if (TF == false)
{
if (i == 1 && arrange[i - 1] == K)
{
break;
}
--num[arrange[i - 1] - 1];
}
int temp;
if ((temp = search(TF ? 1 : arrange[i - 1] + 1, num, count)) == 0)
{
--i;
continue;
}
else
{
arrange[i - 1] = temp;
++num[temp - 1];
}
if (i == N) //找到一个多重全排列输出
{
++number;
cout << "第" << number << "个多重全排列:";
for (const int& m : arrange)
{
cout << m;
}
cout << endl;
cout << endl;
TF = false;
}
else
{
++i;
TF = true;
}
}
return 0;
}
运行结果:
算法二(K=3,N=5,a1=2,a2=1,a3=2,a[i]=i):代码相对而言更复杂,方法较笨,就是单纯地模拟手工寻找多重全排列的过程,该过程中按a[1],—,a[k]的顺序逐一选择子排列,成功找到子排列则向前试探扩大候选解的规模寻找下一个,当a[1],—,a[k]的所有子排列全部找到后即获得一个多重全排列,当已无新的子排列可供选择时即回溯
C++代码:
#include <vector>
#include <iostream>
using namespace std;
/*bool find(bool TF, int n, int k, vector<int>& temp) //TF==true,函数从头寻找满足1<=a1<--<ak<=n的第一个排列a1,a2,--,ak存放在temp中
{ //TF==false,函数寻找上一次找到的存放在temp中满足1<=a1<--<ak<=n的排列a1,a2,--,ak的下一个排列,找到存放在temp中返回true,找不到返回false
int i;
if (TF == true)
{
if (n == k)
{
for (int i = 1; i <= k; ++i)
{
temp[i - 1] = i;
}
return true;
}
i = 0;
}
else
{
if (n == k)
return false;
i = k - 1;
}
while (1)
{
if (i == 0)
{
if (TF == true)
{
temp[i] = 1;
}
else
{
temp[i]++;
}
if (temp[i] > n - k + 1)
return false;
if (k == 1)
return true;
}
else
{
if (TF == true)
temp[i] = temp[i - 1] + 1;
else
{
temp[i]++;
if ((temp[i] - temp[i - 1]) > (n - temp[i - 1]) - (k - i) + 1)
{
i--;
TF = false;
continue;
}
}
if (i == k - 1)
{
return true;
}
}
i++;
TF = true;
}
}*/
void find(int N, int K, vector<vector<int>>& all_comb)
{
vector<int> a(K, 0);
bool TF;
int i, j;
i = 0;
TF = false;
j = 0;
while (1)
{
if (i == 0)
{
a[i]++;
if (a[i] > N - K + 1)
break;
}
else
{
if (TF == false)
a[i] = a[i - 1] + 1;
else
{
a[i]++;
if ((a[i] - a[i - 1]) > (N - a[i - 1]) - (K - i) + 1)
{
i--;
TF = true;
continue;
}
}
}
if (i == K - 1)
{
all_comb[j++] = a;
TF = true;
continue;
}
i++;
TF = false;
}
}
long long C(long long m, long long n, vector<vector<long long>> &C_Matrix)
{
if (m == n || n == 0)
return 1;
long long top_left;
if (C_Matrix[m - 1][n - 1] != 0)
top_left = C_Matrix[m - 1][n - 1];
else
{
top_left = C_Matrix[m - 1][n - 1] = C(m - 1, n - 1, C_Matrix);
}
long long top;
if (C_Matrix[m - 1][n] != 0)
top = C_Matrix[m - 1][n];
else
{
top = C_Matrix[m - 1][n] = C(m - 1, n, C_Matrix);
}
return top_left + top;
}
struct back
{
int sub; //存放find函数找到的多重全排列中的一个子排列的排列位置
vector<int> father; //存放多重全排列所有N个排列位置被与其对应的sub子排列之前的所有排列占据后剩下的排列位置
back(int k, int f) :sub(k), father(f) {}
back() = default;
};
void diffset(vector<back>& stack, const vector<int>& count, const vector<int> &count_add, int cur_level, vector<vector<vector<int>>> &all_combination) //计算father和sub的差集,得到sub的下一排列可取得排列位置
{ //优化
int i = 0;
int j = 0;
int k = 0;
cur_level -= 2;
back temp(0, count_add[cur_level]);
while (i < stack[cur_level].father.size() && j < all_combination[cur_level][stack[cur_level].sub].size())
{
if (i + 1 < all_combination[cur_level][stack[cur_level].sub][j])
{
temp.father[k++] = stack[cur_level].father[i];
++i;
}
else if (i + 1 > all_combination[cur_level][stack[cur_level].sub][j])
{
++j;
}
else
{
++i;
++j;
}
}
while (i < stack[cur_level].father.size())
{
temp.father[k++] = stack[cur_level].father[i];
++i;
}
stack[cur_level + 1] = temp;
}
const int K = 3;
const int N = 5;
int main() //C函数vector没写
{
vector<int> count{ 2, 1, 2 }; //a1,---,ak值
vector<int> count_add(count.size() - 1);
int m_max = N;
int n_max = count[0];
count_add[0] = N - count[0];
for (int i = 1; i < count.size() - 1; ++i)
{
if (m_max < count_add[i - 1])
m_max = count_add[i - 1];
if (n_max < count[i])
n_max = count[i];
count_add[i] = count_add[i - 1] - count[i];
}
if (m_max < count_add.back())
m_max = count_add.back();
if (n_max < count.back())
n_max = count.back();
vector<vector<long long>> C_Matrix(m_max + 1, vector<long long>(n_max + 1, 0));
vector<vector<vector<int>>> all_combination(count.size());
all_combination[0] = vector<vector<int>>(C(N, count[0], C_Matrix), vector<int>());
find(N, count[0], all_combination[0]);
for (size_t i = 1; i < all_combination.size(); ++i)
{
all_combination[i] = vector<vector<int>>(C(count_add[i - 1], count[i], C_Matrix), vector<int>());
find(count_add[i - 1], count[i], all_combination[i]);
}
int i = 2; //循环当前正在处理的子排列序号
int num = 0; //多重全排列计数
bool TF = true; //回溯标志,true前进至本层,false回溯至本层
vector<back> stack(count.size());
stack[0] = back(0, N);
for (int j = 1; j <= N; j++)
stack[0].father[j - 1] = j;
while (true)
{
if (TF == true)
{
diffset(stack, count, count_add, i, all_combination);
}
else
{
++stack[i - 1].sub;
if (stack[i - 1].sub == all_combination[i - 1].size())
{
if (i == 1)
break;
--i;
continue;
}
}
if (i == K) //找到一个多重全排列,输出
{
++num;
vector<int> temp(N, 0);
for (vector<back>::size_type i = 0; i < stack.size(); ++i)
{
for (vector<int>::size_type j = 0; j < all_combination[i][stack[i].sub].size(); ++j)
{
temp[stack[i].father[all_combination[i][stack[i].sub][j] - 1] - 1] = i + 1;
}
}
cout << "第" << num << "个多重排列:";
for (const int& m : temp)
{
cout << m;
}
cout << endl;
cout << endl;
TF = false;
}
else
{
++i;
TF = true;
}
}
return 0;
}
运行结果: