ACM 擅长排列的小明

擅长排列的小明

时间限制:1000 ms  |  内存限制:65535 KB
难度:4
 
描述
小明十分聪明,而且十分擅长排列计算。比如给小明一个数字5,他能立刻给出1-5按字典序的全排列,如果你想为难他,在这5个数字中选出几个数字让他继续全排列,那么你就错了,他同样的很擅长。现在需要你写一个程序来验证擅长排列的小明到底对不对。
 
输入
第一行输入整数N(1<N<10)表示多少组测试数据,
每组测试数据第一行两个整数 n m (1<n<9,0<m<=n)
输出
在1-n中选取m个字符进行全排列,按字典序全部输出,每种排列占一行,每组数据间不需分界。如样例
样例输入
2
3 1
4 2
样例输出
1
2
3
12
13
14
21
23
24
31
32
34
41
42
43

看到此题首先想到的使用递归方法实现,可以画出递归树结构,难点在于,利用深度遍历的同时,还要考虑到下一次遍历的数值不能和原来的重复。变换成树结构的话,子节点的数值不能与父节点及其父节点(祖辈节点)的数值相同。我们需要有一个数组结构来保存其祖辈节点。我首先使用的方法是在每一个节点都构造一个二维数值,利用二维数值的逻辑与或来保存父辈节点。但是还要考虑输出情况,输出是要从上往下的。所以我构造了一个树结构来还原这个递归遍历过程。
下面是我的代码:
#include <stdio.h>
#include <stdlib.h>

struct Node
{
    int data;
    int ids;// 保存祖辈节点
    Node* parent;// 父节点用于递归输出
};

void outPut(Node* node)
{
    if(node->parent != NULL)
        outPut(node->parent);
    if(node->data != 0)
        printf("%d", node->data);
}

void loop(Node* node, int n, int m)
{
    int count = 1, i = 1;
    if(m < 1)
    {
        outPut(node);
        printf("\n");
        return;
    }

    for(;count <= n;count++)
    {    
        if((node->ids & (1 << count)) != 0)
        {
            Node* newNode = (Node*)malloc(sizeof(Node));
            newNode->ids = node->ids & (~(1 << count));
            newNode->parent = node;
            newNode->data = count;
            loop(newNode, n, m-1);
        }
    }
}

int main()
{
    int N = 0;
    scanf("%d", &N);
    while(N--)
    {
        int n = 0, m = 0, count = 1, i = 0;
        scanf("%d%d", &n, &m);
        Node* node = (Node*)malloc(sizeof(Node));
        node->ids = 0xFFFFFFFF;
        node->data = 0;
        node->parent = NULL;
        loop(node, n, m);
    }
    return 1;
}

 

程序虽然速度还可以,但是占用空间较大。使用了树结构。

然后我看了下最优代码,发现可优化的空间非常大。

首先,保存祖辈节点的结构使用了两个数组来代替。一个used数组,用来记录使用过的数值(也就是祖辈节点),用过的键值对应的是1,没有是0;一个char数组,用来保存祖辈节点的顺序性(如 “12” “21”)。

然后在递归遍历的同时,反复使用这两个数组记录祖辈节点,而不是每次递归到一个节点就记录一次。

仔细想想,递归常常跟树结构相伴而生。树结构如果只需要一次遍历的话,只依靠递归就可以解决,但是递归不具有重现性,因为通过函数栈push到内存空间,然后pop后里面的所有参数和局部变量就不存在了。而我们自行构造的树结构可以保存在堆空间里,可以重现遍历操作。

本题的最优解,就是最大程度的利用了递归方法,考略到需要适度的重现其祖父节点,就添加了两个全局数组变量。一定程度上弥补了递归的不可重现性。但是,也并没有实现完全可重现,全局变量需要高度重用。我实现的程序虽然占用了大量空间,但是却完整保留了递归的遍历过程。如果说给出任意一个节点,我就可以重现出关于它的遍历过程。

就本题来讲,当然是追求最快的速度和最小的空间。完全不用完整保存遍历过程,所以见招拆招才是最有效的。

最优解:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
int n,m,used[10];
char ans[11];
void dfs(int x,int num)
{
    if(num==m-1)
    {
        ans[m]='\0';
        printf("%s\n",ans);
        return;
    }
    int i;
    for(i=1;i<=n;i++)
    {
        if(!used[i])
        {
            used[i]=1;
            ans[num+1]=i+'0';
            dfs(i,num+1);
            used[i]=0;
        }
    }
}
int main()
{
    int i,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
        {
            memset(used,0,sizeof(used));
            used[i]=1;
            ans[0]=i+'0';
            dfs(i,0);
        }
    }
    return 0;
} 

 

posted @ 2015-06-07 16:15  sdlwlxf  阅读(922)  评论(0编辑  收藏  举报