查找组和字母的组合

时间:2015-12-30 08:41:35

标签: c combinations permutation letter

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

例如,该群组的解决方案: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.");
}

3 个答案:

答案 0 :(得分:2)

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

要么改变:

char group[sum][2];

为:

char group[sum][3];

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

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

答案 1 :(得分:1)

您的计划有几个问题:

  • 您使用基于1的索引,这会导致混淆并导致引用超出其定义目的的数组和子数组。
  • 使用fscanf说明符解析输入%s:这是不安全的,并且会为每个输入写入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函数处理的,以保持一致性。

上面的代码解决了我的笔记本电脑上n = 50 小于0.002秒的指定输入问题。它会打印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可以使用permutation而不是1来呼叫0permutation可以简化为buffer永远不能0

答案 2 :(得分:0)

您的新代码存在一些问题:

  • 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;
}