วิธีที่มีประสิทธิภาพที่สุดในการค้นหาคำที่พบบ่อย K อันดับต้น ๆ ในลำดับคำใหญ่


86

อินพุต: จำนวนเต็มบวก K และข้อความขนาดใหญ่ ข้อความสามารถดูเป็นลำดับคำได้ เราจึงไม่ต้องกังวลว่าจะแยกย่อยออกเป็นลำดับคำอย่างไร
ผลลัพธ์: คำ K ที่ใช้บ่อยที่สุดในข้อความ

ความคิดของฉันเป็นแบบนี้

  1. ใช้ตารางแฮชเพื่อบันทึกความถี่ของคำทั้งหมดในขณะที่สำรวจลำดับคำทั้งหมด ในขั้นตอนนี้คีย์คือ "word" และค่าคือ "word-frequency" ใช้เวลา O (n)

  2. เรียงคู่ (word, word-frequency); และที่สำคัญคือ "ความถี่ของคำ" สิ่งนี้ใช้เวลา O (n * lg (n)) ด้วยอัลกอริทึมการเรียงลำดับปกติ

  3. หลังจากจัดเรียงแล้วเราก็ใช้คำ K แรก ใช้เวลา O (K)

สรุปได้ว่าเวลาทั้งหมดคือ O (n + n lg (n) + K), เนื่องจาก K มีขนาดเล็กกว่า N อย่างแน่นอนดังนั้นจึงเป็น O (n lg (n))

เราสามารถปรับปรุงสิ่งนี้ได้ จริงๆแล้วเราต้องการแค่คำ K อันดับต้น ๆ ความถี่ของคำอื่นไม่น่ากังวลสำหรับเรา ดังนั้นเราจึงสามารถใช้ "การเรียงลำดับฮีปบางส่วน" สำหรับขั้นตอนที่ 2) และ 3) เราไม่เพียงทำการเรียงลำดับ แต่เราเปลี่ยนเป็น

2 ') สร้างกองของ (word, word-frequency) โดยมี "word-frequency" เป็นคีย์ ต้องใช้เวลา O (n) ในการสร้างกอง

3 ') แยกคำ K ยอดนิยมออกจากกอง การสกัดแต่ละครั้งคือ O (lg (n)) ดังนั้นเวลาทั้งหมดคือ O (k * lg (n))

สรุปได้ว่าโซลูชันนี้ใช้เวลา O (n + k * lg (n))

นี่เป็นเพียงความคิดของฉัน ฉันยังไม่พบวิธีปรับปรุงขั้นตอนที่ 1)
ฉันหวังว่าผู้เชี่ยวชาญด้านการดึงข้อมูลบางคนสามารถให้ความกระจ่างเกี่ยวกับคำถามนี้ได้มากขึ้น


คุณจะใช้การเรียงลำดับผสานหรือ Quicksort สำหรับการจัดเรียง O (n * logn) หรือไม่?
committedandroider

1
สำหรับการใช้งานจริงคำตอบของAaron Maenpaa ในการนับตัวอย่างนั้นดีที่สุด ไม่ใช่ว่าคำที่ใช้บ่อยที่สุดจะซ่อนจากตัวอย่างของคุณ สำหรับความซับซ้อนของคุณมันเป็น O (1) เนื่องจากขนาดของตัวอย่างได้รับการแก้ไข คุณไม่ได้รับการนับที่แน่นอน แต่คุณก็ไม่ได้ขอเช่นกัน
Nikana Reklawyks

หากสิ่งที่คุณต้องการคือการทบทวนการวิเคราะห์ความซับซ้อนของคุณฉันควรพูดถึง: ถ้าnคือจำนวนคำในข้อความของคุณและmคือจำนวนคำที่แตกต่างกัน (ประเภทที่เราเรียกว่า) ขั้นตอนที่ 1 คือ O ( n ) แต่ขั้นตอนที่ 2 คือ O ( m .lg ( m )) และm << n (คุณอาจมีคำเป็นพันล้านคำและไม่ถึงล้านประเภทลองดู) ดังนั้นแม้จะมีอัลกอริทึมแบบจำลอง แต่ก็ยังคงเป็น O ( n + m lg ( m )) = O ( n )
Nikana Reklawyks

1
โปรดเพิ่มข้อสันนิษฐานให้กับคำถามว่าเรามีหน่วยความจำหลักเพียงพอที่จะเก็บคำทั้งหมดของข้อความขนาดใหญ่ มันน่าสนใจถ้าจะดูวิธีการค้นหา k = 100 คำจากไฟล์ 10GB (คือทุกคำจะไม่พอดีกับ RAM 4GB) !!
KGhatak

@KGhatak เราจะทำอย่างไรถ้ามันเกินขนาด RAM?
user7098526

คำตอบ:


67

สามารถทำได้ในเวลา O (n)

แนวทางที่ 1:

ขั้นตอน:

  1. นับคำและแฮชซึ่งจะจบลงในโครงสร้างเช่นนี้

    var hash = {
      "I" : 13,
      "like" : 3,
      "meow" : 3,
      "geek" : 3,
      "burger" : 2,
      "cat" : 1,
      "foo" : 100,
      ...
      ...
    
  2. สำรวจผ่านแฮชและค้นหาคำที่ใช้บ่อยที่สุด (ในกรณีนี้คือ "foo" 100) จากนั้นสร้างอาร์เรย์ขนาดนั้น

  3. จากนั้นเราสามารถสำรวจแฮชอีกครั้งและใช้จำนวนคำที่เกิดขึ้นเป็นดัชนีอาร์เรย์หากไม่มีสิ่งใดในดัชนีให้สร้างอาร์เรย์อื่นต่อท้ายในอาร์เรย์ จากนั้นเราจะจบลงด้วยอาร์เรย์เช่น:

      0   1      2            3                  100
    [[ ],[cat],[burger],[like, meow, geek],[]...[foo]]
    
  4. จากนั้นเพียงแค่สำรวจอาร์เรย์จากส่วนท้ายและรวบรวมคำ k

แนวทางที่ 2:

ขั้นตอน:

  1. เช่นเดียวกับด้านบน
  2. ใช้ min heap และรักษาขนาดของ min heap เป็น k และสำหรับแต่ละคำในแฮชเราจะเปรียบเทียบการเกิดขึ้นของคำกับ min 1) ถ้ามากกว่าค่า min ให้ลบ min (ถ้าขนาดของ min ฮีปเท่ากับ k) และใส่หมายเลขในฮีปขั้นต่ำ 2) พักผ่อนเงื่อนไขง่ายๆ
  3. หลังจากข้ามผ่านอาร์เรย์เราเพียงแค่แปลงฮีปขั้นต่ำเป็นอาร์เรย์และส่งคืนอาร์เรย์

16
โซลูชันของคุณ (1) คือการจัดเรียงถัง O (n) แทนที่การเรียงลำดับเปรียบเทียบ O (n lg n) มาตรฐาน แนวทางของคุณต้องการพื้นที่เพิ่มเติมสำหรับโครงสร้างที่เก็บข้อมูล แต่การเปรียบเทียบสามารถทำได้ในสถานที่ โซลูชันของคุณ (2) ทำงานในเวลา O (n lg k) - นั่นคือ O (n) เพื่อวนซ้ำทุกคำและ O (lg k) เพื่อเพิ่มแต่ละคำลงในฮีป
stackoverflowuser2010

4
วิธีแก้ปัญหาแรกต้องใช้พื้นที่มากขึ้น แต่สิ่งสำคัญคือต้องเน้นว่าแท้จริงแล้ว O (n) ทันเวลา 1: ความถี่แฮชที่คีย์ด้วยคำ O (n); 2: แฮชความถี่ทราเวิร์สสร้างแฮชที่สองที่คีย์ด้วยความถี่ นี่คือ O (n) เพื่อข้ามแฮชและ O (1) เพื่อเพิ่มคำในรายการคำที่ความถี่นั้น 3: แฮชทราเวิร์สลดลงจากความถี่สูงสุดจนกว่าคุณจะกด k มากที่สุด O (n) รวม = 3 * O (n) = O (n)
BringMyCakeBack

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

วิธีแก้ปัญหา # 1 ของคุณไม่ได้ผลเมื่อ k (จำนวนคำที่ใช้บ่อย) น้อยกว่าจำนวนของคำที่เกิดบ่อยที่สุด (เช่น 100 ในกรณีนี้) แน่นอนว่าอาจไม่เกิดขึ้นในทางปฏิบัติ แต่ควร ไม่ถือว่า!
วันทูทรี

@OneTwoThree โซลูชันที่เสนอเป็นเพียงตัวอย่าง จำนวนจะขึ้นอยู่กับความต้องการ
Chihung Yu

22

คุณจะไม่ได้รันไทม์ที่ดีกว่าโซลูชันที่คุณอธิบายไว้ คุณต้องทำงานอย่างน้อย O (n) เพื่อประเมินคำทั้งหมดจากนั้น O (k) ทำงานพิเศษเพื่อค้นหาคำศัพท์อันดับสูงสุด

หากตั้งปัญหาของคุณคือจริงๆใหญ่คุณสามารถใช้วิธีการแก้ปัญหาการกระจายเช่นแผนที่ / ลด ให้คนงานแผนที่ n นับความถี่ใน 1 / nth ของแต่ละข้อความและสำหรับแต่ละคำให้ส่งไปยังหนึ่งในผู้ปฏิบัติงานตัวลด m ซึ่งคำนวณตามแฮชของคำนั้น จากนั้นตัวลดจำนวนจะรวมจำนวน การรวมการเรียงลำดับมากกว่าผลลัพธ์ของตัวลดจะทำให้คุณมีคำที่ได้รับความนิยมมากที่สุดตามลำดับความนิยม


13

