Cách hiệu quả nhất để tìm K từ thường gặp hàng đầu trong chuỗi từ lớn


85

Dữ liệu vào: Một số nguyên dương K và một văn bản lớn. Văn bản thực sự có thể được xem như một chuỗi từ. Vì vậy, chúng ta không phải lo lắng về cách chia nó thành chuỗi từ.
Đầu ra: K từ thường gặp nhất trong văn bản.

Suy nghĩ của tôi là như thế này.

  1. sử dụng bảng Hash để ghi lại tần suất của tất cả các từ trong khi duyệt qua toàn bộ chuỗi từ. Trong giai đoạn này, khóa là "từ" và giá trị là "tần suất từ". Điều này mất O (n) thời gian.

  2. sắp xếp cặp (từ, từ-tần số); và chìa khóa là "tần số từ". Điều này mất O (n * lg (n)) thời gian với thuật toán sắp xếp thông thường.

  3. Sau khi sắp xếp, chúng ta chỉ lấy K từ đầu tiên. Điều này mất O (K) thời gian.

Tóm lại, tổng thời gian là O (n + n lg (n) + K) , Vì K chắc chắn nhỏ hơn N nên nó thực sự là O (n lg (n)).

Chúng tôi có thể cải thiện điều này. Trên thực tế, chúng tôi chỉ muốn K từ hàng đầu. Các từ khác 'tần suất không phải là mối quan tâm đối với chúng tôi. Vì vậy, chúng ta có thể sử dụng "phân loại Heap từng phần". Đối với bước 2) và 3), chúng tôi không chỉ thực hiện phân loại. Thay vào đó, chúng tôi thay đổi nó thành

2 ') xây dựng một đống cặp (từ, từ-tần suất) với "từ-tần số" làm khóa. Mất O (n) thời gian để xây dựng một đống;

3 ') trích xuất K từ hàng đầu từ đống. Mỗi lần chiết là O (lg (n)). Vì vậy, tổng thời gian là O (k * lg (n)).

Tóm lại, giải pháp này tốn thời gian là O (n + k * lg (n)).

Đây chỉ là suy nghĩ của tôi. Tôi chưa tìm ra cách để cải thiện bước 1).
Tôi hy vọng một số chuyên gia Truy xuất Thông tin có thể làm sáng tỏ hơn về câu hỏi này.


Bạn sẽ sử dụng sắp xếp hợp nhất hoặc sắp xếp nhanh cho sắp xếp O (n * logn)?
committedandroider

1
Đối với các mục đích sử dụng thực tế, câu trả lời của Aaron Maenpaa là đếm trên một mẫu là tốt nhất. Nó không giống như những từ thường xuyên nhất sẽ ẩn khỏi mẫu của bạn. Đối với những người thích phức tạp, đó là O (1) vì kích thước của mẫu là cố định. Bạn không nhận được số lượng chính xác, nhưng bạn cũng không yêu cầu chúng.
Nikana Reklawyks

Nếu những gì bạn muốn là xem xét phân tích độ phức tạp của mình, thì tốt hơn tôi nên đề cập đến: nếu n là số từ trong văn bản của bạn và m là số từ khác nhau (loại, chúng tôi gọi chúng), bước 1 là O ( n ), nhưng bước 2 là O ( m .lg ( m )) và m << n (bạn có thể có hàng tỷ từ và không đạt được một triệu loại, hãy thử nó). Vì vậy, ngay cả với một thuật toán giả, nó vẫn là O ( n + m lg ( m )) = O ( n ).
Nikana Reklawyks

1
Xin thêm một giả định cho câu hỏi rằng chúng ta có đủ bộ nhớ chính để chứa tất cả các từ của văn bản lớn. Sẽ rất thú vị khi xem các phương pháp tìm k = 100 từ từ tệp 10GB (tức là tất cả các từ sẽ không vừa trong 4GB RAM) !!
KGhatak

@KGhatak chúng tôi sẽ làm như thế nào nếu nó vượt quá kích thước RAM?
user7098526

Câu trả lời:


66

Điều này có thể được thực hiện trong O (n) thời gian

Giải pháp 1:

