Thuật toán tìm kiếm chuỗi con nhanh nhất là gì?


165

OK, vì vậy tôi không có vẻ như một thằng ngốc Tôi sẽ nói rõ hơn vấn đề / yêu cầu:

  • Kim (mẫu) và haystack (văn bản để tìm kiếm) đều là các chuỗi kết thúc null kiểu C. Không có thông tin chiều dài được cung cấp; nếu cần, nó phải được tính toán.
  • Hàm sẽ trả về một con trỏ cho kết quả khớp đầu tiên hoặc NULLnếu không tìm thấy kết quả khớp nào.
  • Trường hợp thất bại không được phép. Điều này có nghĩa là bất kỳ thuật toán nào có yêu cầu lưu trữ không cố định (hoặc hằng số lớn) sẽ cần phải có trường hợp dự phòng cho lỗi phân bổ (và hiệu suất trong chăm sóc dự phòng do đó góp phần vào hiệu suất trong trường hợp xấu nhất).
  • Việc thực hiện là ở C, mặc dù một mô tả tốt về thuật toán (hoặc liên kết đến như vậy) mà không có mã cũng tốt.

... cũng như những gì tôi muốn nói là "nhanh nhất":

  • Xác định O(n)trong đó n= chiều dài haystack. (Nhưng có thể sử dụng các ý tưởng từ các thuật toán thông thường O(nm)(ví dụ: băm) nếu chúng được kết hợp với một thuật toán mạnh hơn để đưa ra O(n)kết quả xác định ).
  • Không bao giờ thực hiện (có thể đo lường được; một vài đồng hồ đôi, if (!needle[1])v.v.) không tệ hơn thuật toán vũ phu ngây thơ, đặc biệt là trên các kim rất ngắn có khả năng là trường hợp phổ biến nhất. (Chi phí tiền xử lý nặng vô điều kiện là không tốt, vì đang cố gắng cải thiện hệ số tuyến tính cho kim bệnh lý với chi phí của kim có khả năng.)
  • Đưa ra một kim và cỏ khô tùy ý, hiệu suất tương đương hoặc tốt hơn (không quá thời gian tìm kiếm dài hơn 50%) so với bất kỳ thuật toán được triển khai rộng rãi nào khác.
  • Ngoài những điều kiện này, tôi còn để lại định nghĩa về kết thúc mở "nhanh nhất". Một câu trả lời tốt sẽ giải thích lý do tại sao bạn xem xét phương pháp bạn đề xuất "nhanh nhất".

Việc triển khai hiện tại của tôi chạy chậm hơn khoảng 10% và nhanh hơn 8 lần (tùy thuộc vào đầu vào) so với triển khai Hai chiều của glibc.

Cập nhật: Thuật toán tối ưu hiện tại của tôi như sau:

  • Đối với kim có chiều dài 1, sử dụng strchr.
  • Đối với các kim có độ dài 2-4, sử dụng các từ máy để so sánh 2-4 byte cùng một lúc như sau: Tải trước kim trong số nguyên 16 hoặc 32 bit với bithifts và chu kỳ byte cũ / byte mới từ haystack trong mỗi lần lặp . Mỗi byte của haystack được đọc chính xác một lần và phát sinh một kiểm tra đối với 0 (cuối chuỗi) và một so sánh 16 hoặc 32 bit.
  • Đối với kim có độ dài> 4, sử dụng thuật toán Hai chiều với bảng dịch chuyển xấu (như Boyer-Moore) chỉ áp dụng cho byte cuối cùng của cửa sổ. Để tránh chi phí khởi tạo bảng 1kb, sẽ là tổn thất ròng đối với nhiều kim có độ dài vừa phải, tôi giữ một mảng bit (32 byte) đánh dấu các mục trong bảng dịch chuyển được khởi tạo. Các bit không được đặt tương ứng với các giá trị byte không bao giờ xuất hiện trong kim, trong đó có thể dịch chuyển toàn bộ chiều dài kim.

Những câu hỏi lớn còn lại trong tâm trí tôi là:

  • Có cách nào để sử dụng tốt hơn bảng dịch chuyển xấu? Boyer-Moore sử dụng nó tốt nhất bằng cách quét ngược (từ phải sang trái) nhưng Two-Way yêu cầu quét từ trái sang phải.
  • Hai thuật toán ứng cử viên khả thi duy nhất tôi tìm thấy cho trường hợp chung (không có điều kiện hiệu năng ngoài bộ nhớ hoặc bậc hai) là Kết hợp hai chiềuChuỗi trên bảng chữ cái được đặt hàng . Nhưng có những trường hợp dễ phát hiện trong đó các thuật toán khác nhau sẽ là tối ưu? Chắc chắn nhiều thuật toán O(m)(trong đó mlà chiều dài kim) trong thuật toán không gian có thể được sử dụng cho m<100hoặc như vậy. Cũng có thể sử dụng các thuật toán là phương pháp bậc hai trong trường hợp xấu nhất nếu có một phép thử dễ dàng đối với các kim chỉ yêu cầu thời gian tuyến tính.

Điểm thưởng cho:

  • Bạn có thể cải thiện hiệu suất bằng cách giả sử kim và cỏ khô đều là UTF-8 được hình thành tốt không? (Với các ký tự có độ dài byte khác nhau, trạng thái được định dạng tốt sẽ áp đặt một số yêu cầu căn chỉnh chuỗi giữa kim và haystack và cho phép dịch chuyển 2-4 byte tự động khi gặp phải byte đầu không khớp. Nhưng những ràng buộc này sẽ mua cho bạn nhiều / bất cứ điều gì ngoài những gì tính toán hậu tố tối đa, dịch chuyển hậu tố tốt, vv đã cung cấp cho bạn các thuật toán khác nhau?)