การเปลี่ยนแปลงเล็กน้อยในโซลูชันของคุณจะให้อัลกอริทึมO (n)หากเราไม่สนใจเกี่ยวกับการจัดอันดับ K สูงสุดและโซลูชันO (n + k * lg (k))หากเราทำ ฉันเชื่อว่าขอบเขตทั้งสองนี้เหมาะสมที่สุดภายในปัจจัยคงที่

การเพิ่มประสิทธิภาพที่นี่จะเกิดขึ้นอีกครั้งหลังจากที่เราเรียกใช้รายการแทรกลงในตารางแฮช เราสามารถใช้ค่ามัธยฐานของค่ามัธยฐานอัลกอริทึมเพื่อเลือกองค์ประกอบที่ใหญ่ที่สุด Kth ในรายการ อัลกอริทึมนี้สามารถพิสูจน์ได้ว่าเป็น O (n)

หลังจากเลือกองค์ประกอบที่เล็กที่สุด Kth เราแบ่งรายการรอบ ๆ องค์ประกอบนั้นเช่นเดียวกับใน Quicksort เห็นได้ชัดว่านี่คือ O (n) ด้วย ทุกสิ่งที่อยู่ทางด้าน "ซ้าย" ของเดือยอยู่ในกลุ่มขององค์ประกอบ K เราก็ทำเสร็จแล้ว (เราสามารถทิ้งอย่างอื่นไปได้เลยในขณะที่เราดำเนินการไป)

ดังนั้นกลยุทธ์นี้คือ:

  1. ผ่านแต่ละคำและแทรกลงในตารางแฮช: O (n)
  2. เลือกองค์ประกอบที่เล็กที่สุด Kth: O (n)
  3. พาร์ติชันรอบ ๆ องค์ประกอบนั้น: O (n)

หากคุณต้องการจัดอันดับองค์ประกอบ K เพียงจัดเรียงองค์ประกอบเหล่านั้นด้วยการเรียงลำดับการเปรียบเทียบที่มีประสิทธิภาพในเวลา O (k * lg (k)) โดยให้เวลาทำงานทั้งหมดเป็น O (n + k * lg (k))

ขอบเขตเวลา O (n) เหมาะสมที่สุดภายในปัจจัยคงที่เนื่องจากเราต้องตรวจสอบแต่ละคำอย่างน้อยหนึ่งครั้ง

ขอบเขตเวลา O (n + k * lg (k)) ก็เหมาะสมเช่นกันเนื่องจากไม่มีวิธีการเปรียบเทียบเพื่อเรียงลำดับองค์ประกอบ k ในเวลาน้อยกว่า k * lg (k)


เมื่อเราเลือกองค์ประกอบที่เล็กที่สุด Kth สิ่งที่เลือกคือแฮชคีย์ที่เล็กที่สุด Kth ไม่จำเป็นว่าจะต้องมีคำ K ตรงในพาร์ติชันด้านซ้ายของขั้นตอนที่ 3
Prakash Murali

2
คุณจะไม่สามารถเรียกใช้ "ค่ามัธยฐานของค่ามัธยฐาน" บนตารางแฮชได้เนื่องจากมีการแลกเปลี่ยน คุณจะต้องคัดลอกข้อมูลจากตารางแฮชไปยังอาร์เรย์ชั่วคราว ดังนั้นพื้นที่จัดเก็บ O (n) จะได้รับการร้องขอ
user674669

ฉันไม่เข้าใจว่าคุณจะเลือกองค์ประกอบที่เล็กที่สุด K ใน O (n) ได้อย่างไร
Michael Ho Chum

ลองดูอัลกอริทึมสำหรับการค้นหาองค์ประกอบที่เล็กที่สุด Kth ใน O (n) - wikiwand.com/th/Median_of_medians
Piyush

ความซับซ้อนจะเหมือนกันแม้ว่าคุณจะใช้ตารางแฮช + ฮีปขั้นต่ำ ฉันไม่เห็นการเพิ่มประสิทธิภาพใด ๆ
Vinay

8

หาก "รายการคำใหญ่" ของคุณมีขนาดใหญ่พอคุณสามารถสุ่มตัวอย่างและรับค่าประมาณได้ มิฉะนั้นฉันชอบการรวมแฮช

แก้ไข :

ตามตัวอย่างฉันหมายถึงเลือกส่วนย่อยของหน้าและคำนวณคำที่ใช้บ่อยที่สุดในหน้าเหล่านั้น หากคุณเลือกหน้าเว็บด้วยวิธีที่สมเหตุสมผลและเลือกตัวอย่างที่มีนัยสำคัญทางสถิติการประมาณคำที่ใช้บ่อยที่สุดของคุณควรมีความสมเหตุสมผล

วิธีนี้สมเหตุสมผลจริงๆก็ต่อเมื่อคุณมีข้อมูลมากจนการประมวลผลทั้งหมดเป็นเรื่องโง่ ๆ หากคุณมี megs เพียงไม่กี่ตัวคุณควรจะสามารถฉีกข้อมูลและคำนวณคำตอบที่แน่นอนได้โดยไม่ต้องเสียเหงื่อแทนที่จะต้องกังวลกับการคำนวณค่าประมาณ


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