Các bước:

  1. Đếm từ và băm nó, sẽ có cấu trúc như thế này

    var hash = {
      "I" : 13,
      "like" : 3,
      "meow" : 3,
      "geek" : 3,
      "burger" : 2,
      "cat" : 1,
      "foo" : 100,
      ...
      ...
    
  2. Duyệt qua hàm băm và tìm từ được sử dụng thường xuyên nhất (trong trường hợp này là "foo" 100), sau đó tạo mảng có kích thước đó

  3. Sau đó, chúng ta có thể duyệt lại hàm băm và sử dụng số lần xuất hiện của các từ làm chỉ số mảng, nếu không có gì trong chỉ mục, hãy tạo một mảng khác nối nó vào mảng. Sau đó, chúng tôi kết thúc với một mảng như:

      0   1      2            3                  100
    [[ ],[cat],[burger],[like, meow, geek],[]...[foo]]
    
  4. Sau đó, chỉ cần duyệt qua mảng từ cuối và thu thập k từ.

Giải pháp 2:

Các bước:

  1. Giống như trên
  2. Sử dụng min heap và giữ kích thước của min heap thành k và đối với mỗi từ trong hàm băm, chúng tôi so sánh số lần xuất hiện của các từ với min, 1) nếu nó lớn hơn giá trị min, hãy loại bỏ min (nếu kích thước của min heap bằng k) và chèn số vào heap tối thiểu. 2) điều kiện đơn giản còn lại.
  3. Sau khi duyệt qua mảng, chúng ta chỉ cần chuyển đổi min heap thành mảng và trả về mảng.

16
Giải pháp (1) của bạn là sắp xếp theo nhóm O (n) thay thế cho sắp xếp so sánh O (n lg n) tiêu chuẩn. Cách tiếp cận của bạn yêu cầu không gian bổ sung cho cấu trúc nhóm, nhưng có thể thực hiện sắp xếp so sánh tại chỗ. Giải pháp của bạn (2) chạy trong thời gian O (n lg k) - nghĩa là, O (n) để lặp lại tất cả các từ và O (lg k) để thêm từng từ vào đống.
stackoverflowuser2010

4
Giải pháp đầu tiên đòi hỏi nhiều không gian hơn, nhưng điều quan trọng cần nhấn mạnh là nó thực tế là O (n) trong thời gian. 1: Các tần số băm được khóa theo từ, O (n); 2: Băm tần số ngang, tạo hàm băm thứ hai được khóa theo tần số. Đây là O (n) để duyệt qua hàm băm và O (1) để thêm một từ vào danh sách các từ ở tần số đó. 3: Đảo ngược băm xuống từ tần số tối đa cho đến khi bạn đạt k. Nhiều nhất là O (n). Tổng = 3 * O (n) = O (n).
BringMyCakeBack,

3
Thông thường khi đếm từ, số lượng nhóm của bạn trong giải pháp 1 được đánh giá quá cao (vì từ xuất hiện nhiều nhất thường xuyên hơn nhiều so với từ tốt nhất thứ hai và thứ ba), do đó, mảng của bạn thưa thớt và không hiệu quả.
Nikana Reklawyks

Giải pháp số 1 của bạn không hoạt động khi k (số lượng từ thường xuyên) ít hơn số lần xuất hiện của từ thường xuyên nhất (ví dụ: 100 trong trường hợp này) Tất nhiên, điều đó có thể không xảy ra trong thực tế, nhưng bạn nên không cho rằng!
Một Hai Ba,

@OneTwoThree giải pháp được đề xuất chỉ là một ví dụ. Số lượng sẽ dựa trên nhu cầu.
Chihung Yu

22

Nhìn chung, bạn sẽ không nhận được thời gian chạy tốt hơn so với giải pháp bạn đã mô tả. Bạn phải làm ít nhất O (n) công việc để đánh giá tất cả các từ, và sau đó thêm O (k) công việc để tìm k số hạng đầu.

Nếu bộ vấn đề của bạn thực sự lớn, bạn có thể sử dụng một giải pháp phân tán như bản đồ / thu nhỏ. Yêu cầu n nhân viên bản đồ đếm tần số trên 1 / n của mỗi văn bản và đối với mỗi từ, hãy gửi nó đến một trong số m nhân viên rút gọn được tính toán dựa trên băm của từ đó. Các bộ giảm sau đó tổng các số đếm. Sắp xếp hợp nhất trên đầu ra của bộ giảm sẽ cung cấp cho bạn các từ phổ biến nhất theo thứ tự phổ biến.


13

Một biến thể nhỏ trong giải pháp của bạn tạo ra thuật toán O (n) nếu chúng ta không quan tâm đến việc xếp hạng K hàng đầu và giải pháp O (n + k * lg (k)) nếu chúng ta làm như vậy. Tôi tin rằng cả hai giới hạn này đều tối ưu trong một hệ số không đổi.

