อัลกอริทึมการค้นหาสตริงใดที่เร็วที่สุด


27

ฉันติดอยู่กับที่ซึ่งเป็นอัลกอริธึมการค้นหาสตริงที่เร็วที่สุดได้ยินความคิดเห็นมากมาย แต่ในที่สุดฉันก็ไม่แน่ใจ

ฉันเคยได้ยินบางคนพูดว่าอัลกอริทึมที่เร็วที่สุดคือ Boyer-Moore และบางคนบอกว่า Knuth-Morris-Pratt เร็วกว่าจริง

ผมได้มองขึ้นสำหรับซับซ้อนทั้งในส่วนของพวกเขา O(n+m)แต่พวกเขาส่วนใหญ่มีลักษณะเดียวกัน ฉันได้พบว่าในกรณีที่เลวร้ายที่สุดกรณี Boyer-Moore มีO(nm)ความซับซ้อนเมื่อเทียบกับ Knuth-Morris-Pratt ซึ่งมี O (m + 2 * n) โดยที่ n = ความยาวของข้อความและ m = ความยาวของรูปแบบ

เท่าที่ฉันรู้บอยเยอร์ - มัวร์มีเวลาตรงเชิงเส้นที่เลวร้ายที่สุดถ้าฉันจะใช้กฎกาลิล

คำถามของฉันสิ่งที่จริงแล้วคืออัลกอริธึมการค้นหาสตริงที่เร็วที่สุด (คำถามนี้รวมถึงอัลกอริธึมต่อยที่เป็นไปได้ทั้งหมดไม่ใช่แค่ Boyer-Moore และ Knuth-Morris-Pratt)

แก้ไข:เนื่องจากคำตอบนี้

สิ่งที่ฉันกำลังมองหาคือ:

ได้รับข้อความTและรูปแบบที่Pผมต้องไปหาที่ปรากฏทั้งหมดของในPT

นอกจากนี้ความยาวของ P และ T มาจาก[1,2 000 000]และโปรแกรมต้องทำงานต่ำกว่า 0.15 วินาที

ฉันรู้ว่า KMP และ Rabin-Karp เพียงพอที่จะได้รับคะแนน 100% สำหรับปัญหา แต่ฉันต้องการลองใช้ Boyer-Moore สิ่งใดจะดีที่สุดสำหรับการค้นหารูปแบบนี้


6
เมื่อคุณทดสอบสิ่งเหล่านี้ในภาษาที่คุณเลือกคุณพบอะไร
Walter

4
ในการทดสอบบางอย่าง Boyer-Moore ดีกว่าใน KMP อื่น ๆ ดีกว่า แต่ฉันไม่แน่ใจว่าฉันมีการใช้งานที่ดีที่สุด สำหรับภาษาที่เลือกจะอยู่ในแท็ก: C ++ (ไม่แน่ใจว่าคุณเห็นหรือไม่ว่าเมื่อคุณเขียนว่า "language of choice") ป.ล. ฉันไม่แน่ใจเช่นกันว่าได้ทำการทดสอบที่ดีที่สุดแล้ว
vandamon taigi


Knuth-Morris-Pratt ซึ่งมี O (m + 2 * n) ... คุณหมายถึง O (m + n)
จูลส์

เลือกอันใดอันหนึ่งที่มีความซับซ้อนของอัลกอริทึมที่เหมาะสมจากนั้นปรับแต่งอึออกจากมันด้วย profiler ในมือ - ใช้ได้กับฉันเสมอ :-D

คำตอบ:


38

ขึ้นอยู่กับประเภทของการค้นหาที่คุณต้องการ อัลกอริทึมแต่ละอันทำงานได้ดีเป็นพิเศษสำหรับการค้นหาบางประเภท แต่คุณไม่ได้ระบุบริบทของการค้นหาของคุณ

