Công cụ tìm kiếm hậu quả chung dài nhất nhanh nhất


11

Nhiệm vụ của bạn là giải quyết vấn đề Hậu quả chung dài nhất cho n chuỗi có độ dài 1000.

Một giải pháp có giá trị cho vấn đề LCS cho hai hay nhiều chuỗi S 1 , S ... n là bất kỳ chuỗi T có độ dài tối đa như vậy mà nhân vật của T xuất hiện trong tất cả các S i , theo thứ tự như trong T .

Lưu ý rằng T không phải là một chuỗi con của S i .

Chúng tôi đã giải quyết vấn đề này với số lượng mã ngắn nhất . Lần này, kích thước không thành vấn đề.

Thí dụ

Các chuỗi axbyczxaybzccó 8 chuỗi chung có độ dài 3:

abc abz ayc ayz xbc xbz xyc xyz

Bất kỳ trong số này sẽ là một giải pháp hợp lệ cho vấn đề LCS.

Chi tiết

Viết một chương trình đầy đủ giải quyết vấn đề LCS, như đã giải thích ở trên, tuân thủ các quy tắc sau:

  • Đầu vào sẽ bao gồm hai hoặc nhiều chuỗi có độ dài 1000, bao gồm các ký tự ASCII với các điểm mã trong khoảng từ 0x30 đến 0x3F.

  • Bạn phải đọc đầu vào từ STDIN.

    Bạn có hai lựa chọn cho định dạng đầu vào:

    • Mỗi chuỗi (bao gồm cả chuỗi cuối cùng) được theo sau bởi một dòng cấp.

    • Các chuỗi được nối với nhau không có dấu phân cách và không có dòng cấp dữ liệu.

  • Số lượng chuỗi sẽ được truyền dưới dạng tham số dòng lệnh cho chương trình của bạn.

  • Bạn phải viết đầu ra, tức là, bất kỳ một trong những giải pháp hợp lệ cho LCS, sang STDOUT, theo sau là một dòng cấp.

  • Ngôn ngữ bạn chọn phải có trình biên dịch / phiên dịch miễn phí (như bia) cho hệ điều hành của tôi (Fedora 21).

  • Nếu bạn yêu cầu bất kỳ cờ biên dịch hoặc trình thông dịch cụ thể, vui lòng đề cập đến nó trong bài viết của bạn.

Chấm điểm

Tôi sẽ chạy mã của bạn với các chuỗi 2, 3, v.v. cho đến khi mất hơn 120 giây để in một giải pháp hợp lệ. Điều này có nghĩa là bạn có 120 giây cho mỗi giá trị của n .

Số lượng chuỗi cao nhất mà mã của bạn kết thúc đúng lúc là điểm số của bạn.

Trong trường hợp số điểm bị ràng buộc là n , bài nộp đã giải quyết vấn đề cho chuỗi n trong thời gian ngắn nhất sẽ được tuyên bố là người chiến thắng.

Tất cả các lần gửi sẽ được tính giờ trên máy của tôi (Intel Core i7-3770, RAM 16 GiB, không trao đổi).

Các n dây của (n-1) lần thứ thử nghiệm sẽ được tạo ra bằng cách gọi rand n(và tước linefeeds, nếu có yêu cầu), nơi randđược xác định như sau:

rand()
{
    head -c$[500*$1] /dev/zero |
    openssl enc -aes-128-ctr -K 0 -iv $1 |
    xxd -c500 -ps |
    tr 'a-f' ':-?'
}

Khóa nằm 0trong đoạn mã trên, nhưng tôi bảo lưu quyền thay đổi nó thành giá trị không được tiết lộ nếu tôi nghi ngờ bất kỳ ai (một phần) mã hóa đầu ra.


Chúng ta có thể ném ngoại lệ?
HyperNeutrino

@JamesSmith Miễn là đầu ra là chính xác, chắc chắn.
Dennis

Vì tôi đang đọc với bộ đệm, tôi có thể ném ioexception public static void main(...)không?
HyperNeutrino

@JamesSmith Tôi không thực sự biết Java, vì vậy tôi không biết đó là gì, nhưng đừng lo lắng về các ngoại lệ.
Dennis

4
@JamesSmith Vì độ dài mã không quan trọng đối với thử thách này, bạn có thể đơn giản nắm bắt các ngoại lệ không?
Reto Koradi

Câu trả lời:


5

C, n = 3 trong ~ 7 giây

Thuật toán