Việc tối ưu hóa ở đây sẽ xuất hiện trở lại sau khi chúng tôi chạy qua danh sách, chèn vào bảng băm. Chúng ta có thể sử dụng thuật toán trung vị của trung vị để chọn phần tử lớn nhất thứ K trong danh sách. Thuật toán này có thể là O (n).

Sau khi chọn phần tử nhỏ nhất thứ K, chúng ta phân vùng danh sách xung quanh phần tử đó giống như trong quicksort. Đây rõ ràng cũng là O (n). Bất kỳ thứ gì ở phía "bên trái" của trục xoay đều nằm trong nhóm K của chúng ta, vì vậy chúng ta đã hoàn thành (chúng ta có thể đơn giản vứt bỏ mọi thứ khác khi chúng ta tiếp tục).

Vì vậy, chiến lược này là:

  1. Xem qua từng từ và chèn nó vào bảng băm: O (n)
  2. Chọn phần tử nhỏ nhất thứ K: O (n)
  3. Phân vùng xung quanh phần tử đó: O (n)

Nếu bạn muốn xếp hạng K phần tử, chỉ cần sắp xếp chúng với bất kỳ sắp xếp so sánh hiệu quả nào theo thời gian O (k * lg (k)), mang lại tổng thời gian chạy là O (n + k * lg (k)).

Giới hạn thời gian O (n) là tối ưu trong một hệ số không đổi vì chúng ta phải kiểm tra từng từ ít nhất một lần.

Giới hạn thời gian O (n + k * lg (k)) cũng là tối ưu vì không có cách nào dựa trên so sánh để sắp xếp k phần tử trong thời gian ít hơn k * lg (k).


Khi chúng ta chọn phần tử nhỏ nhất thứ K, thứ được chọn là khóa băm nhỏ nhất thứ K. Không nhất thiết phải có chính xác K từ trong phân vùng bên trái của Bước 3.
Prakash Murali

2
Bạn sẽ không thể chạy "trung bình của các phương tiện" trên bảng băm vì nó hoán đổi. Bạn sẽ phải sao chép dữ liệu từ bảng băm sang một mảng tạm thời. Vì vậy, lưu trữ O (n) sẽ được yêu cầu.
user674669

Tôi không hiểu làm thế nào bạn có thể chọn phần tử nhỏ nhất thứ K trong O (n)?
Michael Ho Chum

Kiểm tra điều này để biết thuật toán tìm phần tử nhỏ nhất thứ K trong O (n) - wikiwand.com/en/Median_of_medians
Piyush

Độ phức tạp là như nhau ngay cả khi bạn sử dụng bảng băm + min heap. tôi không thấy bất kỳ tối ưu hóa nào.
Vinay

8

Nếu "danh sách từ lớn" của bạn đủ lớn, bạn có thể chỉ cần lấy mẫu và nhận ước tính. Nếu không, tôi thích tổng hợp băm.

Chỉnh sửa :

Theo mẫu, tôi có nghĩa là chọn một số tập hợp con của các trang và tính toán từ thường xuyên nhất trong các trang đó. Miễn là bạn chọn các trang một cách hợp lý và chọn một mẫu có ý nghĩa thống kê, ước tính của bạn về các từ thường xuyên nhất phải hợp lý.

Cách tiếp cận này chỉ thực sự hợp lý nếu bạn có quá nhiều dữ liệu mà việc xử lý tất cả chỉ là một việc ngớ ngẩn. Nếu bạn chỉ có một vài megs, bạn sẽ có thể xé toạc dữ liệu và tính toán một câu trả lời chính xác mà không phải đổ mồ hôi thay vì bận tâm đến việc tính toán một ước tính.


Đôi khi bạn phải làm điều này nhiều lần, chẳng hạn như nếu bạn đang cố gắng lấy danh sách các từ thường gặp trên mỗi trang web hoặc mỗi chủ đề. Trong trường hợp đó, "không đổ một giọt mồ hôi" không thực sự cắt giảm nó. Bạn vẫn cần phải tìm cách để làm điều đó một cách hiệu quả nhất có thể.
itsadok,

1
+1 cho một câu trả lời thiết thực không giải quyết các vấn đề phức tạp không liên quan. @itsadok: Đối với mỗi lần chạy: nếu nó đủ lớn, hãy lấy mẫu nó; nếu không, thì việc đạt được hệ số log là không liên quan.
Nikana Reklawyks

2