ต่อไปนี้เป็นความคิดทั่วไปของประเภทการค้นหา:

  • Boyer-Moore: ทำงานโดยการวิเคราะห์รูปแบบล่วงหน้าและเปรียบเทียบจากขวาไปซ้าย หากไม่ตรงกันเกิดขึ้นการวิเคราะห์เริ่มต้นจะถูกใช้เพื่อกำหนดว่ารูปแบบสามารถเลื่อนไปได้มากแค่ไหนข้อความที่กำลังค้นหา สิ่งนี้ทำงานได้ดีเป็นพิเศษสำหรับรูปแบบการค้นหาที่ยาวนาน โดยเฉพาะอย่างยิ่งมันสามารถย่อยเชิงเส้นในขณะที่คุณไม่จำเป็นต้องอ่านตัวละครทุกตัวของข้อความของคุณ

  • Knuth-Morris-Pratt: เช่นกันวิเคราะห์รูปแบบล่วงหน้า แต่พยายามที่จะใช้สิ่งที่จับคู่ไว้แล้วในส่วนเริ่มต้นของรูปแบบอีกครั้งเพื่อหลีกเลี่ยงการแข่งขันซ้ำ สิ่งนี้สามารถทำงานได้ค่อนข้างดีหากตัวอักษรของคุณมีขนาดเล็ก (ฐานดีเอ็นเอ f.ex. ) เนื่องจากคุณมีโอกาสสูงที่รูปแบบการค้นหาของคุณจะมีรูปแบบย่อยที่สามารถใช้ซ้ำได้

  • Aho-Corasick: ต้องการการประมวลผลล่วงหน้าจำนวนมาก แต่ใช้สำหรับรูปแบบจำนวนมาก หากคุณรู้ว่าคุณกำลังมองหารูปแบบการค้นหาเดียวกันซ้ำแล้วซ้ำอีกสิ่งนี้จะดีกว่ารูปแบบอื่น ๆ เพราะคุณต้องวิเคราะห์รูปแบบเพียงครั้งเดียวไม่ใช่ครั้งเดียวต่อการค้นหา

ดังนั้นตามปกติใน CS ไม่มีคำตอบที่ชัดเจนกับภาพรวมที่ดีที่สุด มันค่อนข้างเป็นเรื่องของการเลือกเครื่องมือที่เหมาะสมสำหรับงานในมือ

ข้อสังเกตอีกประการหนึ่งเกี่ยวกับการใช้เหตุผลกรณีที่เลวร้ายที่สุดของคุณ: พิจารณาประเภทการค้นหาที่จำเป็นในการสร้างกรณีและปัญหาที่เลวร้ายที่สุด ตัวอย่างเช่นO(mn)ความซับซ้อนในกรณีที่เลวร้ายที่สุดของอัลกอริทึม Boyer-Moore เกิดจากรูปแบบการค้นหาและข้อความที่แต่ละตัวใช้เพียงตัวเดียว (เช่นการค้นหาaaaในaaaaaaaaaaaaaaaaaaaaa) - คุณต้องการการค้นหาแบบเร็วหรือไม่?


ฉันมีทั้งตัวอักษรภาษาอังกฤษหรือเพื่อใช้และฉันอัปเดตคำถามขออภัยที่ไม่ได้เริ่มต้นด้วยสิ่งนี้ในการขอทาน
vandamon taigi

และใช่ฉันต้องรวดเร็วแม้จะทำการค้นหาแบบนั้น
vandamon taigi

1

แม้ว่าฉันจะสายเล็กน้อยที่จะตอบคำถามนี้ แต่ฉันคิดว่าZ-Algorithmมันเร็วกว่าคู่อื่น ๆ ความซับซ้อนของกรณีที่เลวร้ายที่สุดคือ O (m + n) และไม่จำเป็นต้องมีการประมวลผลรูปแบบ / ข้อความล่วงหน้า นอกจากนี้ยังง่ายในการเขียนโค้ดเมื่อเทียบกับอัลกอริธึมอื่น ๆ

มันทำงานในลักษณะดังต่อไปนี้

S ='abaaba'ตัวอย่างเช่นมีเป็นสตริง เราจะพบค่าz(i) i=0 to len(S)-1ก่อนที่จะอธิบายให้ฉันวางคำจำกัดความก่อน

z(i)= ไม่ ของตัวละครของคำนำหน้าของที่ตรงกับคำนำหน้าของSs(i)

