全排列的两种实现策略,以及去重、去镜像方案

147 阅读2分钟

一、全排列的实现

方案一:用子集树实现

// 注意: i不能作为全局变量

#include <iostream>
using namespace std;


int k,n;
int x[1000];
int vis[1000];

void show()
{
    for(int i=1;i<=n;i++)
        cout<<x[i];
    cout<<endl;
}


void dfs(int k)
{
    if(k>n)
    {
        show();
        return;
    }
    else
    {
        for(int i=1;i<=n;i++)
        {
            if(vis[i]==0)
            {
                x[k]=i;
                vis[i]=1;
                dfs(k+1);
                vis[i]=0;

            }

        }
    }
}

int main()
{
    cin>>n;
    dfs(1);
}

 

 

方案二:用排列树实现

// 注意: x数组必须先赋值
//		i不能作为全局变量

#include <iostream>
using namespace std;

int k,n;
int x[1000];

void show()
{
    for(int i=1;i<=n;i++)
        cout<<x[i];
    cout<<endl;
}

void dfs(int k)
{
    if(k>n)
    {
        show();
        return;
    }
    else
    {
        for(int i=k;i<=n;i++)
        {
            swap(x[i],x[k]);
            // 如果有剪枝函数的话,写在这里。  只把 “dfs(k+1);” 语句写在if语句里面
            	dfs(k+1);
            swap(x[i],x[k]);
        }
    }
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        x[i]=i;
    dfs(1);
}

 

全排列的生成会有两个问题,分别是重复问题和镜像问题。以排列树为例,引出去重复和去镜像策略。

 

 

二、去重复(check函数)

方案一

回溯前先对原始序列进行排序。如果当前的数与它前一个相等,则不允许当前的数执行swap动作。

核心在于比较的是当前位和前一位是否相等,这样就能保证对于重复的元素,有且仅有第一个出现的进行交换。保证重复元素只使用一次。

#include <algorithm>
#include <iostream>
using namespace std;

int k,n;
int x[1000];

void show()
{
    for(int i=1;i<=n;i++)
        cout<<x[i];
    cout<<endl;
}

void dfs(int k)
{
    if(k>n)
    {
        show();
        return;
    }
    else
    {
        for(int i=k;i<=n;i++)
        {
            if(i!=k && x[i]==x[i-1])
                continue;
            swap(x[i],x[k]);
            dfs(k+1);
            swap(x[i],x[k]);
        }
    }
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i];
    sort(x,x+n);
    dfs(1);
}

 

方案二

若想选第i个数交换到k位置上,需要判断 (i,n] 这一区间中是否有与第i个数相等的元素。若有,则不允许第i个数交换过来。跳过这个i就可以,毕竟后面还有“i”。这样就能保证对于重复的元素,有且仅有最后一个出现的进行交换。保证重复元素只使用一次。

#include <algorithm>
#include <iostream>
using namespace std;

int k,n;
int x[1000];

void show()
{
    for(int i=1;i<=n;i++)
        cout<<x[i];
    cout<<endl;
}

// 若想选第 i 个数交换到 k 位置上,需要判断 (i,n] 这一区间中是否有与第 i 个数相等的元素。若有,则不允许第 i 个数交换过来。跳过这个 i 就可以,毕竟后面还有 “i”。这样就能保证对于重复的元素,有且仅有最后一个出现的进行交换。保证重复元素只使用一次。
int check(int j)
{
    for(int i=j+1;i<=n;i++)
    {
        if(x[i]==x[j])
            return 0;
    }
    return 1;
}


void dfs(int k)
{
    if(k>n)
    {
        show();
        return;
    }
    else
    {
        for(int i=k;i<=n;i++)
        {
            if(!check(i))
                continue;
            swap(x[i],x[k]);
            dfs(k+1);
            swap(x[i],x[k]);
        }
    }
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i];

    dfs(1);
}

 

方案三

若想选第i个圆到k位置上,需要判断[k,i)这一区间中是否有圆与第i个圆等大。如有,则不允许第i个圆换过来

#include <iostream>
using namespace std;

int k,n;
int x[1000];

void show()
{
    for(int i=1;i<=n;i++)
        cout<<x[i];
    cout<<endl;
}

// 若想选第 i 个圆到 k 位置上,需要判断 [k,i) 这一区间中是否有圆与第 i 个圆等大。如有,则不允许第 i 个圆换过来
int check(int start,int end)
{
    for(int i=start ; i<end ; i++)
    {
        if(x[i]==x[end])
        return 0;
    }
    return 1;
}


void dfs(int k)
{
    if(k>n)
    {
        show();
        return;
    }
    else
    {
        for(int i=k;i<=n;i++)
        {
            if(!check(k,i))
                continue;
            swap(x[i],x[k]);
            dfs(k+1);
            swap(x[i],x[k]);
        }
    }
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i];
    dfs(1);
}

 

 

三、去镜像

思路:在每个序列生成的过程中,时刻保证第一位小于最后一位,就不会出现镜像,可以少考虑一半的数。

办法:先为第一位从n个数中挑选一个数,然后从剩下的n-1个数里为第n位选一个比第一位大的数,然后正常的生成2~n-1这一区间的全排列。

生成完这n-2个数之后,就得到了一个解。改变第n个数,继续正常的生成2~n-1这一区间的全排列。直到第n位已经没有别的可用的数,去更换第1位数,再重复执行上述所有过程...

注意:如果去镜像前没有去重复,那么去镜像操作不会有减半效果。

#include <iostream>
using namespace std;

int k,n,flag;
int x[1000];

void show()
{
 for(int i=1;i<=n;i++)
     cout<<x[i];
 cout<<endl;
}


void dfs(int k)
{
 if(k>n-1)
 {
     show();
     return;
 }
 else
 {
     for(int i=k;i<=n-flag;i++)
     {
         if(k==1 || flag)    // flag的作用是保证正常的进行[2,n-1]区间的全排列的生成
         {
             swap(x[i],x[k]);
             dfs(k+1);
             swap(x[i],x[k]);
         }
         else
         {
             if (x[i] > x[1])
             {
                 swap(x[i],x[n]);
                 flag=1;
                 dfs(2);
                 flag=0;
                 swap(x[i],x[n]);
             }
         }
     }
 }
}

int main()
{
 cin>>n;
 for(int i=1;i<=n;i++)
     cin>>x[i];
 dfs(1);
}