Lưu ý: Tôi nhận thức rõ về hầu hết các thuật toán ngoài kia, chỉ là chúng không hoạt động tốt như thế nào trong thực tế. Đây là một tài liệu tham khảo tốt để mọi người không tiếp tục cung cấp cho tôi các tài liệu tham khảo về các thuật toán dưới dạng nhận xét / câu trả lời: http://www-igm.univ-mlv.fr/~lecroq/opes/index.html


Có khá nhiều thuật toán tìm kiếm chuỗi được liệt kê trên Thuật toán trên Chuỗi . Bạn có thể muốn mô tả thuật toán nào bạn đã xem xét từ danh sách này.
Greg Hewgill

61
Liên kết đó ở cuối là vàng!
Carlos

4
Tôi không thể tin rằng bạn vẫn chưa chấp nhận câu trả lời.
dùng541686

1
@Mehrdad: Tôi đã định nói rằng không có câu trả lời nào thực sự giải quyết câu hỏi như đã hỏi, nhưng dường như câu hỏi của bạn. Vào thời điểm bạn trả lời tôi đã tiếp tục và tiếp tục cải thiện strstrnhư một điều gì đó cho lần sau, vì vậy tôi thực sự đã loay hoay đọc đúng bài báo bạn liên kết, nhưng nghe có vẻ rất hứa hẹn. Cảm ơn và xin lỗi vì đã không quay lại với bạn.
R .. GitHub DỪNG GIÚP ICE

Câu trả lời:


37

Xây dựng một thư viện thử nghiệm của kim và đống cỏ khô. Hồ sơ các bài kiểm tra trên một số thuật toán tìm kiếm, bao gồm cả lực lượng vũ phu. Chọn một trong đó thực hiện tốt nhất với dữ liệu của bạn.

Boyer-Moore sử dụng bảng nhân vật xấu với bảng hậu tố tốt.

Boyer-Moore-Horspool sử dụng bảng nhân vật xấu.

Knuth-Morris-Pratt sử dụng bảng so khớp một phần.

Rabin-Karp sử dụng băm chạy.

Tất cả đều trao đổi chi phí để so sánh giảm ở một mức độ khác nhau, vì vậy hiệu suất trong thế giới thực sẽ phụ thuộc vào độ dài trung bình của cả kim và cỏ khô. Chi phí ban đầu càng nhiều, càng tốt với đầu vào dài hơn. Với kim rất ngắn, lực lượng vũ phu có thể giành chiến thắng.

Biên tập:

Một thuật toán khác nhau có thể là tốt nhất để tìm các cặp cơ sở, cụm từ tiếng Anh hoặc từ đơn. Nếu có một thuật toán tốt nhất cho tất cả các đầu vào, nó sẽ được công khai.

Hãy suy nghĩ về bảng nhỏ sau đây. Mỗi dấu hỏi có thể có một thuật toán tìm kiếm tốt nhất khác nhau.

                 short needle     long needle
short haystack         ?               ?
long haystack          ?               ?

Đây thực sự phải là một biểu đồ, với một phạm vi đầu vào ngắn hơn đến dài hơn trên mỗi trục. Nếu bạn vẽ từng thuật toán trên một biểu đồ như vậy, mỗi biểu đồ sẽ có một chữ ký khác nhau. Một số thuật toán chịu nhiều sự lặp lại trong mẫu, điều này có thể ảnh hưởng đến việc sử dụng như tìm kiếm gen. Một số yếu tố khác ảnh hưởng đến hiệu suất tổng thể là tìm kiếm cùng một mẫu nhiều lần và tìm kiếm các mẫu khác nhau cùng một lúc.

Nếu tôi cần một bộ mẫu, tôi nghĩ rằng tôi sẽ quét một trang web như google hoặc wikipedia, sau đó tách html khỏi tất cả các trang kết quả. Đối với một trang tìm kiếm, nhập một từ sau đó sử dụng một trong các cụm từ tìm kiếm được đề xuất. Chọn một vài ngôn ngữ khác nhau, nếu có. Sử dụng các trang web, tất cả các văn bản sẽ ngắn đến trung bình, vì vậy hãy hợp nhất các trang đủ để có được các văn bản dài hơn. Bạn cũng có thể tìm thấy sách trong phạm vi công cộng, hồ sơ pháp lý và các cơ quan lớn khác của văn bản. Hoặc chỉ tạo nội dung ngẫu nhiên bằng cách chọn từ trong từ điển. Nhưng quan điểm của hồ sơ là kiểm tra loại nội dung bạn sẽ tìm kiếm, vì vậy hãy sử dụng các mẫu trong thế giới thực nếu có thể.

Tôi để lại ngắn và dài mơ hồ. Đối với kim, tôi nghĩ ngắn dưới 8 ký tự, trung bình dưới 64 ký tự và dài dưới 1k. Đối với haystack, tôi nghĩ ngắn dưới 2 ^ 10, trung bình dưới 2 ^ 20 và dài tối đa 2 ^ 30 ký tự.


1
Bạn có đề xuất tốt cho một thư viện thử nghiệm? Câu hỏi trước tôi đã hỏi về SO có liên quan đến điều đó và tôi không bao giờ có câu trả lời thực sự nào. (ngoại trừ của riêng tôi ...) Nó nên được mở rộng. Ngay cả khi ý tưởng của tôi về một ứng dụng cho strstr là tìm kiếm văn bản tiếng Anh, người khác có thể đang tìm kiếm gen theo trình tự cặp cơ sở ...
R .. GitHub DỪNG GIÚP ICE

3
Nó phức tạp hơn một chút so với ngắn / dài. Đối với kim, câu hỏi lớn liên quan đến hiệu suất của hầu hết các thuật toán là: Độ dài? Có định kỳ không? Có kim chứa tất cả các ký tự duy nhất (không lặp lại)? Hay tất cả cùng một nhân vật? Có một số lượng lớn các nhân vật trong đống cỏ khô không bao giờ xuất hiện trong kim? Có cơ hội phải đối phó với kim tiêm được cung cấp bởi kẻ tấn công muốn khai thác hiệu suất trong trường hợp xấu nhất để làm tê liệt hệ thống của bạn không? V.v ..
R .. GitHub DỪNG GIÚP ICE