Thuật toán là một khái quát trực tiếp của giải pháp lập trình động tiêu chuẩn cho ncác chuỗi. Đối với 2 chuỗi AB, tái phát tiêu chuẩn trông như thế này:

L(p, q) = 1 + L(p - 1, q - 1)           if A[p] == B[q]
        = max(L(p - 1, q), L(p, q - 1)) otherwise

Đối với 3 dây A, B, Ctôi sử dụng:

L(p, q, r) = 1 + L(p - 1, q - 1, r - 1)                          if A[p] == B[q] == C[r]
           = max(L(p - 1, q, r), L(p, q - 1, r), L(p, q, r - 1)) otherwise

Mã thực hiện logic này cho các giá trị tùy ý của n.

Hiệu quả

Độ phức tạp của mã của tôi là O (s ^ n), với sđộ dài của chuỗi. Dựa trên những gì tôi tìm thấy, có vẻ như vấn đề là NP-Complete. Vì vậy, trong khi thuật toán được đăng rất không hiệu quả đối với các giá trị lớn hơn n, thì thực tế có thể không thể thực hiện tốt hơn một cách ồ ạt. Điều duy nhất tôi thấy là một số phương pháp cải thiện hiệu quả cho bảng chữ cái nhỏ. Vì bảng chữ cái nhỏ vừa phải ở đây (16), điều đó có thể dẫn đến một sự cải tiến. Tôi vẫn dự đoán rằng không ai sẽ tìm thấy một giải pháp hợp pháp cao hơn n = 4trong 2 phút và có n = 4vẻ đầy tham vọng.

Tôi đã giảm mức sử dụng bộ nhớ trong lần triển khai ban đầu để nó có thể xử lý n = 4đủ thời gian. Nhưng nó chỉ tạo ra độ dài của chuỗi chứ không phải chính chuỗi đó. Kiểm tra lịch sử sửa đổi của bài đăng này để xem mã đó.

Do các vòng lặp trên ma trận n chiều đòi hỏi nhiều logic hơn các vòng lặp cố định, nên tôi đang sử dụng một vòng lặp cố định cho kích thước thấp nhất và chỉ sử dụng logic vòng lặp chung cho các kích thước còn lại.

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

#define N_MAX 4

int main(int argc, char* argv[]) {
    int nSeq = argc - 1;
    if (nSeq > N_MAX) {
        nSeq = N_MAX;
    }

    char** seqA = argv + 1;

    uint64_t totLen = 1;
    uint64_t lenA[N_MAX] = {0};
    uint64_t offsA[N_MAX] = {1};
    uint64_t offsSum = 0;
    uint64_t posA[N_MAX] = {0};

    for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
        lenA[iSeq] = strlen(seqA[iSeq]);
        totLen *= lenA[iSeq] + 1;

        if (iSeq + 1 < nSeq) {
            offsA[iSeq + 1] = totLen;
        }

        offsSum += offsA[iSeq];
    }

    uint16_t* mat = calloc(totLen, 2);
    uint64_t idx = offsSum;

    for (;;) {
        for (uint64_t pos0 = 0; pos0 < lenA[0]; ++pos0) {
            char firstCh = seqA[0][pos0];
            int isSame = 1;
            uint16_t maxVal = mat[idx - 1];

            for (int iSeq = 1; iSeq < nSeq; ++iSeq) {
                char ch = seqA[iSeq][posA[iSeq]];
                isSame &= (ch == firstCh);

                uint16_t val = mat[idx - offsA[iSeq]];
                if (val > maxVal) {
                    maxVal = val;
                }
            }

            if (isSame) {
                mat[idx] = mat[idx - offsSum] + 1;
            } else {
                mat[idx] = maxVal;
            }

            ++idx;
        }

        idx -= lenA[0];

        int iSeq = 1;
        while (iSeq < nSeq && posA[iSeq] == lenA[iSeq] - 1) {
            posA[iSeq] = 0;
            idx -= (lenA[iSeq] - 1) * offsA[iSeq];
            ++iSeq;
        }
        if (iSeq == nSeq) {
            break;
        }

        ++posA[iSeq];
        idx += offsA[iSeq];
    }

    int score = mat[totLen - 1];

    char* resStr = malloc(score + 1);
    resStr[score] = '\0';

    for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
        posA[iSeq] = lenA[iSeq] - 1;
    }

    idx = totLen - 1;
    int resIdx = score - 1;

    while (resIdx >= 0) {
        char firstCh = seqA[0][posA[0]];
        int isSame = 1;
        uint16_t maxVal = mat[idx - 1];
        int maxIdx = 0;

        for (int iSeq = 1; iSeq < nSeq; ++iSeq) {
            char ch = seqA[iSeq][posA[iSeq]];
            isSame &= (ch == firstCh);

            uint16_t val = mat[idx - offsA[iSeq]];
            if (val > maxVal) {
                maxVal = val;
                maxIdx = iSeq;
            }
        }

        if (isSame) {
            resStr[resIdx--] = firstCh;
            for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
                --posA[iSeq];
            }
            idx -= offsSum;
        } else {
            --posA[maxIdx];
            idx -= offsA[maxIdx];
        }
    }

    free(mat);

    printf("score: %d\n", score);
    printf("%s\n", resStr);

    return 0;
}

