有重复元素排列问题

有重复元素的排列问题
【问题描述】
设R={ r1, r2 , …, rn}是要进行排列的n个元素。其中元素r1, r2 , …, rn可能相同。试设计一个算法,列出R的所有不同排列。
【编程任务】
给定n 以及待排列的n 个元素。计算出这n 个元素的所有不同排列。
【输入格式】
文件的第1 行是元素个数n,1≤n≤500。接下来的1 行是待排列的n个元素。
【输出格式】
计算出的n个元素的所有不同排列输出。文件最后1行中的数是排列总数。
【输入样例】
4
aacc
【输出样例】多解
aacc
acac
acca
caac
caca
ccaa
6
解题思路:单从排列的所有可能数出发,可以直接使用
这里写图片描述

其中M为字符总数,ni表示其中重复字符数。但是考虑到需要把所有可能的排列结果输出,所以还需遍历所有可能的排序,
最后统计所有可能排序的总数。假设,没有重复字符的情况,则是对应M个位置,每个位置所有可能字符数的乘积M!即为排列总数。
现在有重复元素,同样借助无重复字符排序方式来求解。首先有重复字符串构成一个无重复字符的字符串,
同时为新串用一个辅助数组表示每个字符出现的次数。即abbac,排列成无重复序列abc,辅助数组为221。然后采用回溯法,
先建立一个字符长度的数组,该数组的每一个位置都从无重复序列中选择一个可能的字符,每次选出一个字符,
则辅助数组与该字符对应减一,表示该字符可用数减一。直到最后一个字符,每次到最后一个字符的时候,
计数器count++同时把此时数组中排序的字符串输出,即为可能的排序。
然后回溯到上一个位置遍历上次递归选出的无重复串中的下一个字符递归直到最后一个字符后(这里递归下去后,也会回溯),
又往上回溯一步。这里一开始对初始化的字符串重新排列的原因就是为了在回溯到上一步时,
保证选出的下一个字符与上一次递归选取的字符不同。

例如abbac abc 221
递归到最后:aabbc
第一次回溯:aabcb
第二次回溯:aacbb
第三次回溯:ababc abacb abbac abcab abcba …
第四次回溯:baacb baabc …

#include<iostream>
#include<cstdio>
#include<cstring>
#include <stdlib.h>
#define num 5
using namespace std;
char a[1000];//用来存储当前位置所选取的字符
int ans;//用于统计所有可能的序列总数
/**
回溯法实现-有重复字符排序问题:
能排除相同字符重排序的根本是因为在调用回溯之前,所有字符按重复排列好,由于回溯上一步时,虽然当前字符被++,
循环遍历也不会再考虑选择当前字符,而是会选择当前字符的下一个字符,直到回溯到第一个字符选择新的可能字符

*/
void dfs(int dep,char *p,int *q)
{
    int r;
    if (dep==num)//表示排序完所有字符
    {
        ans++;//记录方案总数
        for (r=0; r<num; ++r)
            printf("%c",a[r]);//输出当前排序在a数组中的字符串
        printf("\n");
        return;
    }
    for (r=0; p[r]!='#'; ++r)
        if (q[r]>0)//如果这个字母没有取完
        {
            a[dep]=p[r];//a依然是记录数组
            q[r]--;//计数器-1
            dfs(dep+1,p,q);
            q[r]++;//回溯一步
        }
}
/***
对所有出现的字符进行统计出现的次数,按首次出现顺序存入p指针,出现次数存入q指针
*/
int strtj(char *p,int *q)
{
    int i,j,k;
    for(i=0; i<num; i++)
    {
        if(p[i]!='#')
        {
            q[i]=1;
            for(j=i+1; j<num; j++)
            {
                if(p[i]==p[j])
                {
                    p[j]='#';
                    q[j]=0;
                    q[i]++;
                }
            }
        }
    }
    for(j=0,i=0; i<num; i++,j++)
    {
        if(p[j]=='#')
        {
            for(k=j; k<num; k++)
            {//然后回溯到上一个位置遍历另一种可能(指的是所有不同字符中剩下的可选字符),
                p[k]=p[k+1];
                q[k]=q[k+1];
            }
            j--;
        }
    }
    p[j]='#';
    q[j]=0;
    return 0;
}
int main()
{
    char *p;
    int *q;
    int i=0,j=0,n=0,k=0;
    p=(char *)malloc(num*sizeof(char));
    q=(int *)malloc(num*sizeof(int));
    for(i=0; i<num&&p[i]!='\n'; i++)
        cin>>p[i];
    for(i=0; i<num; i++)
    {
        q[i]=0;
    }
    strtj(p,q);//统计每个字符出现的次数,并存入p q指针中
    dfs(0,p,q);//回溯构建所有可能的序列,输出所有可能的序列
    printf("%d",ans);//输出所有可能的总数
    return 0;
}

猜你喜欢

转载自blog.csdn.net/u010865478/article/details/69950375