31

Được xuất bản vào năm 2011, tôi tin rằng nó rất có thể là thuật toán "Kết hợp chuỗi không gian liên tục theo thời gian thực đơn giản" của Dany Breslauer, Roberto Grossi và Filippo Mignosi.

Cập nhật:

Trong năm 2014, các tác giả đã công bố cải tiến này: Hướng tới kết hợp chuỗi đóng gói tối ưu .


1
Ồ cảm ơn nhé. Tôi đang đọc báo. Nếu nó trở nên tốt hơn những gì tôi có, tôi chắc chắn sẽ chấp nhận câu trả lời của bạn.
R .. GitHub DỪNG GIÚP ICE

1
@R ..: Chắc chắn rồi! :) Nói về điều này, nếu bạn quản lý để thực hiện thuật toán, vui lòng xem xét việc đăng nó lên StackOverflow để mọi người đều có thể hưởng lợi từ nó! Tôi chưa tìm thấy bất kỳ triển khai nào ở bất cứ đâu và tôi không giỏi triển khai các thuật toán tôi tìm thấy trong các tài liệu nghiên cứu haha.
dùng541686

2
Đây là một biến thể của thuật toán "hai chiều" mà tôi đã sử dụng, do đó, việc điều chỉnh mã của tôi để sử dụng thuật toán này thực sự có thể dễ dàng. Tuy nhiên, tôi sẽ phải đọc bài viết chi tiết hơn để chắc chắn, và tôi cần đánh giá xem các thay đổi được thực hiện có tương thích với việc tôi sử dụng "bảng ký tự xấu" giúp tăng tốc đáng kể trường hợp thông thường hay không.
R .. GitHub DỪNG GIÚP ICE

11
Và bạn vẫn chưa chấp nhận câu trả lời của @ Mehrdad! :-)
tuổi thọ

3
@DavidWallace: Cái gì? Nó có tiêu đề giấy và các tác giả. Ngay cả khi liên kết bị chết, bạn có thể tìm thấy các giấy tờ. Bạn đang mong đợi tôi làm gì, viết mã giả cho thuật toán? Điều gì làm cho bạn nghĩ rằng tôi hiểu thuật toán?
dùng541686

23

Các http://www-igm.univ-mlv.fr/~lecroq/string/index.html liên kết bạn trỏ đến là một nguồn tuyệt vời và bản tóm tắt của một số các hợp thuật toán chuỗi nổi tiếng nhất và được nghiên cứu.

Giải pháp cho hầu hết các vấn đề tìm kiếm liên quan đến sự đánh đổi liên quan đến các yêu cầu về tiền xử lý, thời gian và không gian. Không có thuật toán duy nhất sẽ là tối ưu hoặc thực tế trong mọi trường hợp.

Nếu mục tiêu của bạn là thiết kế một thuật toán cụ thể cho tìm kiếm chuỗi, thì hãy bỏ qua phần còn lại của những gì tôi muốn nói, Nếu bạn muốn phát triển một thói quen dịch vụ tìm kiếm chuỗi tổng quát thì hãy thử như sau:

Dành thời gian để xem xét các điểm mạnh và điểm yếu cụ thể của các thuật toán bạn đã tham khảo. Tiến hành đánh giá với mục tiêu tìm một bộ thuật toán bao gồm phạm vi và phạm vi tìm kiếm chuỗi mà bạn quan tâm. Sau đó, xây dựng bộ chọn tìm kiếm giao diện người dùng dựa trên hàm phân loại để nhắm mục tiêu thuật toán tốt nhất cho các đầu vào đã cho. Bằng cách này bạn có thể sử dụng thuật toán hiệu quả nhất để thực hiện công việc. Điều này đặc biệt hiệu quả khi một thuật toán rất tốt cho một số tìm kiếm nhất định nhưng xuống cấp kém. Ví dụ, lực vũ phu có lẽ là tốt nhất đối với kim có chiều dài 1 nhưng nhanh chóng xuống cấp khi chiều dài kim tăng lên, trong đó algoritim sustik-moorecó thể trở nên hiệu quả hơn (trên bảng chữ cái nhỏ), sau đó đối với kim dài hơn và bảng chữ cái lớn hơn, thuật toán KMP hoặc Boyer-Moore có thể tốt hơn. Đây chỉ là những ví dụ để minh họa một chiến lược có thể.

Cách tiếp cận nhiều thuật toán không phải là một ý tưởng mới. Tôi tin rằng nó đã được sử dụng bởi một vài gói Sắp xếp / Tìm kiếm thương mại (ví dụ: SYNCSORT thường được sử dụng trên các máy tính lớn thực hiện một số thuật toán sắp xếp và sử dụng phương pháp phỏng đoán để chọn gói "tốt nhất" cho các đầu vào đã cho)

Mỗi thuật toán tìm kiếm có một số biến thể có thể tạo ra sự khác biệt đáng kể cho hiệu suất của nó, ví dụ như bài báo này minh họa.

Điểm chuẩn dịch vụ của bạn để phân loại các khu vực cần chiến lược tìm kiếm bổ sung hoặc điều chỉnh hiệu quả hơn chức năng chọn của bạn. Cách tiếp cận này không nhanh chóng hay dễ dàng nhưng nếu được thực hiện tốt có thể tạo ra kết quả rất tốt.


1
Cảm ơn phản hồi, đặc biệt là liên kết đến Sustik-Moore mà tôi chưa từng thấy trước đây. Cách tiếp cận nhiều thuật toán chắc chắn được sử dụng rộng rãi. Glibc về cơ bản thực hiện strchr, Two-Way mà không có bảng dịch chuyển ký tự xấu hoặc Two-Way với bảng dịch chuyển ký tự xấu, tùy thuộc vào việc kim_len là 1, <32 hay> 32. Cách tiếp cận hiện tại của tôi là như nhau ngoại trừ việc tôi luôn sử dụng bảng shift; Tôi đã thay thế bộ nhớ 1kb cần thiết để làm như vậy với bộ nhớ 32 byte trên một bit được sử dụng để đánh dấu các phần tử nào của bảng đã được khởi tạo và tôi nhận được lợi ích (nhưng không phải chi phí) ngay cả đối với các kim nhỏ.
R .. GitHub DỪNG GIÚP ICE