Hướng dẫn chạy

Chạy:

  • Lưu mã trong một tập tin, ví dụ lcs.c.
  • Biên dịch với các tùy chọn tối ưu hóa cao. Tôi đã sử dụng:

    clang -O3 lcs.c
    

    Trên Linux, tôi sẽ thử:

    gcc -Ofast lcs.c
    
  • Chạy với 2 đến 4 chuỗi được đưa ra dưới dạng đối số dòng lệnh:

    ./a.out axbycz xaybzc
    

    Trích dẫn một lần đối số dòng lệnh nếu cần thiết, vì bảng chữ cái được sử dụng cho các ví dụ chứa các ký tự đặc biệt shell.

Các kết quả

test2.shtest3.shlà các chuỗi thử nghiệm từ Dennis. Tôi không biết kết quả chính xác, nhưng đầu ra có vẻ hợp lý nhất.

$ ./a.out axbycz xaybzc
score: 3
abc

$ time ./test2.sh 
score: 391
16638018802020>3??3232270178;47240;24331395?<=;99=?;178675;866002==23?87?>978891>=9<6<9381992>>7030829?255>6804:=3>:;60<9384=081;0:?66=51?0;5090724<85?>>:2>7>3175?::<9199;5=0:494<5<:7100?=95<91>1887>33>67710==;48<<327::>?78>77<6:2:02:<7=5?:;>97<993;57=<<=:2=9:8=118563808=962473<::8<816<464<1==925<:5:22?>3;65=>=;27?7:5>==3=4>>5>:107:20575347>=48;<7971<<245<??219=3991=<96<??735698;62?<98928

real  0m0.012s
user  0m0.008s
sys   0m0.003s

$ time ./test3.sh 
score: 269
662:2=2::=6;738395=7:=179=96662649<<;?82?=668;2?603<74:6;2=04=>6;=6>=121>1>;3=22=3=3;=3344>0;5=7>>7:75238;559133;;392<69=<778>3?593?=>9799<1>79827??6145?7<>?389?8<;;133=505>2703>02?323>;693995:=0;;;064>62<0=<97536342603>;?92034<?7:=;2?054310176?=98;5437=;13898748==<<?4

real  0m7.218s
user  0m6.701s
sys   0m0.513s

Xin lỗi nếu điều đó không rõ ràng, nhưng bạn phải in LCS, không chỉ độ dài của nó.
Dennis

@Dennis tôi thấy. Một số tối ưu hóa của tôi là vô ích sau đó. Tôi sẽ phải quay lại phiên bản lưu trữ ma trận đầy đủ để tôi có thể xây dựng lại chuỗi. Điều đó sẽ không thể chạy trong n = 4, nhưng vẫn hoàn thành dưới 10 giây cho n = 3. Tôi nghĩ rằng tôi đã ở khoảng 6-7 giây khi tôi vẫn còn ma trận đầy đủ.
Reto Koradi

Một lần nữa xin lỗi. Câu hỏi không rõ lắm về điều này ... Khi bạn đăng sản phẩm của mình, tôi sẽ có thể so sánh nó với BrainSteel. Độ dài báo cáo chương trình của bạn vượt quá độ dài đầu ra của anh ta bằng 5 cho n = 2. Nhân tiện, tôi phải xác định N_MAXlà macro và thêm cờ trình biên dịch -std=c99để biên dịch mã của bạn với GCC.
Dennis

@Dennis Không có vấn đề. Nó nói rằng giải pháp "là một chuỗi", vì vậy điều đó đã đủ rõ ràng. Tôi hầu như chỉ sử dụng C ++, vì vậy tôi không bao giờ chắc chắn những gì được phép trong C. Mã này bắt đầu là C ++, nhưng một khi tôi nhận ra rằng tôi không thực sự sử dụng bất kỳ tính năng C ++ nào, tôi đã chuyển nó sang C. clang trên máy Mac của tôi hài lòng với nó, nhưng có lẽ nó sử dụng một phiên bản C khác theo mặc định, hoặc chỉ nhẹ nhàng hơn.
Reto Koradi