s(i)= ต่อท้ายของithS

ต่อไปนี้เป็นค่าสำหรับs(i)s = 'abaaba'

s(0) = 'abaaba' = S
s(1) = 'baaba'
s(2) = 'aaba'
s(3) = 'aba'
s(4) = 'ba'
s(5) = 'a'

ค่า z ตามลำดับ

z(0) = 6 = length(S)
z(1) = 0
z(2) = 1
z(3) = 3
z(4) = 0
z(5) = 1

สำหรับการทำความเข้าใจรายละเอียดของอัลกอริทึมให้อ้างอิงจากลิงค์ต่อไปนี้

http://codeforces.com/blog/entry/3107

https://www.youtube.com/watch?v=MFK0WYeVEag

ตอนนี้ใช้เวลา O (N) เพื่อค้นหาzค่าทั้งหมดโดยไม่มีค่าใช้จ่ายในการประมวลผลล่วงหน้า มีใครสงสัยว่าตอนนี้คุณจะใช้ตรรกะนี้เพื่อจับคู่รูปแบบในสตริงที่กำหนดได้อย่างไร

มาดูตัวอย่างกัน รูปแบบ (P): abaข้อความ aacbabcabaad(T):

ใส่สิ่งนี้ในรูปแบบ P $ T ( $- ตัวละครใด ๆ ที่ไม่ปรากฏในรูปแบบหรือข้อความฉันจะมาถึงความสำคัญของ$ในเวลาไม่นาน)

P$T = aba$aacbabcabaad

เรารู้ว่าlen(P)= 3

ค่า z ทั้งหมดของP$Tคือ

z(0) = 16 = len(P$T)
z(1) = 0
z(2) = 1
z(3) = 0
z(4) = 1
z(5) = 1
z(6) = 0
z(7) = 0
z(8) = 2
z(9) = 0
z(10) = 0
z(11) = 3
z(12) = 0
z(13) = 1
Z(14) = 1
Z(15) = 0

ซึ่งขณะนี้=z(i) ดังนั้นรูปแบบของเราเป็นปัจจุบันที่= สำหรับตัวละครlen(P)Ans = 11.Ans-len(P)-17-1$

ตอนนี้ทำไม$หรือตัวละครพิเศษใด ๆ ที่มีความสำคัญ พิจารณาP = 'aaa'และT = 'aaaaaaa'. หากไม่มีอักขระพิเศษทั้งหมดz(i)จะมีค่าที่เพิ่มขึ้น หนึ่งยังคงสามารถค้นหาตำแหน่งของรูปแบบในข้อความด้วยสูตรด้านล่าง:

สภาพ: z(i)> = และตำแหน่ง:len(P) Ans-len(P)แต่เงื่อนไขในกรณีนี้กลายเป็นเรื่องยุ่งยากเล็กน้อยและสับสน ส่วนตัวแล้วผมชอบที่จะใช้เทคนิคพิเศษของตัวละคร


1
คุณสามารถอธิบายได้ด้วยตัวเองที่นี่ การมีลิงก์ไปยังไซต์ภายนอกสามารถใช้เพื่ออธิบายรายละเอียดได้ แต่หลักของคำตอบควรอยู่ในคำตอบเองแทนที่จะต้องติดตามลิงก์ไปยังเว็บไซต์อื่น

อัลกอริธึม z นั้นเหมือนกับ kmp ฉันสงสัยว่ามันเร็วขึ้นมาก
โทมัส Ahle

2
ฉันเห็นด้วยกับ @ThomasAhle คอมพิวเตอร์z กำลังประมวลผลล่วงหน้า มันเป็นคำอธิบายที่ดี ฉันหาO(n)วิธีแปลงจากการประมวลผลล่วงหน้า KMP เป็นการประมวลผลล่วงหน้า Z เนื่องจากคำตอบนี้ ที่นี่
leewz

-1

ใช้หน่วยความจำที่สามารถระบุตำแหน่งเนื้อหาได้ซึ่งมีการใช้งานในซอฟต์แวร์ในรูปแบบของการกำหนดแอดเดรสเสมือน (ตัวอักษรชี้ไปที่ตัวอักษร)