1
Sau khi suy nghĩ về nó, tôi thực sự tò mò ứng dụng dự định cho Sustik-Moore là gì. Với bảng chữ cái nhỏ, bạn sẽ không bao giờ thực hiện bất kỳ thay đổi đáng kể nào (tất cả các ký tự của bảng chữ cái gần như chắc chắn xuất hiện ở gần cuối kim) và phương pháp tự động hữu hạn rất hiệu quả (bảng chuyển trạng thái nhỏ). Vì vậy, tôi không thể hình dung bất kỳ kịch bản nào mà Sustik-Moore có thể là tối ưu ...
R .. GitHub DỪNG GIÚP ICE

phản ứng tuyệt vời - nếu tôi có thể đánh dấu sao câu trả lời cụ thể này thì tôi sẽ làm.
Jason S

1
@R .. Lý thuyết đằng sau thuật toán sustik-moore là nó sẽ cung cấp cho bạn lượng dịch chuyển trung bình lớn hơn khi kim tương đối lớn và bảng chữ cái tương đối nhỏ (ví dụ: tìm kiếm chuỗi DNA). Lớn hơn trong trường hợp này chỉ có nghĩa là lớn hơn thuật toán Boyer-Moore cơ bản sẽ tạo ra các đầu vào tương tự. Điều này hiệu quả hơn bao nhiêu so với cách tiếp cận automata hữu hạn hoặc với một số biến thể Boyer-Moore khác (trong đó có nhiều) là khó nói. Đó là lý do tại sao tôi nhấn mạnh dành một chút thời gian để nghiên cứu các điểm mạnh / điểm yếu cụ thể của các thuật toán ứng viên của bạn.
NealB

1
Hừm, tôi đoán là tôi đã bế tắc khi nghĩ về những thay đổi chỉ trong ý nghĩa của những thay đổi nhân vật xấu từ Boyer-Moore. Với sự cải thiện về dịch chuyển hậu tố tốt BM, Sustik-Moore có thể có khả năng vượt trội hơn các phương pháp tiếp cận DFA để tìm kiếm DNA. Thứ gọn gàng.
R .. GitHub DỪNG GIÚP ICE

21

Tôi đã ngạc nhiên khi thấy báo cáo công nghệ của chúng tôi được trích dẫn trong cuộc thảo luận này; Tôi là một trong những tác giả của thuật toán được đặt tên là Sustik-Moore ở trên. (Chúng tôi không sử dụng thuật ngữ đó trong bài viết của mình.)

Tôi muốn ở đây để nhấn mạnh rằng đối với tôi, tính năng thú vị nhất của thuật toán là khá đơn giản để chứng minh rằng mỗi chữ cái được kiểm tra nhiều nhất một lần. Đối với các phiên bản Boyer-Moore trước đó, họ đã chứng minh rằng mỗi chữ cái được kiểm tra nhiều nhất là 3 và sau đó nhiều nhất là 2 lần, và những bằng chứng đó có liên quan nhiều hơn (xem trích dẫn trên giấy). Do đó tôi cũng thấy một giá trị thực tế trong việc trình bày / nghiên cứu biến thể này.

Trong bài báo, chúng tôi cũng mô tả các biến thể tiếp theo hướng đến hiệu quả trong khi nới lỏng các đảm bảo lý thuyết. Đó là một bài viết ngắn và tài liệu nên dễ hiểu đối với một người tốt nghiệp trung học phổ thông theo ý kiến ​​của tôi.

Mục tiêu chính của chúng tôi là đưa phiên bản này đến sự chú ý của những người khác, những người có thể cải thiện hơn nữa về nó. Tìm kiếm chuỗi có rất nhiều biến thể và một mình chúng ta không thể nghĩ ra được ý tưởng này có thể mang lại lợi ích gì. (Đã sửa lỗi văn bản và thay đổi mẫu, văn bản cố định khác nhau, có thể xử lý trước / không thể thực hiện, thực thi song song, tìm các tập hợp con trong các văn bản lớn, cho phép lỗi, gần khớp, v.v.)


1
Bạn có tình cờ biết về việc triển khai C hoặc C ++ không? Tôi đang nghĩ đến việc sử dụng điều này cho một số tìm kiếm mô-đun (khớp chính xác mô-đun). Nếu không, có lẽ tôi sẽ cố gắng phát triển một thực hiện bản thân mình và trình thúc đẩy giải thuật
JDiMatteo

4
Không có triển khai có sẵn, thuật toán Sustik-Moore / 2BLOCK dường như không được sử dụng trong thực tế và tiếp tục bị bỏ qua trong các kết quả trong các bài báo tóm tắt như "Vấn đề khớp chuỗi chính xác: Đánh giá thử nghiệm toàn diện"
JDiMatteo

18

Thuật toán tìm kiếm chuỗi con nhanh nhất sẽ phụ thuộc vào ngữ cảnh:

  1. kích thước bảng chữ cái (ví dụ DNA so với tiếng Anh)
  2. chiều dài kim

Bài viết năm 2010 "Vấn đề khớp chuỗi chính xác: Đánh giá thử nghiệm toàn diện" đưa ra các bảng với thời gian chạy cho 51 thuật toán (với kích thước bảng chữ cái và độ dài kim khác nhau), vì vậy bạn có thể chọn thuật toán tốt nhất cho ngữ cảnh của mình.

Tất cả các thuật toán đó đều có triển khai C, cũng như một bộ thử nghiệm, tại đây:

http://www.dmi.unict.it/~faro/smart/alerskyms.php


4

