【问题标题】:Find combination of groups and letters查找组和字母的组合
【发布时间】:2016-04-04 05:06:56
【问题描述】:

我必须找到一组字母的组合,第一组中的第二个字母应该与第二组中的第一个字母相同,等等。

例如,该组的解决方案:AA, CB, AC, BA, BD, DB 这是:CB, BD, DB, BA, AA, AC

到目前为止,我有这段代码,它可以工作,但如果有很多组,计算需要很长时间。我需要让它更有效率。

在输入文件中,有这个输入

10
C D
B C
B B
B B
D B
B B
C A
A B
B D
D C

我的代码

#include <stdio.h>
#include <stdlib.h>

void permutation(char group[][2], int buffer, int sum) {
    int i, j;
    char temp;

    if (buffer == sum && group[1][1] == group[sum][2]) {
        for (i = 1; i < sum; i++)
            if (group[i][2] != group[i+1][1]) break;

        if (i == sum) {
            FILE *output;
            output = fopen("output.txt", "a");
            for (j = 1; j <= sum; j++) {
                fprintf(output, "%c %c\n", group[j][1], group[j][2]);
            }
            exit(1);
        }
    } else {
        for (i = buffer; i <= sum; i++) {
            temp = group[buffer][1];
            group[buffer][1] = group[i][1];
            group[i][1] = temp;
            temp = group[buffer][2];
            group[buffer][2] = group[i][2];
            group[i][2] = temp;

            permutation(group, buffer + 1, sum);

            temp = group[buffer][1];
            group[buffer][1] = group[i][1];
            group[i][1] = temp;
            temp = group[buffer][2];
            group[buffer][2] = group[i][2];
            group[i][2] = temp;
        }
    }
}

int main() {
    FILE *input;

    input = fopen("input.txt", "r");

    int sum, i;

    fscanf(input, "%d", &sum);

    char group[sum][2];

    for (i = 1; i <= sum; i++) {
        fscanf(input, "%s", &group[i][1]);
        fscanf(input, "%s", &group[i][2]);
    }

    permutation(group, 1, sum);
}

编辑所以我对我的程序进行了一些更改(感谢您的帮助,我对编程很陌生,所以我很抱歉出现错误),我不再使用排列,我'我只是寻找路径。它运作良好,但现在我的输入有 100000 个组,并且再次需要很多时间(大约 2 小时,我需要在 1 小时内完成)。我可能不得不再次以其他方式这样做xD有什么想法吗?

#include <stdio.h>

int find(char group[][2], int buffer, int sum, int path[]) {
    int i, j;

    for (i = 0; i < sum; i++) {
        for (j = 0; j < buffer; j++)
            if (path[j] == i)
                break;
        if (buffer == 0 ||
            (group[path[buffer-1]][1] == group[i][0] && buffer == j)) {
            printf("%d\n", buffer); // just for me to know what program is currently computing
            path[buffer] = i;
            find(group, buffer + 1, sum, path);
            if (path[sum-1] != 0)
                return;
        }
    }
}

int main() {
    FILE *input = fopen("input.txt", "r");

    if (input != NULL) {
        int sum, i;

        fscanf(input, "%d", &sum);
        char group[sum][2];
        int path[sum];

        for (i = 0; i < sum; i++)
            fscanf(input, " %c %c", &group[i][0], &group[i][1]);
        for (i = 0; i < sum;i++)
            path[i] = 0;

        find(group, 0, sum, path);

        FILE *output = fopen("output.txt", "a");
        for (i = 0; i < sum; i++)
            fprintf(output, "%c %c\n", group[path[i]][0], group[path[i]][1]);
    } else
        printf("Input file was not found.");
}

【问题讨论】:

  • 请停止从答案和 cmets 建议中逐步编辑发布的代码,这会使整个讨论不一致。
  • 我建议您共享所有必要的代码,以便可以运行程序并测试性能。您至少应该尽最大努力让我们更容易提供帮助。您的问题不是编码问题,而是性能问题;这是一个编码问答网站,而不是性能网站。完成测试需要多长时间?
  • 这里你输入了 50 对:50 F C A F E E E C E F E B F F E E E E E E E E B F F F C F E A A E E E E E E E E B C A E C E C E E B C E E E E F E F E C A D E E E D A E B C C E E E E F A A C E E B D我认为它应该以其他方式编程,有人可以告诉我一些建议如何以其他方式编程吗?
  • 你能发布一个指向这个大测试文件的链接吗?

标签: c combinations permutation letter


【解决方案1】:

在 C 数组中,索引从 0 开始,因此大小为 N 的数组具有从 0N-1 的有效索引。在上面的代码中,您正在访问数组group 越界,因为它的大小为2(因此有效索引为01),但您正在尝试访问索引1 和@987654329 @。

任一变化:

char group[sum][2];

到:

char group[sum][3];

或使用索引0/1 而不是1/2

另请注意,您的代码缺少错误检查,例如打电话给fopen