Bạn có thể cắt giảm thời gian hơn nữa bằng cách phân vùng bằng cách sử dụng chữ cái đầu tiên của từ, sau đó phân vùng tập hợp nhiều từ lớn nhất bằng cách sử dụng ký tự tiếp theo cho đến khi bạn có k bộ từ đơn. Bạn sẽ sử dụng một cây sắp xếp 256 chiều với danh sách các từ một phần / hoàn chỉnh ở các lá. Bạn sẽ cần phải rất cẩn thận để không gây ra các bản sao chuỗi ở khắp mọi nơi.

Thuật toán này là O (m), với m là số ký tự. Nó tránh sự phụ thuộc vào k, điều này rất tốt cho k lớn [do thời gian chạy đã đăng của bạn bị sai, nó phải là O (n * lg (k)), và tôi không chắc đó là gì về m].

Nếu bạn chạy cả hai thuật toán cạnh nhau, bạn sẽ nhận được điều mà tôi khá chắc chắn là thuật toán O (min (m, n * lg (k)))) tiệm cận tối ưu, nhưng trung bình của tôi sẽ nhanh hơn vì nó không liên quan đến băm hoặc sắp xếp.


7
Những gì bạn đang mô tả được gọi là 'trie'.
Nick Johnson

Xin chào Strilanc. Bạn có thể giải thích quá trình phân vùng chi tiết?
Morgan Cheng

1
làm thế nào để điều này không liên quan đến phân loại ?? một khi bạn đã có trie, làm thế nào để bạn rút ra k từ có tần số lớn nhất. không có ý nghĩa gì
bình thường

2

Bạn có một lỗi trong mô tả của mình: Đếm mất O (n) thời gian, nhưng sắp xếp mất O (m * lg (m)), trong đó m là số duy nhất . Con số này thường nhỏ hơn nhiều so với tổng số từ, vì vậy có lẽ chỉ nên tối ưu hóa cách tạo hàm băm.



2

Nếu những gì bạn đang theo đuổi là danh sách k từ thường xuyên nhất trong văn bản của bạn cho bất kỳ k và cho bất kỳ ngôn ngữ tự nhiên nào, thì độ phức tạp của thuật toán của bạn là không phù hợp.

Chỉ cần lấy mẫu , ví dụ , một vài triệu từ từ văn bản của bạn, xử lý điều đó bằng bất kỳ thuật toán nào trong vài giây và số lượng thường xuyên nhất sẽ rất chính xác.

Lưu ý thêm, độ phức tạp của thuật toán giả (1. đếm tất cả 2. sắp xếp các số đếm 3. lấy tốt nhất) là O (n + m * log (m)), trong đó m là số từ khác nhau trong bản văn. log (m) nhỏ hơn nhiều so với (n / m) nên nó vẫn là O (n).

Thực tế, bước dài đang được đếm.


2
  1. Sử dụng cấu trúc dữ liệu hiệu quả trong bộ nhớ để lưu trữ các từ
  2. Sử dụng MaxHeap, để tìm K từ thường xuyên hàng đầu.

Đây là mã

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

}   

}

Đây là bài kiểm tra đơn vị

@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));
}

Để biết thêm chi tiết, hãy tham khảo trường hợp thử nghiệm này


1
  1. sử dụng bảng Hash để ghi lại tần suất của tất cả các từ trong khi duyệt qua toàn bộ chuỗi từ. Trong giai đoạn này, khóa là "từ" và giá trị là "tần suất từ". Điều này mất O (n) thời gian, giống như mọi giải thích ở trên

  2. Trong khi tự chèn trong hashmap, hãy giữ Treeset (cụ thể cho java, có các triển khai ở mọi ngôn ngữ) ở kích thước 10 (k = 10) để giữ 10 từ thường xuyên nhất. Cho đến khi kích thước nhỏ hơn 10, hãy tiếp tục thêm nó. Nếu kích thước bằng 10, nếu phần tử được chèn lớn hơn phần tử tối thiểu tức là phần tử đầu tiên. Nếu có, hãy xóa nó và chèn phần tử mới

Để hạn chế kích thước của bộ cây, hãy xem liên kết này


0