Một câu hỏi thực sự tốt. Chỉ cần thêm một số bit nhỏ ...

  1. Ai đó đã nói về kết hợp trình tự DNA. Nhưng đối với chuỗi DNA, những gì chúng ta thường làm là xây dựng cấu trúc dữ liệu (ví dụ mảng hậu tố, cây hậu tố hoặc chỉ mục FM) cho đống cỏ khô và ghép nhiều kim với nó. Đây là một câu hỏi khác nhau.

  2. Sẽ thật sự tuyệt vời nếu ai đó muốn điểm chuẩn các thuật toán khác nhau. Có các điểm chuẩn rất tốt về nén và xây dựng các mảng hậu tố, nhưng tôi chưa thấy một điểm chuẩn nào về kết hợp chuỗi. Các ứng viên haystack tiềm năng có thể từ điểm chuẩn SACA .

  3. Vài ngày trước tôi đã thử nghiệm triển khai Boyer-Moore từ trang bạn đề xuất (EDIT: Tôi cần một hàm gọi như memmem (), nhưng nó không phải là một chức năng tiêu chuẩn, vì vậy tôi đã quyết định thực hiện nó). Chương trình điểm chuẩn của tôi sử dụng haystack ngẫu nhiên. Có vẻ như việc triển khai Boyer-Moore trong trang đó nhanh hơn nhiều lần so với memmem của glibc () và strnstr () của Mac. Trong trường hợp bạn quan tâm, việc thực hiện là ở đây và mã điểm chuẩn ở đây . Đây chắc chắn không phải là một tiêu chuẩn thực tế, nhưng nó là một sự khởi đầu.


Nếu bạn có một số kim tốt để kiểm tra cùng với các ứng cử viên haystack từ điểm chuẩn SACA, hãy đăng chúng dưới dạng câu trả lời cho câu hỏi khác của tôi và, để nhận được câu trả lời tốt hơn, tôi sẽ đánh dấu nó được chấp nhận.
R .. GitHub DỪNG GIÚP ICE

3
Về memmem của bạn và Boyer-Moore, rất có thể Boyer-Moore (hay đúng hơn là một trong những cải tiến của Boyer-Moore) sẽ hoạt động tốt nhất trên dữ liệu ngẫu nhiên. Dữ liệu ngẫu nhiên có xác suất định kỳ cực kỳ thấp và các phần trùng khớp dài dẫn đến trường hợp xấu nhất bậc hai. Tôi đang tìm cách kết hợp Boyer-Moore và Two-Way hoặc để phát hiện hiệu quả khi Boyer-Moore "an toàn để sử dụng" nhưng cho đến nay tôi vẫn chưa có thành công. BTW Tôi sẽ không sử dụng memmem của glibc để so sánh. Việc triển khai thuật toán cơ bản giống như thuật toán của Glibc nhanh hơn nhiều lần.
R .. GitHub DỪNG GIÚP ICE

Như tôi đã nói, nó không phải là thực hiện của tôi. Tín dụng cho Christian Charras và Thierry Lecroq. Tôi có thể tưởng tượng tại sao đầu vào ngẫu nhiên là xấu cho điểm chuẩn và tôi chắc chắn glibc chọn thuật toán vì lý do. Tôi cũng đoán memmem () không được triển khai hiệu quả. Tôi sẽ thử. Cảm ơn.
dùng172818

4

Tôi biết đó là một câu hỏi cũ, nhưng hầu hết các bảng thay đổi xấu là ký tự đơn. Nếu nó có ý nghĩa đối với tập dữ liệu của bạn (ví dụ: đặc biệt nếu đó là các từ được viết) và nếu bạn có sẵn dung lượng, bạn có thể tăng tốc đáng kể bằng cách sử dụng bảng dịch chuyển xấu được làm bằng n-gram thay vì các ký tự đơn.


3

Sử dụng stdlib strstr:

char *foundit = strstr(haystack, needle);

Nó rất nhanh, chỉ mất khoảng 5 giây để gõ.


26
Và nếu bạn đọc câu hỏi của tôi, bạn sẽ thấy tôi đã có một thời gian khá dễ dàng hơn nó. Tôi thích sự mỉa mai của bạn đủ, tôi sẽ bỏ qua -1.
R .. GitHub DỪNG GIÚP ICE

3

Đây là triển khai tìm kiếm của Python , được sử dụng từ khắp lõi. Các ý kiến ​​cho thấy nó sử dụng bảng delta 1 boyer-moore .

Tôi đã thực hiện một số thử nghiệm khá rộng rãi với chuỗi tìm kiếm bản thân, nhưng nó là cho nhiều chuỗi tìm kiếm. Việc triển khai lắp ráp HorspoolBitap thường có thể tự chống lại các thuật toán như Aho-Corasick cho số lượng mẫu thấp.


3

strchrThuật toán "Tìm kiếm một ký tự khớp" (ala ) nhanh hơn .

Lưu ý quan trọng:

  • Các hàm này sử dụng một gcctrình biên dịch "số / số lượng (hàng đầu | dấu)" bên trong- __builtin_ctz. Các chức năng này có thể chỉ nhanh trên các máy có (các) lệnh thực hiện thao tác này (ví dụ: x86, ppc, arm).

  • Các hàm này giả định kiến ​​trúc đích có thể thực hiện tải không phân bổ 32 và 64 bit. Nếu kiến ​​trúc mục tiêu của bạn không hỗ trợ điều này, bạn sẽ cần thêm một số logic khởi động để căn chỉnh chính xác các lần đọc.

  • Các chức năng này là bộ xử lý trung tính. Nếu CPU mục tiêu có các hướng dẫn vectơ, bạn có thể làm tốt hơn (nhiều). Ví dụ, strlenHàm bên dưới sử dụng SSE3 và có thể được sửa đổi một cách tầm thường thành XOR các byte được quét để tìm kiếm một byte khác 0. Điểm chuẩn được thực hiện trên máy tính xách tay Core 2 2,66GHz chạy Mac OS X 10.6 (x86_64):

    • 843.433 MB / s cho strchr
    • 2656,742 MB / s cho findFirstByte64
    • 13094.479 MB / s cho strlen