1
+1 สำหรับคำตอบที่ใช้ได้จริงที่ไม่เกี่ยวข้องกับปัญหาความซับซ้อนที่ไม่เกี่ยวข้อง @itsadok: สำหรับการวิ่งแต่ละครั้ง: ถ้ามันใหญ่พอตัวอย่างมัน หากไม่เป็นเช่นนั้นการได้รับปัจจัยบันทึกก็ไม่เกี่ยวข้อง
Nikana Reklawyks

2

คุณสามารถลดเวลาให้สั้นลงได้โดยการแบ่งพาร์ติชันโดยใช้อักษรตัวแรกจากนั้นแบ่งชุดคำหลายคำที่ใหญ่ที่สุดโดยใช้อักขระถัดไปจนกว่าคุณจะมีชุดคำเดี่ยว k คุณจะใช้ต้นไม้แบบ 256 ทิศทางที่มีรายการคำบางส่วน / ทั้งหมดที่ใบไม้ คุณจะต้องระมัดระวังอย่างมากที่จะไม่ทำให้เกิดการคัดลอกสตริงทุกที่

อัลกอริทึมนี้คือ O (m) โดยที่ m คือจำนวนอักขระ หลีกเลี่ยงการพึ่งพา k ซึ่งดีมากสำหรับ k ขนาดใหญ่ [เนื่องจากเวลาทำงานที่โพสต์ของคุณไม่ถูกต้องควรเป็น O (n * lg (k)) และฉันไม่แน่ใจว่าเป็นอย่างไร ม].

หากคุณเรียกใช้อัลกอริทึมทั้งสองข้างกันคุณจะได้รับสิ่งที่ฉันค่อนข้างแน่ใจว่าเป็นอัลกอริทึม O (นาที (m, n * lg (k))) ที่ไม่แสดงอาการ แต่ของฉันควรเร็วกว่าโดยเฉลี่ยเพราะไม่เกี่ยวข้องกับ การแฮชหรือการเรียงลำดับ


7
สิ่งที่คุณกำลังอธิบายเรียกว่า 'trie'
Nick Johnson

สวัสดี Strilanc คุณสามารถอธิบายขั้นตอนการแบ่งพาร์ติชันโดยละเอียดได้หรือไม่?
Morgan Cheng

1
ไม่เกี่ยวกับการเรียงลำดับได้อย่างไร ?? เมื่อคุณมี trie แล้วคุณจะดึงคำ k ที่มีความถี่มากที่สุดได้อย่างไร ไม่สมเหตุสมผล
ธรรมดา

2

คุณมีข้อบกพร่องในคำอธิบายของคุณ: การนับใช้เวลา O (n) แต่การเรียงลำดับใช้ O (m * lg (m)) โดยที่ m คือจำนวนคำที่ไม่ซ้ำกัน โดยปกติจะมีขนาดเล็กกว่าจำนวนคำทั้งหมดดังนั้นจึงควรปรับวิธีสร้างแฮชให้เหมาะสมที่สุด



2

หากสิ่งที่คุณหลังจากเป็นรายการของkคำที่พบบ่อยที่สุดในข้อความของคุณสำหรับการปฏิบัติใด ๆkและ Langage ธรรมชาติใด ๆ แล้วความซับซ้อนของขั้นตอนวิธีการของคุณไม่เกี่ยวข้อง

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

ตามหมายเหตุด้านข้างความซับซ้อนของอัลกอริทึมจำลอง (1. นับทั้งหมด 2. เรียงลำดับการนับ 3. ใช้เวลาที่ดีที่สุด) คือ O (n + m * log (m)) โดยที่ m คือจำนวนคำต่างๆในของคุณ ข้อความ log (m) มีขนาดเล็กกว่า (n / m) มากดังนั้นจึงยังคงเป็น O (n)

ในทางปฏิบัติขั้นตอนที่ยาวนานกำลังนับ


2
  1. ใช้โครงสร้างข้อมูลที่มีประสิทธิภาพของหน่วยความจำเพื่อจัดเก็บคำ
  2. ใช้ MaxHeap เพื่อค้นหาคำที่ใช้บ่อย K อันดับต้น ๆ

นี่คือรหัส

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

import com.nadeem.app.dsa.adt.Trie;
import com.nadeem.app.dsa.adt.Trie.TrieEntry;
import com.nadeem.app.dsa.adt.impl.TrieImpl;