Giả sử chúng ta có một chuỗi từ "ad" "ad" "boy" "big" "bad" "com" "come" "cold". Và K = 2. như bạn đã đề cập đến "phân vùng bằng cách sử dụng chữ cái đầu tiên của từ", chúng tôi đã nhận được ("ad", "ad") ("boy", "big", "bad") ("com" "come" "cold") "sau đó phân vùng tập hợp nhiều từ lớn nhất bằng cách sử dụng ký tự tiếp theo cho đến khi bạn có k tập hợp từ đơn. " nó sẽ phân vùng ("boy", "big", "bad") ("com" "come" "cold"), phân vùng đầu tiên ("ad", "ad") bị bỏ lỡ, trong khi "ad" thực sự là từ thường xuyên nhất.

Có lẽ tôi hiểu sai ý của bạn. Bạn có thể vui lòng nêu chi tiết quá trình của bạn về phân vùng?


0

Tôi tin rằng vấn đề này có thể được giải quyết bằng một thuật toán O (n). Chúng tôi có thể sắp xếp một cách nhanh chóng. Nói cách khác, việc sắp xếp trong trường hợp đó là một vấn đề phụ của vấn đề sắp xếp truyền thống vì chỉ có một bộ đếm được tăng lên một lần mỗi khi chúng ta truy cập bảng băm. Ban đầu, danh sách được sắp xếp vì tất cả các bộ đếm đều bằng không. Khi chúng ta tiếp tục tăng dần các bộ đếm trong bảng băm, chúng ta lưu giữ một mảng giá trị băm khác được sắp xếp theo tần suất như sau. Mỗi khi chúng tôi tăng một bộ đếm, chúng tôi kiểm tra chỉ số của nó trong mảng được xếp hạng và kiểm tra xem số lượng của nó có vượt quá giá trị tiền nhiệm của nó trong danh sách hay không. Nếu vậy, chúng tôi hoán đổi hai yếu tố này. Như vậy, chúng ta thu được một giải pháp có nhiều nhất là O (n) với n là số từ trong văn bản gốc.


Đây thường là một hướng đi tốt - nhưng nó có một lỗ hổng. khi số lượng được tăng lên, chúng tôi sẽ không chỉ kiểm tra "người tiền nhiệm của nó", mà chúng ta sẽ cần kiểm tra "người tiền nhiệm". ví dụ: có khả năng lớn là mảng sẽ là [4,3,1,1,1,1,1,1,1,1,1] - số 1 có thể là nhiều - điều đó sẽ làm cho nó kém hiệu quả hơn vì chúng tôi sẽ phải nhìn lại tất cả các phiên bản tiền nhiệm để tìm cái thích hợp để hoán đổi.
Shawn

Thực tế điều này sẽ không tệ hơn O (n) sao? Giống O (n ^ 2) hơn vì nó về cơ bản là một loại khá kém hiệu quả?
dcarr622

