全排列、子集

问题

输出数组P[a,b,b,b]的全排列\全子集 ; 注意集合元素有重复

测试

#define N 4
char P[]={'a', 'b', 'b', 'c'}

int main()
{
    int i, j, A[N];
    printf("全排列-递归\n");
    permutation1(N, A, 0);

    printf("全排列-非递归\n");
    //用索引 转化数组P 为A[1, 2, 2, 4]
    for(i=0; i<N; i++)
    {
        if( i==0 || P[i]!=P[i-1] )
            j = i;

        A[i]=j;
    }
    permutation2(N, P);

    printf("子集--增量构造--没有去重\n");
    subset1(N, A, 0);

    printf("子集--位向量--没有去重\n");
    subset2(N, A, 0);

    printf("子集--二进制--去重啦\n");
    //用索引 转化数组P 为A[1, 2, 2, 4]
    for(i=0; i<N; i++)
    {
        if( i==0 || P[i]!=P[i-1] )
            j = i;

        A[i]=j;
    }
    memset(HashArray, -1, sizeof(HashArray));
    subset3(N, A);
}

输出:
全排列-递归
a b b b 
b a b b 
b b a b 
b b b a 
全排列-非递归
a b b b 
b a b b 
b b a b 
b b b a 
子集--增量构造--没有去重

a 
a b 
a b b 
a b b b 
a b b 
a b 
a b b 
a b 
b 
b b 
b b b 
b b 
b 
b b 
b 
子集--位向量--没有去重

b 
b 
b b 
b 
b b 
b b 
b b b 
a 
a b 
a b 
a b b 
a b 
a b b 
a b b 
a b b b 
子集--二进制--去重啦

a 
b 
a b 
b b 
a b b 
b b b 
a b b b 

全排列

递归算法



/* 递归
 * 不断的填充索引A[cur]的值 直到cur==n
 */
void permutation1(int n, int *A, int cur)
{
    int i,j,c1,c2;
    if(cur==n)
    {
        for(i=0; i<n; i++)
        {
            printf("%c ", P[A[i]]);
        }
        printf("\n");
        return;
    }

    for(i=0;i<n;i++) if( i==0 || P[i]!=P[i-1] )
    {
        c1=c2=0;
        for(j=0;j<cur;j++) if(P[i]==P[A[j]]) c1++; // 已经出现次数
        for(j=0;j<n && c1>=c2; j++)   if(P[i]==P[j]) c2++; // 是否至少出现了c1+1次
        if( c1 < c2 )
        {
            A[cur]=i;
            permutation1(n, A, cur+1);
        }
    }
}

非递归算法

/* 非递归 */
void permutation2(int n, int *A)
{
    int i, j;
    /*
     * 从字典序最小[升序排列] 逐步增大 直到 字典序最大[降序排列]
     * 算法:
     *  初始态 值升序排列
     *  从右往左找到i  使得 A[i] > A[i+1] && A[i+1:]降序  < i==-1 A[0:] 已经是降序排列了 算法结束 >
     *  从右往左找到j  使得 A[i] < A[j];
     *  逆序 A[i+1:]
     */
    while(1)
    {
        //打印
        for(i=0; i<n; i++)
            printf("%c ", P[A[i]]);
        printf("\n");

        //find 临界点A[i]
        i=n-2;
        while( i>=0 && A[i]>=A[i+1]) i--;

        if(i==-1) return;

        //find 比A[i]大的数字A[j]
        j=n-1;
        while(A[i]>=A[j]) j--;

        // exchange
        A[i] ^= A[j]; A[j] ^= A[i]; A[i] ^= A[j];

        // reversed A[i+1:]
        for(i=i+1, j=n-1; i<j; i++,j--)
        { A[i] ^= A[j]; A[j] ^= A[i]; A[i] ^= A[j]; }
    }
}

子集

子集–增量构造

/* 子集--增量构造发 */
void subset1(int n, int *A, int cur )
{
    int i, s;
    for(i=0; i<cur; i++) printf("%c ", P[A[i]]);
    printf("\n");

    s = cur ? A[cur-1]+1 : 0;
    for(i=s; i<n; i++)
    {
        A[cur]=i;
        subset1(n, A, cur+1);
    }
}

子集–位向量

void subset2(int n, int *A, int cur)
{
    int i;
    if(cur==n)
    {
        for(i=0; i<n; i++) if(A[i])
            printf("%c ", P[i]);
        printf("\n");
        return;
    }

    A[cur]=0;
    subset2(n, A, cur+1);
    A[cur]=1;
    subset2(n, A, cur+1);
}

子集–二进制

/* 判断n是否出现过 需要初始化memset(HashArray, -1, sizeof(HashArray))
 * HashArray大小应该要大于所有无重复子集的个数
 * */
static int  HashArray[1024];
int hasExisted(int n)
{
    int LEN=1024;
    int k = n%LEN;
    while( HashArray[k]!=-1 && HashArray[k]!=n && k!=(n%LEN-1)%LEN) k=(k+1)%LEN;

    if (k==(n%LEN-1)%LEN){
        return 2; // 数据满了
    }
    if (HashArray[k]==-1) // 没有n
    {
        HashArray[k]=n;
        return 0;
    }
    return 1;
}

/*子集--二进制*/
void subset3(int n, int *A)
{
    int i=0, j=0, code;
    while(i < 1<<n)
    {
        code=1;
        for(j=0; j<n ; j++) if( i & 1<<j )
            code = code*10+A[j];
        switch( hasExisted(code) )
        {
        case 1:
            break;
        case 2:
            printf("数据error!\n");
             return;
        case 0:
            for(j=0; j<n ; j++) if( i & 1<<j )
                printf("%c ", P[A[j]]);
            printf("\n");
            break;
        }
        i++;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值