多重全排列的生成与构造算法

设有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 "stdafx.h"
#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 (i == 1)
        {
            if (TF == true)
            {
                arrange[i - 1] = 1;
                ++num[0];
                ++i;
            }
            else
            {   
                --num[arrange[i - 1] - 1];
                ++arrange[i - 1];
                if (arrange[i-1] > K)
                {
                    break;
                }
                else
                {
                    ++num[arrange[i - 1] - 1];
                    ++i;
                    TF = true;
                }
            }
        }
        else
        {
            if (TF == true)
            {
                arrange[i - 1] = search(1, num, count);
                ++num[arrange[i - 1] - 1];
            }
            else
            {
                int temp;
                if ((temp = search(arrange[i - 1] + 1, num, count)) == 0)
                {
                    --num[arrange[i - 1] - 1];
                    --i;
                    continue;
                }
                else
                {
                    --num[arrange[i - 1] - 1];
                    arrange[i - 1] = temp;
                    ++num[arrange[i - 1] - 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 "stdafx.h"
#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)
    {
        i = 0;
    }
    else
    {
        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;
            i++;
            TF = 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;
        }
    }
}

const int K = 3;
const int N = 5;

struct back
{
    vector<int> sub; //存放find函数找到的多重全排列中的一个子排列的排列位置
    vector<int> father;   //存放多重全排列所有N个排列位置被与其对应的sub子排列之前的所有排列占据后剩下的排列位置
    back(int k) :sub(k, 0) {}
};

void diffset(vector<back> &stack, const vector<int> &count)  //计算father和sub的差集,得到sub的下一排列可取得排列位置
{
    int i = 0;
    int j = 0;
    back temp(count[stack.size()]);
    while (i < stack.back().father.size() && j < stack.back().sub.size())
    {
        if (i + 1 < stack.back().sub[j])
        {
            temp.father.push_back(stack.back().father[i]);
            ++i;
        }
        else if (i + 1 > stack.back().sub[j])
        {
            ++j;
        }
        else
        {
            ++i;
            ++j;
        }
    }

    while (i < stack.back().father.size())
    {
        temp.father.push_back(stack.back().father[i]);
        ++i;
    }

    stack.push_back(temp);

}

int main()
{
    vector<int> count{2, 1, 2};  //a1,---,ak值
    int i = 1;  //循环当前正在处理的子排列序号
    int num = 0;  //多重全排列计数
    bool TF = true;   //回溯标志,true前进至本层,false回溯至本层
    vector<back> stack;
    while (true)
    {
        if (i == 1)
        {
            if (TF == true)
            {
                stack.push_back(back(count[0]));
                find(true, N, count[i - 1], stack.back().sub);

                for (int j = 1; j <= N; j++)
                    stack.back().father.push_back(j);
                ++i;
            }
            else
            {
                if (find(false, N, count[i-1], stack.back().sub) == true)
                {
                    ++i;
                    TF = true;
                }
                else
                {
                    break;
                }
            }
        }
        else
        {
            if (TF == true)
            {
                diffset(stack, count);
                find(true, stack.back().father.size(), count[i - 1], stack.back().sub);
            }
            else
            {
                if (find(false, stack.back().father.size(), count[i - 1], stack.back().sub) == false)
                {
                    stack.pop_back();
                    --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 < stack[i].sub.size(); ++j)
                    {
                        temp[stack[i].father[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;
}

运行结果:

多重全排列的生成与构造算法

上一篇:22.括号生成——回溯


下一篇:leetcode 2097. 合法重新排列数对