มันไม่จำเป็นเลยสำหรับอัลกอริธึมการจับคู่สตริงโดยเฉลี่ย

CAM สามารถจับคู่รูปแบบจำนวนมากพร้อมกันได้มากถึง 128 รูปแบบตัวอักษร (ถ้าเป็น ASCII ถ้าเป็น Unicode เพียง 64) และมันคือหนึ่งการโทรต่อความยาวของตัวอักษรในสตริงที่คุณต้องการจับคู่และหนึ่งการอ่านแบบสุ่มจากหน่วยความจำต่อความยาวของความยาวของรูปแบบสูงสุด ดังนั้นถ้าคุณวิเคราะห์สตริงตัวอักษร 100,000 ตัวด้วยรูปแบบมากถึง 90,000,000 รูปแบบพร้อมกัน (ซึ่งจะใช้เวลาประมาณ 128 GiB เพื่อเก็บรูปแบบที่มีขนาดใหญ่) มันจะใช้เวลาสุ่ม 12,800,000 ครั้งอ่านจาก RAM ดังนั้นมันจะเกิดขึ้นใน 1 มิลลิวินาที

นี่คือวิธีการทำงานของที่อยู่เสมือน

ถ้าฉันเริ่มต้นด้วยที่อยู่เริ่มต้น 256 รายการซึ่งแสดงถึงตัวอักษรตัวแรกตัวอักษรเหล่านี้จะชี้ไปที่ 256 ตัวอักษรถัดไป หากรูปแบบไม่มีอยู่คุณจะไม่เก็บรูปแบบนั้น

ดังนั้นถ้าฉันเชื่อมโยงตัวอักษรกับตัวอักษรต่อไปมันก็เหมือนกับการมีที่อยู่เสมือน 128 ชิ้นชี้ไปที่การกำหนดที่อยู่เสมือน

นั่นจะใช้งานได้ แต่เพื่อให้ได้รูปแบบที่ตรงกันมากกว่า 900,000,000 รูปแบบมีเคล็ดลับสุดท้ายที่จะเพิ่มเข้ามา - และเป็นการใช้ประโยชน์จากความจริงที่ว่าคุณเริ่มต้นด้วยการนำบัฟเฟอร์จดหมายจำนวนมากกลับมาใช้ใหม่ หากคุณแสดงรายการเนื้อหาแทนที่จะจัดสรรอักขระทั้งหมด 256 ตัวก็จะช้าลงเล็กน้อยและคุณจะได้รับความจุเพิ่มขึ้น 100 เท่าเพราะโดยทั่วไปแล้วคุณจะได้รับเพียง 1 ตัวอักษรที่ใช้ในบัฟเฟอร์ตัวชี้ตัวอักษรทุกตัว (ซึ่งฉันขนานนามว่า ' หนี').

หากคุณต้องการได้รับการจับคู่สตริงเพื่อนบ้านที่ใกล้ที่สุดจากนั้นคุณมีหลายเหล่านี้ทำงานในแบบคู่ขนานและคุณรวบรวมในลำดับชั้นเพื่อให้คุณกระจายข้อผิดพลาดของคุณออกมาโดยไม่ลำเอียง หากคุณพยายามที่จะใกล้เคียงกับเพื่อนบ้านที่มีเพียงหนึ่งแล้วคุณจะมีอคติต่อการเริ่มต้นของต้นไม้


4
@MagnusRobertCarlWoot เนื่องจากคุณมีกาวาตาเหมือนกับ roucer81 มันเป็นเรื่องบังเอิญทางดาราศาสตร์ของการชนกันของรหัสแฮชหรือคุณมีที่อยู่อีเมลเดียวกัน หากคุณเป็นบุคคลเดียวกันที่อยู่เบื้องหลังทั้งสองบัญชีคุณควรใช้แบบฟอร์ม "ติดต่อเรา" เพื่อรวมเข้าด้วยกันเพื่อให้คุณได้รับเครดิตที่เหมาะสมสำหรับชื่อเสียงที่ได้รับจาก upvotes ในคำตอบนี้
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.