... Phiên bản 32 bit:

#ifdef __BIG_ENDIAN__
#define findFirstZeroByte32(x) ({ uint32_t _x = (x); _x = ~(((_x & 0x7F7F7F7Fu) + 0x7F7F7F7Fu) | _x | 0x7F7F7F7Fu); (_x == 0u)   ? 0 : (__builtin_clz(_x) >> 3) + 1; })
#else
#define findFirstZeroByte32(x) ({ uint32_t _x = (x); _x = ~(((_x & 0x7F7F7F7Fu) + 0x7F7F7F7Fu) | _x | 0x7F7F7F7Fu);                    (__builtin_ctz(_x) + 1) >> 3; })
#endif

unsigned char *findFirstByte32(unsigned char *ptr, unsigned char byte) {
  uint32_t *ptr32 = (uint32_t *)ptr, firstByte32 = 0u, byteMask32 = (byte) | (byte << 8);
  byteMask32 |= byteMask32 << 16;
  while((firstByte32 = findFirstZeroByte32((*ptr32) ^ byteMask32)) == 0) { ptr32++; }
  return(ptr + ((((unsigned char *)ptr32) - ptr) + firstByte32 - 1));
}

... và phiên bản 64 bit:

#ifdef __BIG_ENDIAN__
#define findFirstZeroByte64(x) ({ uint64_t _x = (x); _x = ~(((_x & 0x7F7F7F7F7f7f7f7full) + 0x7F7F7F7F7f7f7f7full) | _x | 0x7F7F7F7F7f7f7f7full); (_x == 0ull) ? 0 : (__builtin_clzll(_x) >> 3) + 1; })
#else
#define findFirstZeroByte64(x) ({ uint64_t _x = (x); _x = ~(((_x & 0x7F7F7F7F7f7f7f7full) + 0x7F7F7F7F7f7f7f7full) | _x | 0x7F7F7F7F7f7f7f7full);                    (__builtin_ctzll(_x) + 1) >> 3; })
#endif

unsigned char *findFirstByte64(unsigned char *ptr, unsigned char byte) {
  uint64_t *ptr64 = (uint64_t *)ptr, firstByte64 = 0u, byteMask64 = (byte) | (byte << 8);
  byteMask64 |= byteMask64 << 16;
  byteMask64 |= byteMask64 << 32;
  while((firstByte64 = findFirstZeroByte64((*ptr64) ^ byteMask64)) == 0) { ptr64++; }
  return(ptr + ((((unsigned char *)ptr64) - ptr) + firstByte64 - 1));
}

Chỉnh sửa 2011/06/04 OP chỉ ra trong các ý kiến ​​rằng giải pháp này có "lỗi không thể vượt qua":

nó có thể đọc qua bộ kết thúc byte hoặc null tìm kiếm, có thể truy cập một trang hoặc trang chưa được ánh xạ mà không có sự cho phép đọc. Bạn chỉ đơn giản là không thể sử dụng số đọc lớn trong các hàm chuỗi trừ khi chúng được căn chỉnh.

Điều này đúng về mặt kỹ thuật, nhưng áp dụng cho hầu như bất kỳ thuật toán nào hoạt động trên các khối lớn hơn một byte, bao gồm cả phương thức được OP đề xuất trong các nhận xét:

Một strchrtriển khai điển hình không phải là ngây thơ, nhưng hiệu quả hơn một chút so với những gì bạn đã đưa ra. Xem phần cuối của thuật toán này để biết thuật toán được sử dụng rộng rãi nhất: http://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord

Nó cũng thực sự không có gì để làm với sự liên kết per-se. Đúng, điều này có khả năng có thể gây ra hành vi được thảo luận trên phần lớn các kiến ​​trúc phổ biến đang sử dụng, nhưng điều này có liên quan nhiều hơn đến các chi tiết triển khai kiến ​​trúc vi mô - nếu việc đọc không được sắp xếp nằm ở ranh giới 4K (một lần nữa, điển hình), thì việc đọc đó sẽ gây ra một chương trình chấm dứt lỗi nếu ranh giới trang 4K tiếp theo không được ánh xạ.

Nhưng đây không phải là "lỗi" trong thuật toán được đưa ra trong câu trả lời - hành vi đó là do các hàm thích strchrstrlenkhông chấp nhận một lengthđối số để ràng buộc kích thước của tìm kiếm. Tìm kiếm char bytes[1] = {0x55};, với mục đích thảo luận của chúng tôi chỉ tình cờ được đặt ở cuối ranh giới trang 4K VM và trang tiếp theo không được ánh xạ, với strchr(bytes, 0xAA)(nơi strchrthực hiện theo thời gian byte) sẽ bị sập chính xác cùng một cách Ditto cho strchranh em họ liên quan strlen.

Không có lengthđối số, không có cách nào để biết khi nào bạn nên chuyển ra khỏi thuật toán tốc độ cao và quay lại thuật toán theo từng byte. Một "lỗi" rất có thể sẽ là đọc "vượt quá kích thước của phân bổ", về mặt kỹ thuật dẫn undefined behaviorđến các tiêu chuẩn ngôn ngữ C khác nhau và sẽ bị đánh dấu là lỗi bởi một cái gì đó như valgrind.

Tóm lại, bất cứ điều gì hoạt động trên khối lớn hơn byte đều đi nhanh hơn, vì mã câu trả lời này thực hiện và mã được chỉ ra bởi OP, nhưng phải có ngữ nghĩa đọc chính xác byte có khả năng là "lỗi" nếu không có lengthđối số kiểm soát (các) trường hợp góc của "lần đọc cuối".