1
@Dennis Ok, tôi đã thêm logic truy nguyên để tôi có thể tạo chuỗi. Mất khoảng 7 giây bây giờ cho n = 3.
Reto Koradi

3

Câu trả lời này hiện đang bị hỏng do một lỗi. Khắc phục sớm ...

C, 2 chuỗi trong ~ 35 giây

Đây là một công việc đang tiến triển (như thể hiện bởi sự lộn xộn khủng khiếp), nhưng hy vọng nó sẽ đưa ra một số câu trả lời hay!

Mật mã:

#include "stdlib.h"
#include "string.h"
#include "stdio.h"
#include "time.h"

/* For the versatility */
#define MIN_CODEPOINT 0x30
#define MAX_CODEPOINT 0x3F
#define NUM_CODEPOINT (MAX_CODEPOINT - MIN_CODEPOINT + 1)
#define CTOI(c) (c - MIN_CODEPOINT)

#define SIZE_ARRAY(x) (sizeof(x) / sizeof(*x))

int LCS(char** str, int num);
int getshared(char** str, int num);
int strcount(char* str, char c);

int main(int argc, char** argv) {
    char** str = NULL;
    int num = 0;
    int need_free = 0;
    if (argc > 1) {
        str = &argv[1];
        num = argc - 1;
    }
    else {
        scanf(" %d", &num);
        str = malloc(sizeof(char*) * num);
        if (!str) {
            printf("Allocation error!\n");
            return 1;
        }

        int i;
        for (i = 0; i < num; i++) {
            /* No string will exceed 1000 characters */
            str[i] = malloc(1001);
            if (!str[i]) {
                printf("Allocation error!\n");
                return 1;
            }

            scanf(" %1000s", str[i]);

            str[i][1000] = '\0';
        }

        need_free = 1;
    }

    clock_t start = clock();

    /* The call to LCS */
    int size = LCS(str, num);

    double dt = ((double)(clock() - start)) / CLOCKS_PER_SEC;
    printf("Size: %d\n", size);
    printf("Elapsed time on LCS call: %lf s\n", dt);

    if (need_free) {
        int i;
        for (i = 0; i < num; i++) {
            free(str[i]);
        }
        free(str);
    }

    return 0;
}

/* Some terribly ugly global variables... */
int depth, maximum, mapsize, lenmap[999999][2];
char* (strmap[999999][20]);
char outputstr[1000];

/* Solves the LCS problem on num strings str of lengths len */
int LCS(char** str, int num) {
    /* Counting variables */
    int i, j;

    if (depth + getshared(str, num) <= maximum) {
        return 0;
    }

    char* replace[num];
    char match;
    char best_match = 0;
    int earliest_set = 0;
    int earliest[num];
    int all_late;
    int all_early;
    int future;
    int best_future = 0;
    int need_future = 1;

    for (j = 0; j < mapsize; j++) {
        for (i = 0; i < num; i++)
            if (str[i] != strmap[j][i])
                break;
        if (i == num) {
            best_match = lenmap[j][0];
            best_future = lenmap[j][1];
            need_future = 0;
            if (best_future + depth < maximum || !best_match)
                goto J;
            else {
                match = best_match;
                goto K;
            }
        }
    }

    for (match = MIN_CODEPOINT; need_future && match <= MAX_CODEPOINT; match++) {

    K:
        future = 1;
        all_late = earliest_set;
        all_early = 1;
        for (i = 0; i < num; replace[i++]++) {
            replace[i] = strchr(str[i], match);
            if (!replace[i]) {
                future = 0;
                break;
            }

            if (all_early && earliest_set && replace[i] - str[i] > earliest[i])
                all_early = 0;
            if (all_late && replace[i] - str[i] < earliest[i])
                all_late = 0;
        }
        if (all_late) {
            future = 0;
        }

    I:
        if (future) {
            if (all_early || !earliest_set) {
                for (i = 0; i < num; i++) {
                    earliest[i] = (int)(replace[i] - str[i]);
                }
            }

            /* The recursive bit */
            depth++;
            future += LCS(replace, num);
            depth--;

            best_future = future > best_future ? (best_match = match), future : best_future;
        }
    }

    if (need_future) {
        for (i = 0; i < num; i++)
            strmap[mapsize][i] = str[i];
        lenmap[mapsize][0] = best_match;
        lenmap[mapsize++][1] = best_future;
    }

J:
    if (depth + best_future >= maximum) {
        maximum = depth + best_future;
        outputstr[depth] = best_match;
    }

    if (!depth) {
        mapsize = 0;
        maximum = 0;
        puts(outputstr);
    }

    return best_future;
}