public class TopKFrequentItems {

private int maxSize;

private Trie trie = new TrieImpl();
private PriorityQueue<TrieEntry> maxHeap;

public TopKFrequentItems(int k) {
    this.maxSize = k;
    this.maxHeap = new PriorityQueue<TrieEntry>(k, maxHeapComparator());
}

private Comparator<TrieEntry> maxHeapComparator() {
    return new Comparator<TrieEntry>() {
        @Override
        public int compare(TrieEntry o1, TrieEntry o2) {
            return o1.frequency - o2.frequency;
        }           
    };
}

public void add(String word) {
    this.trie.insert(word);
}

public List<TopK> getItems() {

    for (TrieEntry trieEntry : this.trie.getAll()) {
        if (this.maxHeap.size() < this.maxSize) {
            this.maxHeap.add(trieEntry);
        } else if (this.maxHeap.peek().frequency < trieEntry.frequency) {
            this.maxHeap.remove();
            this.maxHeap.add(trieEntry);
        }
    }
    List<TopK> result = new ArrayList<TopK>();
    for (TrieEntry entry : this.maxHeap) {
        result.add(new TopK(entry));
    }       
    return result;
}

public static class TopK {
    public String item;
    public int frequency;

    public TopK(String item, int frequency) {
        this.item = item;
        this.frequency = frequency;
    }
    public TopK(TrieEntry entry) {
        this(entry.word, entry.frequency);
    }
    @Override
    public String toString() {
        return String.format("TopK [item=%s, frequency=%s]", item, frequency);
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + frequency;
        result = prime * result + ((item == null) ? 0 : item.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        TopK other = (TopK) obj;
        if (frequency != other.frequency)
            return false;
        if (item == null) {
            if (other.item != null)
                return false;
        } else if (!item.equals(other.item))
            return false;
        return true;
    }

}   

}

นี่คือการทดสอบหน่วย

@Test
public void test() {
    TopKFrequentItems stream = new TopKFrequentItems(2);

    stream.add("hell");
    stream.add("hello");
    stream.add("hello");
    stream.add("hello");
    stream.add("hello");
    stream.add("hello");
    stream.add("hero");
    stream.add("hero");
    stream.add("hero");
    stream.add("hello");
    stream.add("hello");
    stream.add("hello");
    stream.add("home");
    stream.add("go");
    stream.add("go");
    assertThat(stream.getItems()).hasSize(2).contains(new TopK("hero", 3), new TopK("hello", 8));
}

สำหรับรายละเอียดเพิ่มเติมโปรดดูกรณีทดสอบนี้


1
  1. ใช้ตารางแฮชเพื่อบันทึกความถี่ของคำทั้งหมดในขณะที่สำรวจลำดับคำทั้งหมด ในขั้นตอนนี้คีย์คือ "word" และค่าคือ "word-frequency" ใช้เวลา O (n) เช่นเดียวกับที่อธิบายข้างต้น