Mã trong câu trả lời này là một hạt nhân để có thể tìm thấy byte đầu tiên trong một đoạn kích thước từ CPU tự nhiên một cách nhanh chóng nếu CPU mục tiêu có một ctzlệnh giống như nhanh . Việc thêm các thứ như đảm bảo nó chỉ hoạt động trên các ranh giới tự nhiên được căn chỉnh chính xác hoặc một số dạng lengthràng buộc sẽ cho phép bạn chuyển ra khỏi nhân tốc độ cao và kiểm tra từng byte chậm hơn.

OP cũng nêu trong các ý kiến:

Đối với tối ưu hóa ctz của bạn, nó chỉ tạo ra sự khác biệt cho hoạt động đuôi O (1). Nó có thể cải thiện hiệu suất với các chuỗi nhỏ (ví dụ strchr("abc", 'a');nhưng chắc chắn không phải với các chuỗi có kích thước chính.

Việc tuyên bố này có đúng hay không phụ thuộc rất nhiều vào kiến ​​trúc vi mô đang được đề cập. Sử dụng mô hình đường ống RISC 4 giai đoạn chuẩn, thì điều đó gần như chắc chắn là đúng. Nhưng thật khó để nói liệu điều đó có đúng với CPU siêu vô hướng không theo thứ tự hiện đại hay không, nơi tốc độ lõi có thể làm giảm hoàn toàn tốc độ truyền bộ nhớ. Trong trường hợp này, nó không chỉ hợp lý, mà còn khá phổ biến, vì có một khoảng cách lớn về "số lượng lệnh có thể được rút lại" so với "số lượng byte có thể được truyền phát" để bạn có " số lượng lệnh có thể được bỏ qua cho mỗi byte có thể được truyền phát ". Nếu điều này đủ lớn, lệnh ctz+ shift có thể được thực hiện "miễn phí".


"Đối với kim có độ dài 1, hãy sử dụng strchr." - Bạn đã yêu cầu (các) thuật toán tìm kiếm chuỗi con nhanh nhất. Tìm một chuỗi con có độ dài 1 chỉ là một trường hợp đặc biệt, một chuỗi cũng có thể được tối ưu hóa. Nếu bạn trao đổi mã trường hợp đặc biệt hiện tại của mình cho các chuỗi con có độ dài 1 ( strchr) với thứ gì đó giống như trên, mọi thứ sẽ (có thể, tùy thuộc vào cách strchrtriển khai) sẽ diễn ra nhanh hơn. Thuật toán trên nhanh hơn gần 3 lần so với strchrtriển khai ngây thơ thông thường .
johne

2
OP cho biết chuỗi đã được kết thúc đúng cách, vì vậy cuộc thảo luận của bạn char bytes[1] = {0x55};là không liên quan. Rất có liên quan là nhận xét của bạn về điều này là đúng đối với bất kỳ thuật toán đọc từ nào không biết trước độ dài.
Seth Robertson

1
Vấn đề không áp dụng cho phiên bản tôi đã trích dẫn vì bạn chỉ sử dụng nó trên các con trỏ được căn chỉnh - ít nhất đó là những gì việc triển khai đúng.
R .. GitHub DỪNG GIÚP ICE

2
@R, nó không có gì để làm với "con trỏ được căn chỉnh". Về mặt giả thuyết, nếu bạn có một kiến ​​trúc hỗ trợ bảo vệ VM với mức độ chi tiết của byte và mỗi mallocphân bổ đã được "đệm đủ" ở hai bên hệ thống VM đã thực thi bảo vệ dạng hạt byte cho phân bổ đó .... cho dù con trỏ có được căn chỉnh hay không ( giả sử intcăn chỉnh tự nhiên 32 bit tầm thường ) là không cần thiết - vẫn có thể đọc được căn chỉnh đó để đọc qua kích thước của phân bổ. BẤT K read đọc qua kích thước của phân bổ là undefined behavior.
johne

5
@johne: +1 để bình luận. Về mặt khái niệm bạn đúng, nhưng thực tế là các biện pháp bảo vệ chi tiết byte rất tốn kém để lưu trữ và để thực thi rằng chúng không và sẽ không bao giờ tồn tại. Nếu bạn biết bộ nhớ bên dưới là ánh xạ chi tiết trang thu được từ tương đương mmap, thì căn chỉnh là đủ.
R .. GitHub DỪNG GIÚP ICE

3

Chỉ cần tìm kiếm "strstr nhanh nhất", và nếu bạn thấy điều gì đó quan tâm, hãy hỏi tôi.

Theo quan điểm của tôi, bạn áp đặt quá nhiều hạn chế cho bản thân (vâng, tất cả chúng ta đều muốn tuyến tính phụ tuyến tính ở trình tìm kiếm tối đa), tuy nhiên, phải có một lập trình viên thực sự bước vào, cho đến lúc đó tôi nghĩ rằng cách tiếp cận băm chỉ đơn giản là một giải pháp tiện lợi ( được củng cố tốt bởi BNDM cho các mẫu 2..16 ngắn hơn).

Chỉ là một ví dụ nhanh:

Làm Tìm kiếm Pattern (32bytes) vào String (206908949bytes) như-một-line ... Skip-Performance (lớn hơn-the-tốt hơn): 3041%, 6.801.754 skips / lặp Railgun_Quadruplet_7Hasherezade_hits / Railgun_Quadruplet_7Hasherezade_clocks: 0/58 Railgun_Quadruplet_7Hasherezade hiệu suất: 3483KB / đồng hồ

Làm Tìm kiếm Pattern (32bytes) vào String (206908949bytes) như-một-line ... Skip-Performance (lớn hơn-the-tốt hơn): 1554%, 13.307.181 bỏ qua / lặp Boyer_Moore_Flensburg_hits / Boyer_Moore_Flensburg_clocks: 0/83 Boyer_Moore_Flensburg hiệu suất: 2434KB / đồng hồ

Thực hiện Tìm kiếm Mẫu (32byte) thành Chuỗi (206908949byte) dưới dạng một dòng ... Hiệu suất bỏ qua (lớn hơn tốt hơn): 129%, 160239051 bỏ qua / lặp lại Two-Way_hits / Two-Way_clocks: 0/816 Two Hiệu suất -Way : 247KB / đồng hồ

Sanmayce,
Trân trọng


3

Thuật toán hai chiều mà bạn đề cập trong câu hỏi của bạn (mà thật không thể tin được!) Gần đây đã được cải tiến để hoạt động hiệu quả trên các từ đa âm tại một thời điểm: Kết hợp chuỗi đóng gói tối ưu .

Tôi chưa đọc toàn bộ bài viết, nhưng có vẻ như họ dựa vào một vài hướng dẫn CPU mới, đặc biệt (bao gồm trong SSE 4.2) là O (1) cho yêu cầu phức tạp về thời gian của họ, mặc dù nếu không có sẵn họ có thể mô phỏng chúng trong thời gian O (log log w) cho các từ w-bit nghe không tệ lắm.


3

Bạn có thể thực hiện, giả sử, 4 thuật toán khác nhau. Mỗi M phút (được xác định theo kinh nghiệm) chạy cả 4 trên dữ liệu thực hiện tại. Tích lũy số liệu thống kê qua N chạy (cũng TBD). Sau đó chỉ sử dụng người chiến thắng trong M phút tiếp theo.

Ghi lại số liệu thống kê về Thắng để bạn có thể thay thế các thuật toán không bao giờ thắng bằng các thuật toán mới. Tập trung nỗ lực tối ưu hóa vào thói quen chiến thắng nhất. Đặc biệt chú ý đến các số liệu thống kê sau khi có bất kỳ thay đổi nào đối với phần cứng, cơ sở dữ liệu hoặc nguồn dữ liệu. Bao gồm thông tin đó trong nhật ký thống kê nếu có thể, vì vậy bạn sẽ không phải tìm ra thông tin đó từ ngày / thời gian đóng dấu nhật ký.


3

Gần đây tôi đã phát hiện ra một công cụ tuyệt vời để đo hiệu suất của các thuật toán có sẵn khác nhau: http://www.dmi.unict.it/~faro/smart/index.php

Bạn có thể thấy nó hữu ích. Ngoài ra, nếu tôi phải thực hiện một cuộc gọi nhanh về thuật toán tìm kiếm chuỗi con, tôi sẽ đi với Knuth-Morris-Pratt.


Cảm ơn các liên kết. Các thử nghiệm có vẻ thú vị đối với thời gian trong trường hợp điển hình nhưng không phải là để nắm bắt thời gian trong trường hợp xấu nhất.
R .. GitHub DỪNG GIÚP ICE

2

Bạn cũng có thể muốn có các điểm chuẩn đa dạng với một số loại chuỗi, vì điều này có thể có tác động lớn đến hiệu suất. Các thuật toán sẽ thực hiện sự khác biệt dựa trên việc tìm kiếm ngôn ngữ tự nhiên (và thậm chí ở đây vẫn có thể có sự phân biệt hạt nhỏ do các hình thái khác nhau), chuỗi DNA hoặc chuỗi ngẫu nhiên, v.v.

Kích thước bảng chữ cái sẽ đóng một vai trò trong nhiều thuật toán, cũng như kích thước kim. Chẳng hạn, Horspool làm tốt về văn bản tiếng Anh nhưng lại kém về DNA vì kích thước bảng chữ cái khác nhau, khiến cuộc sống trở nên khó khăn cho quy tắc ký tự xấu. Giới thiệu các hậu tố tốt cho thấy điều này rất nhiều.


0

Tôi không biết đó có phải là điều tuyệt vời nhất hay không, nhưng tôi đã có trải nghiệm tốt với Boyer-Moore .


Bạn có biết cách kết hợp bảng dịch chuyển xấu của Boyer-Moore với Two-Way không? Glibc thực hiện một biến thể này cho các kim dài (> 32 byte) nhưng chỉ kiểm tra byte cuối cùng. Vấn đề là Two-Way cần tìm kiếm phần bên phải của kim từ trái sang phải, trong khi dịch chuyển xấu của Boyer-Moore là hiệu quả nhất khi tìm kiếm từ phải sang trái. Tôi đã thử sử dụng nó với từ trái sang phải trong Hai chiều (tiến lên bằng bàn thay đổi hoặc nửa không đúng hai chiều thông thường, tùy theo thời gian nào dài hơn) nhưng tôi đã bị chậm 5-10% so với Hai chiều thông thường trong hầu hết các trường hợp và không thể tìm thấy bất kỳ trường hợp nào nó cải thiện hiệu suất.
R .. GitHub DỪNG GIÚP ICE

0

Điều này không trả lời trực tiếp câu hỏi nhưng nếu văn bản rất lớn, làm thế nào về việc chia nó thành các phần chồng chéo (chồng chéo theo chiều dài mẫu), sau đó tìm kiếm đồng thời các phần bằng các chủ đề. Liên quan đến thuật toán nhanh nhất, Boyer-Moore-Horspool tôi nghĩ là một trong những nhanh nhất nếu không phải là nhanh nhất trong số các biến thể của Boyer-Moore. Tôi đã đăng một vài biến thể Boyer-Moore (tôi không biết tên của họ) trong chủ đề này Thuật toán nhanh hơn BMH (BoyerÊet Moore Moore Horspool) Tìm kiếm .


0

Nhanh nhất hiện tại là EPSM, bởi S. Faro và OM Kulekci. Xem http://www.dmi.unict.it/~faro/smart/alerskyms.php?alacticm=EPSM&code=epsm

"Kết hợp chuỗi đóng gói chính xác" được tối ưu hóa cho SIMD SSE4.2 (x86_64 và aarch64). Nó thực hiện ổn định và tốt nhất trên tất cả các kích cỡ.

Trang web tôi liên kết để so sánh 199 thuật toán tìm kiếm chuỗi nhanh, với các thuật toán thông thường (BM, KMP, BMH) khá chậm. EPSM vượt trội hơn tất cả những cái khác được đề cập ở đây trên các nền tảng này. Nó cũng là mới nhất.

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.