/* Return the number of characters total (not necessarily in order) that the strings all share */
int getshared(char** str, int num) {
    int c, i, tot = 0, min;
    for (c = MIN_CODEPOINT; c <= MAX_CODEPOINT; c++) {
        min = strcount(str[0], c);
        for (i = 1; i < num; i++) {
            int count = strcount(str[i], c);
            if (count < min) {
                min = count;
            }
        }
        tot += min;
    }

    return tot;
}

/* Count the number of occurrences of c in str */
int strcount(char* str, char c) {
    int tot = 0;
    while ((str = strchr(str, c))) {
        str++, tot++;
    }
    return tot;
}

Hàm liên quan thực hiện tất cả các tính toán LCS là hàm LCS. Đoạn mã trên sẽ gọi thời gian riêng của nó tới hàm này.

Lưu dưới dạng main.cvà biên dịch với:gcc -Ofast main.c -o FLCS

Mã có thể được chạy hoặc với các đối số dòng lệnh hoặc thông qua stdin. Khi sử dụng stdin, nó mong đợi một số chuỗi được theo sau bởi chính các chuỗi.

~ Me$ ./FLCS "12345" "23456"
2345
Size: 4
Elapsed time on LCS call: 0.000056 s

Hoặc là:

~ Me$ ./FLCS
6 
2341582491248123139182371298371239813
2348273123412983476192387461293472793
1234123948719873491234120348723412349
1234129388234888234812834881423412373
1111111112341234128469128377293477377
1234691237419274737912387476371777273
1241231212323
Size: 13
Elapsed time on LCS call: 0.001594 s

Trên hộp Mac OS X có Intel Core i7 1.7Ghz và trường hợp thử nghiệm mà Dennis cung cấp, chúng tôi nhận được đầu ra sau cho 2 chuỗi:

16638018800200>3??32322701784=4240;24331395?<;=99=?;178675;866002==23?87?>978891>=9<66=381992>>7030829?25265804:=3>:;60<9384=081;08?66=51?0;509072488>2>924>>>3175?::<9199;330:494<51:>748571?153994<45<??20>=3991=<962508?7<2382?489
Size: 386
Elapsed time on LCS call: 33.245087 s

Cách tiếp cận rất giống với cách tiếp cận của tôi với thử thách trước đó, ở đây . Ngoài việc tối ưu hóa trước đó, giờ đây chúng tôi kiểm tra tổng số ký tự được chia sẻ giữa các chuỗi ở mỗi lần đệ quy và thoát sớm nếu không có cách nào để có được chuỗi con dài hơn so với những gì đã tồn tại.

Hiện tại, nó xử lý 2 chuỗi ổn nhưng có xu hướng gặp sự cố nhiều hơn. Nhiều cải tiến và một lời giải thích tốt hơn sẽ đến!


1
Tôi nghĩ rằng tôi đã bỏ lỡ một cái gì đó. Với 2 chuỗi không phải đây là một vấn đề lập trình động cổ điển cần khoảng 1000 ^ 2 bước để giải quyết? Nói cách khác, một phần của một giây.

@Lembik Vâng, nó nên. Phương pháp này được xây dựng để xử lý nhiều hơn 2 chuỗi, nhưng cuối cùng nó được chia tỷ lệ quá kém với độ dài chuỗi để có kết quả tốt. Tôi đã có thêm nhiều mánh khóe nữa, và nếu bất kỳ trong số chúng thực sự hoạt động ... Mọi thứ sẽ được cải thiện rất nhiều.
BrainSteel

Dường như có một vấn đề ở đâu đó. Mã của @ RetoKoradi tìm thấy một chuỗi con chung hợp lệ có độ dài 391 cho n = 2, trong khi mã của bạn báo cáo độ dài 386 và in một chuỗi có độ dài 229.
Dennis

@Dennis Umm ... Vâng, vâng nó ... Ôi trời ơi. Vâng, điều này là xấu hổ. Tôi đang làm việc với nó :) Tôi sẽ chỉnh sửa câu trả lời để phản ánh lỗi.
BrainSteel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.