Chào Shawn. Vâng tôi đồng ý với bạn. Nhưng tôi ngờ rằng vấn đề bạn đề cập là cơ bản của vấn đề. Trên thực tế, nếu thay vì chỉ giữ một mảng giá trị đã được sắp xếp, chúng ta có thể tiếp tục giữ một mảng các cặp (giá trị, chỉ mục), trong đó chỉ mục trỏ đến lần xuất hiện đầu tiên của phần tử lặp lại, vấn đề sẽ có thể giải quyết được trong O (n) thời gian. Ví dụ: [4,3,1,1,1,1,1,1,1,1,1] sẽ giống như [(4,0), (3,1), (1,2), (1 , 2), (1,2, ..., (1,2)]; các chỉ số bắt đầu từ 0.
Aly Farahat

0

Tôi cũng đang đấu tranh với điều này và lấy cảm hứng từ @aly. Thay vì sắp xếp sau đó, chúng ta chỉ có thể duy trì một danh sách các từ được sắp xếp trước ( List<Set<String>>) và từ đó sẽ nằm trong tập hợp ở vị trí X trong đó X là số từ hiện tại. Nói chung, đây là cách nó hoạt động:

  1. cho mỗi từ, lưu nó như một phần của bản đồ của nó xảy ra: Map<String, Integer>.
  2. sau đó, dựa trên số đếm, hãy xóa nó khỏi tập đếm trước đó và thêm nó vào tập đếm mới.

Hạn chế của điều này là danh sách có thể lớn - có thể được tối ưu hóa bằng cách sử dụng TreeMap<Integer, Set<String>>- nhưng điều này sẽ thêm một số chi phí. Cuối cùng, chúng ta có thể sử dụng kết hợp HashMap hoặc cấu trúc dữ liệu của riêng chúng ta.

Mật mã

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

Tôi chỉ tìm ra giải pháp khác cho vấn đề này. Nhưng tôi không chắc nó là đúng. Giải pháp:

  1. Sử dụng bảng băm để ghi lại tần suất của tất cả các từ T (n) = O (n)
  2. Chọn k phần tử đầu tiên của bảng băm và khôi phục chúng trong một bộ đệm (có khoảng trắng = k). T (n) = O (k)
  3. Mỗi lần, trước hết chúng ta cần tìm phần tử min hiện tại của bộ đệm và chỉ cần so sánh phần tử min của bộ đệm với (n - k) phần tử của bảng băm lần lượt. Nếu phần tử của bảng băm lớn hơn phần tử tối thiểu này của bộ đệm, thì hãy loại bỏ tối thiểu của bộ đệm hiện tại và thêm phần tử của bảng băm. Vì vậy, mỗi khi chúng ta tìm thấy một min trong bộ đệm cần T (n) = O (k), và duyệt qua toàn bộ bảng băm cần T (n) = O (n - k). Vì vậy, độ phức tạp toàn bộ thời gian cho quá trình này là T (n) = O ((nk) * k).
  4. Sau khi duyệt qua toàn bộ bảng băm, kết quả nằm trong bộ đệm này.
  5. Độ phức tạp toàn thời gian: T (n) = O (n) + O (k) + O (kn - k ^ 2) = O (kn + n - k ^ 2 + k). Vì k thực sự nhỏ hơn n nói chung. Vì vậy, đối với giải pháp này, độ phức tạp thời gian là T (n) = O (kn) . Đó là thời gian tuyến tính, khi k thực sự nhỏ. Đúng không? Tôi thực sự không chắc chắn.

0

Hãy thử nghĩ về cấu trúc dữ liệu đặc biệt để tiếp cận loại vấn đề này. Trong trường hợp này, loại cây đặc biệt như cây trie để lưu trữ chuỗi theo cách cụ thể, rất hiệu quả. Hoặc cách thứ hai để xây dựng giải pháp của riêng bạn như đếm từ. Tôi đoán TB dữ liệu này sẽ là tiếng Anh thì nói chung chúng ta có khoảng 600.000 từ nên có thể chỉ lưu những từ đó và đếm chuỗi nào sẽ được lặp lại + giải pháp này sẽ cần regex để loại bỏ một số ký tự đặc biệt. Giải pháp đầu tiên sẽ nhanh hơn, tôi khá chắc chắn.

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



0

Mã đơn giản nhất để nhận được sự xuất hiện của từ được sử dụng thường xuyên nhất.

 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

Trong những tình huống này, tôi khuyên bạn nên sử dụng các tính năng tích hợp sẵn của Java. Vì chúng đã được thử nghiệm tốt và ổn định. Trong bài toán này, tôi tìm sự lặp lại của các từ bằng cách sử dụng cấu trúc dữ liệu HashMap. Sau đó, tôi đẩy kết quả vào một mảng đối tượng. Tôi sắp xếp đối tượng theo Arrays.sort () và in k từ hàng đầu và sự lặp lại của chúng.

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();
        }
    }

}

Để biết thêm thông tin, vui lòng truy cập https://github.com/m-vahidalizadeh/foundations/blob/master/src/algorithm/TopKWordsTextFile.java . Tôi hy vọng nó sẽ giúp.


Điều này cải thiện theo cách nào đối với cách tiếp cận được phác thảo trong câu hỏi? (Xin đừng không bỏ qua ý kiến từ các mã trình bày trên SE.) ( I recommend to use Java built-in featuresNhư vòng foreachsuối chế biến ?)
lọ

Như bạn đã biết, một trong những yếu tố quan trọng nhất để thiết kế một thuật toán hiệu quả là chọn đúng cấu trúc dữ liệu. Khi đó, quan trọng là cách bạn tiếp cận vấn đề như thế nào. Ví dụ, bạn cần phải tấn công một vấn đề bằng cách chia và chinh phục. Bạn cần phải tấn công một con khác bằng cách tham lam. Như bạn đã biết công ty Oracle đang làm việc trên Java. Họ là một trong những công ty công nghệ tốt nhất trên thế giới. Có một số kỹ sư giỏi nhất đang làm việc trên các tính năng tích hợp sẵn của Java. Vì vậy, các tính năng này đã được thử nghiệm tốt và có khả năng chống đạn. Nếu chúng ta có thể sử dụng chúng, tốt hơn là sử dụng chúng theo ý kiến ​​của tôi.
Mohammad

0
**

C ++ 11 Thực hiện ý nghĩ trên

**

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;

}

}

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.