【讨论】:

  • 我的功能没有问题。我有计算时间的问题。如果有超过 50 个组,则需要数小时来计算(有 50 个!组合)。我需要在几分钟内完成
  • 实际上你确实有一个“功能问题” - 你的代码有严重的错误,因此会表现出未定义的行为,并且可能会给出不正确的结果。在尝试优化代码之前,您需要修复代码。
  • 在这种情况下,您应该适当地更新您的问题,否则您将浪费其他人的时间。 (另外,你怎么知道它仍然需要“数小时来计算”?你只在最后两分钟内应用了错误修复?)。
  • 您仍然需要修复 main() 中的循环 (i = 0; i
  • group 矩阵中的行和列使用基于0 的索引。还可以使用 fscanf(input, " %c", &amp;group[i][1]); 一次读取 1 个字符,忽略空格并且 not 将空字节存储在子数组的末尾之外。
【解决方案2】:

你的程序有几个问题:

  • 您使用基于 1 的索引,这会导致混淆并导致引用数组和子数组超出其定义的范围。
  • 您使用 fscanf 说明符解析输入:这是不安全的,将为您的每个输入写入 2 个字节,写入超出每个子数组的末尾和最后一个数组的末尾。李>

您已经知道如何解决这些问题,最好使用基于 0 的索引

您的算法非常无效,复杂O(n!),因为您枚举了所有可能的排列并仅检查完整排列的有效性。您可以通过仅枚举已经验证其初始元素的约束的排列来显着提高性能。复杂度大大降低,仍然是二次方,但n 非常小。

这是您的代码的修改版本:

#include <stdio.h>

int permutation(char group[][2], int buffer, int sum) {
    if (buffer == sum)
        return group[sum-1][1] == group[0][0];

    for (int i = buffer; i < sum; i++) {
        if (group[buffer-1][1] == group[i][0]) {
            char temp = group[buffer][0];
            group[buffer][0] = group[i][0];
            group[i][0] = temp;
            temp = group[buffer][1];
            group[buffer][1] = group[i][1];
            group[i][1] = temp;

            if (permutation(group, buffer + 1, sum))
                return 1;

            temp = group[buffer][0];
            group[buffer][0] = group[i][0];
            group[i][0] = temp;
            temp = group[buffer][1];
            group[buffer][1] = group[i][1];
            group[i][1] = temp;
        }
    }
    return 0;
}

int main(void) {
    FILE *input = fopen("input.txt", "r");
    int sum, i;

    if (input != NULL) {
        if (fscanf(input, "%d", &sum) != 1 || sum <= 0) {
            printf("invalid number of pairs\n");
            fclose(input);
            return 1;
        }

        char group[sum][2];

        for (i = 0; i < sum; i++) {
            if (fscanf(input, " %c %c", &group[i][0], &group[i][1]) != 2) {
                printf("incorrect input for pair number %d\n", i);
                fclose(input);
                return 1;
            }
        }
        fclose(input);
        if (permutation(group, 1, sum)) {
            FILE *output = fopen("output.txt", "a");
            if (output == NULL) {
                printf("cannot open output file\n");
                return 2;
            }
            for (i = 0; i < sum; i++) {
                fprintf(output, "%c %c\n", group[i][0], group[i][1]);
            }
            fclose(output);
            return 0;
        } else {
            printf("complete path not found\n");
            return 1;
        }
    }
    printf("cannot open input file\n");
    return 2;
}

我修改了代码的其他方面以提高效率和可重用性:

  • 检查输入的有效性。
  • 递归函数在找到完整路径时停止并返回 1。这使程序无论是否找到路径都可以继续运行。
  • 输出由main 函数处理以保持一致性。

上面的代码在我的笔记本电脑上用 不到 0.002 秒 解决了 n=50 的指定输入问题。它打印F C C E E F F E E E E E E E E E E B B F F E E A A F F C C A A A A E E F F C C E E E E E E E E E E B B C C E E E E F F E E F F F F E E C C E E E E E E B B F F A A D D A A C C C C E E E E E E B B D D F

编辑我意识到,由于您正在寻找一条完全封闭的路径,因此您无需为第一对尝试不同的可能性。 main 可以用1 代替0 调用permutationpermutation 可以简化为buffer 永远不能是0

【讨论】:

  • 谢谢,但我必须使用所有给定的组,他们不能重复:(我也知道,总会有解决方案。
  • @Speedding:我不明白你的限制。我生成完全相同的排列是相同的顺序,加速来自对不可能解决方案的早期修剪。您的意思是身份对不能立即重复并且必须在路径的另一部分之后使用?这很容易添加到算法中。
  • 不,没关系。尝试解决这个输入 6 A A C B A C B A B D D B ... 它会打印出一些废话。
  • @Speedding:我的错,我很自以为是,没有正确评估改变方法的后果:将路径构造成单独的数组需要一个额外的数组来跟踪已使用的对.我恢复了您最初的想法,只是添加了早期修剪,在递归之前检查路径连续性。它仍然非常快,可以正确解决问题。
  • 谢谢。你是最棒的 !! ^^
【解决方案3】:

你的新代码有一些问题:

  • find 被定义为返回 int,但你什么也不返回。你确实没有测试你是否找到了一条完整的路径,完全依赖于至少有一个并且你已经找到它的假设。
  • 您不测试路径闭合。您可能会偶然发现一条闭合路径,但也可能产生一条未闭合路径。
  • 使用 2 个循环来查找未使用的对的效率低于使用临时数组 used[sum]
  • 第一对总是第一个,因此您可以稍微简化find 函数。

这是一个改进的版本:

#include <stdio.h>

int find(char group[][2], int buffer, int sum, int path[], unsigned char used[]) {
    int i;
    char last = group[path[buffer-1]][1];

    if (buffer == sum)
        return last == group[0][0];

    for (i = 1; i < sum; i++) {
        if (!used[i] && last == group[i][0]) {
            path[buffer] = i;
            used[i] = 1;
            if (find(group, buffer + 1, sum, path, used))
                return 1;
            used[i] = 0;
        }
    }
    return 0;
}

int main() {
    FILE *input = fopen("input.txt", "r");

    if (input != NULL) {
        int sum = 0, i;

        fscanf(input, "%d", &sum);

        char group[sum][2];
        int path[sum];
        unsigned char used[sum];

        for (i = 0; i < sum; i++)
            fscanf(input, " %c %c", &group[i][0], &group[i][1]);

        path[0] = 0;  // always start at first element
        used[0] = 1;
        for (i = 1; i < sum; i++)
            used[i] = 0;

        if (find(group, 1, sum, path, used)) {
            FILE *output = fopen("output.txt", "a");
            for (i = 0; i < sum; i++)
                fprintf(output, "%c %c\n", group[path[i]][0], group[path[i]][1]);
        }
    } else {
        printf("Input file was not found.");
    }
    return 0;
}

编辑:我用你的大输入文件测试了这个新版本:它在我的笔记本电脑上崩溃了。具有permutation 函数的先前版本就像一个魅力,在0.060 秒内生成完整路径。所以有一个完整的路径,这个find函数有问题。

算法之间几乎没有区别:

  • permutation 使用更少的堆栈空间:一个大小为 n*2 (200k) 的自动数组与总大小为 3 个自动数组 n*(sizeof(int) + 3) (700k)。
  • permutation 使用较少的变量,因此递归使用较少的堆栈空间,但两者都可能使用超过 1 MB 的堆栈空间来递归 100000 次。
  • find 进行更多扫描,其中 permutation 交换 group 对并始终直接捕捉下一个。

我在没有递归的情况下重新实现了find,最后让它生成了一条完整的路径。这是不同的,计算时间要长得多,3.5 秒。

对于较大的输入文件,你绝对不应该使用递归,你甚至应该使用malloc从堆中分配数组。

这里是非递归代码,使用堆内存:

#include <stdio.h>
#include <stdlib.h>

int find(const char group[][2], int sum, int path[]) {
    path[0] = 0;
    if (sum <= 1)
        return group[0][1] == group[0][0];

    unsigned char *used = calloc((size_t)sum, sizeof(*used));

    for (int buffer = 1, i = 1;; i++) {
        if (i == sum) {
            --buffer;
            if (buffer == 0) {
                free(used);
                return 0;
            }
            i = path[buffer];
            used[i] = 0;
        } else
        if (!used[i] && group[path[buffer-1]][1] == group[i][0]) {
            path[buffer] = i;
            if (buffer == sum - 1) {
                if (group[i][1] == group[0][0]) {
                    free(used);
                    return 1;
                }
            } else {
                buffer++;
                used[i] = 1;
                i = 0;
            }
        }
    }
}

int main() {
    FILE *input = fopen("input.txt", "r");

    if (input != NULL) {
        int sum = 0, i;

        fscanf(input, "%d", &sum);

        char (*group)[2] = calloc((size_t)sum, sizeof(*group));
        int *path = calloc((size_t)sum, sizeof(*path));

        for (i = 0; i < sum; i++)
            fscanf(input, " %c %c", &group[i][0], &group[i][1]);

        if (find(group, sum, path)) {
            FILE *output = fopen("output.txt", "a");
            for (i = 0; i < sum; i++)
                fprintf(output, "%c %c\n", group[path[i]][0], group[path[i]][1]);
        }
    } else {
        printf("Input file was not found.");
    }
    return 0;
}

【讨论】:

  • 曾经,我希望我是一个像你这样优秀的程序员。谢谢楼主。
  • @Speedding:继续努力,这需要很长时间,不断磨练自己的技能。 42 年后(是的,42 年!),我仍然在学习新事物并每天都感到谦卑。 Stackoverflow 是一个很好的地方,projecteuler.net
  • @Speedding:哎呀!我不知道我的代码是否解决了您系统上的问题,但我的 100k 输入文件失败了。我对此进行了调查并得出了一个强有力的结论:递归使用了太多的堆栈空间来进行递归方法,并且可能还用于自动数组。我用使用堆的非递归版本更新了答案。
猜你喜欢
  • 2011-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
  • 2015-07-20
  • 1970-01-01
  • 2014-07-31
  • 2016-07-11
相关资源
最近更新 更多