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

设有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;
}

运行结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值