  2. ในขณะที่แทรกตัวเองในแฮชแมปให้เก็บ Treeset (เฉพาะสำหรับ java มีการใช้งานในทุกภาษา) ขนาด 10 (k = 10) เพื่อรักษาคำที่ใช้บ่อย 10 อันดับแรก ขนาดเล็กกว่า 10 ให้เพิ่มไปเรื่อย ๆ ถ้าขนาดเท่ากับ 10 ถ้าองค์ประกอบที่แทรกมีค่ามากกว่าองค์ประกอบขั้นต่ำเช่นองค์ประกอบแรก ถ้าใช่ให้ลบออกและใส่องค์ประกอบใหม่

หากต้องการ จำกัด ขนาดของชุดต้นไม้โปรดดูลิงค์นี้


0

สมมติว่าเรามีลำดับคำ "ad" "ad" "boy" "big" "bad" "com" "come" "cold" และ K = 2 ตามที่คุณกล่าวถึง "การแบ่งพาร์ติชันโดยใช้อักษรตัวแรก" เราได้ ("ad", "ad") ("boy", "big", "bad") ("com" "come" "cold") "แล้ว แบ่งชุดคำหลายคำที่ใหญ่ที่สุดโดยใช้อักขระถัดไปจนกว่าคุณจะมีชุดคำเดี่ยว k " มันจะแบ่งพาร์ติชัน ("boy", "big", "bad") ("com" "come" "cold"), พาร์ติชันแรก ("ad", "ad") ไม่ได้รับในขณะที่ "ad" คือ คำที่ใช้บ่อยที่สุด

บางทีฉันอาจเข้าใจผิดประเด็นของคุณ ขอรายละเอียดกระบวนการเกี่ยวกับพาร์ติชั่นได้ไหม


0

ฉันเชื่อว่าปัญหานี้สามารถแก้ไขได้ด้วยอัลกอริทึม O (n) เราสามารถจัดเรียงได้ทันที กล่าวอีกนัยหนึ่งการเรียงลำดับในกรณีนั้นเป็นปัญหาย่อยของปัญหาการเรียงลำดับแบบเดิมเนื่องจากมีเพียงตัวนับเดียวที่จะเพิ่มขึ้นทีละตัวทุกครั้งที่เราเข้าถึงตารางแฮช ในขั้นต้นรายการจะถูกจัดเรียงเนื่องจากตัวนับทั้งหมดเป็นศูนย์ ในขณะที่เราทำการเพิ่มตัวนับในตารางแฮชเราจะทำการจองอาร์เรย์ของค่าแฮชอื่นโดยเรียงลำดับตามความถี่ดังนี้ ทุกครั้งที่เราเพิ่มตัวนับเราจะตรวจสอบดัชนีในอาร์เรย์ที่จัดอันดับและตรวจสอบว่าจำนวนของมันเกินตัวนับก่อนหน้าในรายการหรือไม่ ถ้าเป็นเช่นนั้นเราสลับสององค์ประกอบนี้ ด้วยเหตุนี้เราจึงได้คำตอบที่มากที่สุด O (n) โดยที่ n คือจำนวนคำในข้อความต้นฉบับ


โดยทั่วไปแล้วเป็นทิศทางที่ดี แต่ก็มีข้อบกพร่อง เมื่อจำนวนเพิ่มขึ้นเราจะไม่เพียงแค่ตรวจสอบ "รุ่นก่อน" แต่เราต้องตรวจสอบ "รุ่นก่อน" ตัวอย่างเช่นมีโอกาสมากที่อาร์เรย์จะเป็น [4,3,1,1,1,1,1,1,1,1,1] - 1 สามารถมีได้มาก - ซึ่งจะทำให้ประสิทธิภาพน้อยลง เนื่องจากเราจะต้องมองย้อนกลับไปในรุ่นก่อน ๆ ทั้งหมดเพื่อค้นหาสิ่งที่เหมาะสมในการแลกเปลี่ยน
Shawn

นี่จะไม่เลวร้ายไปกว่า O (n) หรือ? เหมือน O (n ^ 2) มากกว่าเนื่องจากเป็นการเรียงลำดับที่ค่อนข้างไม่มีประสิทธิภาพ?
dcarr622

สวัสดี Shawn ใช่ฉันเห็นด้วยกับคุณ. แต่ฉันสงสัยว่าปัญหาที่คุณกล่าวถึงเป็นปัญหาพื้นฐาน ในความเป็นจริงถ้าแทนที่จะเก็บอาร์เรย์ของค่าที่เรียงไว้เราสามารถดำเนินการต่อไปข้างหน้าเก็บอาร์เรย์ของคู่ (ค่าดัชนี) โดยที่ดัชนีชี้ไปที่การเกิดขึ้นครั้งแรกขององค์ประกอบที่ซ้ำปัญหาควรแก้ไขได้ใน O (n) เวลา ตัวอย่างเช่น [4,3,1,1,1,1,1,1,1,1,1] จะมีลักษณะดังนี้ [(4,0), (3,1), (1,2), (1 , 2), (1,2, ... , (1,2)]; ดัชนีเริ่มตั้งแต่ 0
Aly Farahat

0

ฉันกำลังดิ้นรนกับเรื่องนี้เช่นกันและได้รับแรงบันดาลใจจาก @aly แทนที่จะจัดเรียงในภายหลังเราสามารถรักษารายการคำList<Set<String>>ที่กำหนดไว้ล่วงหน้า( ) และคำนั้นจะอยู่ในชุดที่ตำแหน่ง X โดยที่ X คือจำนวนคำในปัจจุบัน โดยทั่วไปนี่คือวิธีการทำงาน:

  1. สำหรับแต่ละคำเก็บไว้เป็นส่วนหนึ่งของแผนที่ที่เกิดขึ้น: Map<String, Integer>สำหรับแต่ละคำเก็บไว้เป็นส่วนหนึ่งของแผนที่มันเกิดขึ้น:
  2. จากนั้นให้ลบออกจากชุดการนับก่อนหน้าและเพิ่มลงในชุดการนับใหม่ตามการนับ

ข้อเสียเปรียบของรายการนี้คือรายการอาจใหญ่ - สามารถปรับให้เหมาะสมได้โดยใช้ไฟล์ TreeMap<Integer, Set<String>> - แต่จะเพิ่มค่าใช้จ่ายบางส่วน ในที่สุดเราสามารถใช้ HashMap หรือโครงสร้างข้อมูลของเราเองได้

รหัส

public class WordFrequencyCounter {
    private static final int WORD_SEPARATOR_MAX = 32; // UNICODE 0000-001F: control chars
    Map<String, MutableCounter> counters = new HashMap<String, MutableCounter>();
    List<Set<String>> reverseCounters = new ArrayList<Set<String>>();

    private static class MutableCounter {
        int i = 1;
    }

    public List<String> countMostFrequentWords(String text, int max) {
        int lastPosition = 0;
        int length = text.length();
        for (int i = 0; i < length; i++) {
            char c = text.charAt(i);
            if (c <= WORD_SEPARATOR_MAX) {
                if (i != lastPosition) {
                    String word = text.substring(lastPosition, i);
                    MutableCounter counter = counters.get(word);
                    if (counter == null) {
                        counter = new MutableCounter();
                        counters.put(word, counter);
                    } else {
                        Set<String> strings = reverseCounters.get(counter.i);
                        strings.remove(word);
                        counter.i ++;
                    }
                    addToReverseLookup(counter.i, word);
                }
                lastPosition = i + 1;
            }
        }

        List<String> ret = new ArrayList<String>();
        int count = 0;
        for (int i = reverseCounters.size() - 1; i >= 0; i--) {
            Set<String> strings = reverseCounters.get(i);
            for (String s : strings) {
                ret.add(s);
                System.out.print(s + ":" + i);
                count++;
                if (count == max) break;
            }
            if (count == max) break;
        }
        return ret;
    }

    private void addToReverseLookup(int count, String word) {
        while (count >= reverseCounters.size()) {
            reverseCounters.add(new HashSet<String>());
        }
        Set<String> strings = reverseCounters.get(count);
        strings.add(word);
    }

}

0

ฉันเพิ่งค้นพบวิธีแก้ปัญหาอื่น ๆ สำหรับปัญหานี้ แต่ผมไม่แน่ใจว่าถูก วิธีการแก้:

  1. ใช้ตารางแฮชเพื่อบันทึกความถี่ของคำทั้งหมด T (n) = O (n)
  2. เลือกองค์ประกอบ k แรกของตารางแฮชและเรียกคืนในบัฟเฟอร์เดียว (ซึ่งมีช่องว่าง = k) T (n) = O (k)
  3. ในแต่ละครั้งอันดับแรกเราต้องหาองค์ประกอบขั้นต่ำปัจจุบันของบัฟเฟอร์และเปรียบเทียบองค์ประกอบขั้นต่ำของบัฟเฟอร์กับองค์ประกอบ (n - k) ของตารางแฮชทีละรายการ หากองค์ประกอบของตารางแฮชมีค่ามากกว่าองค์ประกอบขั้นต่ำของบัฟเฟอร์ให้วางขั้นต่ำของบัฟเฟอร์ปัจจุบันและเพิ่มองค์ประกอบของตารางแฮช ดังนั้นทุกครั้งที่เราพบค่าต่ำสุดหนึ่งในบัฟเฟอร์ต้องใช้ T (n) = O (k) และสำรวจตารางแฮชทั้งหมดต้อง T (n) = O (n - k) ดังนั้นความซับซ้อนตลอดเวลาสำหรับกระบวนการนี้คือ T (n) = O ((nk) * k)
  4. หลังจากสำรวจตารางแฮชทั้งหมดผลลัพธ์จะอยู่ในบัฟเฟอร์นี้
  5. ความซับซ้อนตลอดเวลา: T (n) = O (n) + O (k) + O (kn - k ^ 2) = O (kn + n - k ^ 2 + k) เนื่องจาก k มีขนาดเล็กกว่า n โดยทั่วไป ดังนั้นสำหรับการแก้ปัญหานี้ซับซ้อนเวลาที่T (n) = O (kn) นั่นคือเวลาเชิงเส้นเมื่อ k มีค่าน้อยมาก อยู่ใช่ไหม? ฉันไม่แน่ใจจริงๆ

0

พยายามคิดโครงสร้างข้อมูลพิเศษเพื่อแก้ไขปัญหาประเภทนี้ ในกรณีนี้ต้นไม้ชนิดพิเศษเช่น trie เพื่อจัดเก็บสตริงในลักษณะเฉพาะมีประสิทธิภาพมาก หรือวิธีที่สองในการสร้างโซลูชันของคุณเองเช่นการนับคำ ฉันเดาว่า TB ของข้อมูลนี้น่าจะเป็นภาษาอังกฤษดังนั้นเรามีคำประมาณ 600,000 คำโดยทั่วไปดังนั้นจึงเป็นไปได้ที่จะจัดเก็บเฉพาะคำเหล่านั้นและนับว่าสตริงใดที่จะต้องทำซ้ำ + โซลูชันนี้จะต้องใช้ regex เพื่อกำจัดอักขระพิเศษบางตัว วิธีแก้ปัญหาแรกจะเร็วกว่าฉันค่อนข้างแน่ใจ

http://en.wikipedia.org/wiki/Trie


0

นี่เป็นแนวคิดที่น่าสนใจในการค้นหาและฉันสามารถค้นหาบทความนี้ที่เกี่ยวข้องกับ Top-K https://icmi.cs.ucsb.edu/research/tech_reports/reports/2005-23.pd f

นอกจากนี้ยังมีการดำเนินการของมันที่นี่


ลิงค์ของคุณส่งคืน 404
mbdev

0

รหัสที่ง่ายที่สุดเพื่อให้เกิดคำที่ใช้บ่อยที่สุด

 function strOccurence(str){
    var arr = str.split(" ");
    var length = arr.length,temp = {},max; 
    while(length--){
    if(temp[arr[length]] == undefined && arr[length].trim().length > 0)
    {
        temp[arr[length]] = 1;
    }
    else if(arr[length].trim().length > 0)
    {
        temp[arr[length]] = temp[arr[length]] + 1;

    }
}
    console.log(temp);
    var max = [];
    for(i in temp)
    {
        max[temp[i]] = i;
    }
    console.log(max[max.length])
   //if you want second highest
   console.log(max[max.length - 2])
}

0

ในสถานการณ์เหล่านี้ฉันขอแนะนำให้ใช้คุณสมบัติในตัวของ Java เนื่องจากพวกเขาได้รับการทดสอบอย่างดีและมีเสถียรภาพแล้ว ในปัญหานี้ฉันพบการซ้ำของคำโดยใช้โครงสร้างข้อมูล HashMap จากนั้นฉันส่งผลลัพธ์ไปยังอาร์เรย์ของวัตถุ ฉันจัดเรียงวัตถุตาม Arrays.sort () และพิมพ์คำ k ด้านบนและการทำซ้ำ

import java.io.*;
import java.lang.reflect.Array;
import java.util.*;

public class TopKWordsTextFile {

    static class SortObject implements Comparable<SortObject>{

        private String key;
        private int value;

        public SortObject(String key, int value) {
            super();
            this.key = key;
            this.value = value;
        }

        @Override
        public int compareTo(SortObject o) {
            //descending order
            return o.value - this.value;
        }
    }


    public static void main(String[] args) {
        HashMap<String,Integer> hm = new HashMap<>();
        int k = 1;
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("words.in")));

            String line;
            while ((line = br.readLine()) != null) {
                // process the line.
                //System.out.println(line);
                String[] tokens = line.split(" ");
                for(int i=0; i<tokens.length; i++){
                    if(hm.containsKey(tokens[i])){
                        //If the key already exists
                        Integer prev = hm.get(tokens[i]);
                        hm.put(tokens[i],prev+1);
                    }else{
                        //If the key doesn't exist
                        hm.put(tokens[i],1);
                    }
                }
            }
            //Close the input
            br.close();
            //Print all words with their repetitions. You can use 3 for printing top 3 words.
            k = hm.size();
            // Get a set of the entries
            Set set = hm.entrySet();
            // Get an iterator
            Iterator i = set.iterator();
            int index = 0;
            // Display elements
            SortObject[] objects = new SortObject[hm.size()];
            while(i.hasNext()) {
                Map.Entry e = (Map.Entry)i.next();
                //System.out.print("Key: "+e.getKey() + ": ");
                //System.out.println(" Value: "+e.getValue());
                String tempS = (String) e.getKey();
                int tempI = (int) e.getValue();
                objects[index] = new SortObject(tempS,tempI);
                index++;
            }
            System.out.println();
            //Sort the array
            Arrays.sort(objects);
            //Print top k
            for(int j=0; j<k; j++){
                System.out.println(objects[j].key+":"+objects[j].value);
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

สำหรับข้อมูลเพิ่มเติมกรุณาเยี่ยมชมhttps://github.com/m-vahidalizadeh/foundations/blob/master/src/algorithms/TopKWordsTextFile.java ฉันหวังว่ามันจะช่วยได้


วิธีนี้ช่วยปรับปรุงแนวทางที่ร่างไว้ในคำถามอย่างไร (กรุณาอย่าไม่ออกความเห็นจากรหัสที่แสดงอยู่บน SE.) ( I recommend to use Java built-in featuresเช่นลูป foreachและลำธารประมวลผล ?)
greybeard

ดังที่คุณทราบปัจจัยที่สำคัญที่สุดประการหนึ่งในการออกแบบอัลกอริทึมที่มีประสิทธิภาพคือการเลือกโครงสร้างข้อมูลที่เหมาะสม จากนั้นสิ่งสำคัญคือคุณจะจัดการกับปัญหาอย่างไร ตัวอย่างเช่นคุณต้องโจมตีปัญหาโดยการแบ่งและพิชิต คุณต้องโจมตีอีกคนด้วยความโลภ อย่างที่คุณทราบ บริษัท Oracle กำลังทำงานบน Java พวกเขาเป็นหนึ่งใน บริษัท เทคโนโลยีที่ดีที่สุดในโลก มีวิศวกรที่เก่งที่สุดบางคนทำงานอยู่ที่นั่นในคุณสมบัติในตัวของ Java ดังนั้นคุณสมบัติเหล่านี้จึงได้รับการทดสอบอย่างดีและกันกระสุน หากเราสามารถใช้ประโยชน์ได้ควรใช้ในความคิดของฉันจะดีกว่า
Mohammad

0
**

C ++ 11 การดำเนินการตามความคิดข้างต้น

**

class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {

    unordered_map<int,int> map;
    for(int num : nums){
        map[num]++;
    }

    vector<int> res;
    // we use the priority queue, like the max-heap , we will keep (size-k) smallest elements in the queue
    // pair<first, second>: first is frequency,  second is number 
    priority_queue<pair<int,int>> pq; 
    for(auto it = map.begin(); it != map.end(); it++){
        pq.push(make_pair(it->second, it->first));

        // onece the size bigger than size-k, we will pop the value, which is the top k frequent element value 

        if(pq.size() > (int)map.size() - k){
            res.push_back(pq.top().second);
            pq.pop();
        }
    }
    return res;

}

};

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.