Büyük Bir Kelime Dizisinde En Sık Kullanılan K Kelimeleri Bulmanın En Etkin Yolu


86

Girdi: Pozitif bir tam sayı K ve büyük bir metin. Metin aslında kelime dizisi olarak görülebilir. Dolayısıyla, onu kelime dizisine nasıl ayıracağımız konusunda endişelenmemize gerek yok.
Çıktı: Metinde en sık kullanılan K kelime.

Benim düşüncem böyledir.

  1. Tüm kelime dizisini dolaşırken tüm kelimelerin sıklığını kaydetmek için bir Karma tablosu kullanın. Bu aşamada anahtar "kelime" ve değer "kelime frekansı" dır. Bu O (n) süresi alır.

  2. (kelime, kelime frekansı) çiftini sıralayın; ve anahtar "kelime frekansı" dır. Bu, normal sıralama algoritması ile O (n * lg (n)) süresi alır.

  3. Sıraladıktan sonra ilk K kelimeyi alıyoruz. Bu O (K) süresi alır.

Özetlemek gerekirse, toplam süre O (n + n lg (n) + K) , K kesinlikle N'den daha küçük olduğu için aslında O (n lg (n)) dir.

Bunu geliştirebiliriz. Aslında, sadece en iyi K kelimeleri istiyoruz. Diğer kelimelerin frekansı bizi ilgilendirmez. Bu nedenle, "kısmi Yığın sıralama" kullanabiliriz. Adım 2) ve 3) için, sadece sıralama yapmıyoruz. Bunun yerine, onu

2 ') anahtar olarak "kelime frekansı" ile bir yığın (kelime, kelime frekansı) çifti oluşturun. Bir yığın oluşturmak O (n) zaman alır;

3 ') yığından en iyi K kelimeyi çıkarın. Her bir ekstraksiyon O (lg (n)) 'dir. Yani, toplam süre O (k * lg (n)).

Özetlemek gerekirse, bu çözümün maliyeti O (n + k * lg (n)).

Bu sadece benim düşüncem. 1. adımı iyileştirmenin bir yolunu bulamadım).
Umarım bazı Bilgi Erişim uzmanları bu soruya daha fazla ışık tutabilirler.


O (n * logn) sıralaması için birleştirme sıralaması veya hızlı sıralama kullanır mısınız?
committedandroider

1
Pratik kullanımlar için, Aaron Maenpaa'nın bir örneğe güvenme cevabı en iyisidir. Örneğinizden en sık kullanılan kelimelerin saklanacağı gibi değil . Karmaşıklık meraklıları için, örneklem boyutu sabit olduğu için O (1). Tam sayıları anlamıyorsun, ama onları da istemiyorsun.
Nikana Reklawyks

İstediğiniz şey karmaşıklık analizinizin bir incelemesi ise, şunu belirtmeliyim: eğer n metninizdeki kelimelerin sayısı ve m farklı kelimelerin sayısı ise (türler, biz onları çağırıyoruz), 1. adım O ( n ), ancak 2. adım O ( m .lg ( m )) ve m << n'dir (milyarlarca kelimeye sahip olabilirsiniz ve bir milyon türe ulaşamayabilirsiniz, deneyin). Yani, sahte bir algoritmayla bile, hala O ( n + m lg ( m )) = O ( n ).
Nikana Reklawyks

1
Pls, soruya, büyük metnin tüm kelimelerini tutmak için yeterli ana belleğe sahip olduğumuz varsayımını ekler. 10GB dosyadan k = 100 kelime bulma yaklaşımlarını görmek ilginç olurdu (yani tüm kelimeler 4GB RAM'e sığmaz) !!
KGhatak

@KGhatak RAM boyutunu aşarsa nasıl yaparız?
user7098526

Yanıtlar:


67

Bu, O (n) zamanında yapılabilir

1.Çözüm:

Adımlar:

  1. Kelimeleri sayın ve hashleyin, bu da böyle bir yapıya dönüşecek

    var hash = {
      "I" : 13,
      "like" : 3,
      "meow" : 3,
      "geek" : 3,
      "burger" : 2,
      "cat" : 1,
      "foo" : 100,
      ...
      ...
    
  2. Karmanın üzerinden geçin ve en sık kullanılan kelimeyi (bu durumda "foo" 100) bulun, ardından bu boyutta bir dizi oluşturun

  3. Sonra hash'i tekrar gezebilir ve kelimelerin oluşum sayısını dizi indeksi olarak kullanabiliriz, eğer indekste hiçbir şey yoksa, diziye ekleyen bir dizi yaratırız. Sonra şöyle bir dizi elde ederiz:

      0   1      2            3                  100
    [[ ],[cat],[burger],[like, meow, geek],[]...[foo]]
    
  4. Sonra diziyi sondan çaprazlayın ve k kelimelerini toplayın.

2.Çözüm:

Adımlar:

  1. Yukarıdaki gibi
  2. Min yığın kullanın ve min yığını boyutunu k olarak tutun ve karmadaki her kelime için kelimelerin oluşumlarını min, 1) ile karşılaştırırız) min değerinden büyükse, min değerini kaldırın (min. öbek k) 'ye eşittir ve sayıyı min. yığınına ekleyin. 2) basit koşullara uyun.
  3. Dizide dolaştıktan sonra, min yığınını diziye dönüştürüp diziyi döndürüyoruz.

16
Çözümünüz (1), standart bir O (n lg n) karşılaştırma sıralamasının yerini alan bir O (n) kova sıralamasıdır. Yaklaşımınız, kova yapısı için ek alan gerektirir, ancak karşılaştırma sıralamaları yerinde yapılabilir. Çözümünüz (2), O (n lg k) zamanında çalışır - yani, tüm kelimeleri yinelemek için O (n) ve her birini yığına eklemek için O (lg k).
stackoverflowuser2010

4
İlk çözüm daha fazla alan gerektirir, ancak aslında zaman içinde O (n) olduğunu vurgulamak önemlidir. 1: Kelime ile anahtarlanmış karma frekanslar, O (n); 2: Çapraz frekans karması, frekansa göre anahtarlanmış ikinci karma oluştur. Bu, hash'i geçmek için O (n) ve o frekanstaki kelimeler listesine bir kelime eklemek için O (1) 'dir. 3: Hash'i maksimum frekanstan k'ye basana kadar çaprazlayın. En çok O (n). Toplam = 3 * O (n) = O (n).
BringMyCakeBack

3
Tipik olarak, kelimeleri sayarken, çözüm 1'deki kova sayınız büyük ölçüde fazla tahmin edilir (çünkü en sık kullanılan kelime ikinci ve üçüncü en iyi kelimeden çok daha sıktır), bu nedenle diziniz seyrek ve verimsizdir.
Nikana Reklawyks

1 numaralı çözümünüz, k (sık kullanılan kelimelerin sayısı) en sık kullanılan kelimeden daha az olduğunda işe yaramaz (yani, bu durumda, 100) Elbette, pratikte bu olmayabilir, ancak biri varsaymayın!
Bir İki Üç

@OneTwoThree önerilen çözüm sadece bir örnektir. Numara talebe göre belirlenir.
Chihung Yu

22

Genel olarak tarif ettiğiniz çözümden daha iyi bir çalışma süresi elde edemezsiniz. Tüm kelimeleri değerlendirmek için en az O (n) çalışması yapmalı ve ardından en önemli k terimleri bulmak için O (k) fazladan çalışma yapmalısınız.

Problem setiniz ise gerçekten büyükse, haritalama / küçültme gibi dağıtılmış bir çözüm kullanabilirsiniz. Her bir metnin 1 / n'inde n harita çalışanının frekansları saymasını sağlayın ve her kelime için, kelimenin karma değerine göre hesaplanan m redüktör işçisinden birine gönderin. İndirgeyiciler daha sonra sayıları toplar. Düşürücülerin çıktıları üzerinde birleştirme sıralaması, size popülerlik sırasına göre en popüler kelimeleri verecektir.


13

Çözümünüzdeki küçük bir varyasyon , ilk K'yi sıralamayı umursamıyorsak bir O (n) algoritması ve yaparsak bir O (n + k * lg (k)) çözümü verir. Bu sınırların her ikisinin de sabit bir faktör içinde optimal olduğuna inanıyorum.

Buradaki optimizasyon, listeyi gözden geçirip hash tablosuna ekledikten sonra tekrar gelir. Biz kullanabilirsiniz medyan ortancasını listesinde Kth en büyük elemanı seçmek için Algoritma. Bu algoritma kanıtlanabilir şekilde O (n) 'dir.

Kth en küçük elemanı seçtikten sonra, hızlı sıralamada olduğu gibi listeyi o elemanın etrafına böleriz. Bu da açıkça O (n). Pivotun "sol" tarafındaki herhangi bir şey K öğeleri grubumuzdadır, yani işimiz biter (ilerledikçe diğer her şeyi atabiliriz).

Yani bu strateji:

  1. Her kelimeyi gözden geçirin ve bir hash tablosuna ekleyin: O (n)
  2. K'inci en küçük elemanı seçin: O (n)
  3. Bu elementin etrafındaki bölüm: O (n)

K öğelerini sıralamak istiyorsanız, bunları herhangi bir verimli karşılaştırma sıralamasıyla O (k * lg (k)) zamanına göre sıralayarak toplam çalışma süresi O (n + k * lg (k)) elde edin.

O (n) zaman sınırı, sabit bir faktör içinde optimaldir çünkü her kelimeyi en az bir kez incelemeliyiz.

O (n + k * lg (k)) zaman sınırı da optimaldir çünkü k elemanlarını k * lg (k) süresinden daha kısa sürede sıralamanın karşılaştırmaya dayalı bir yolu yoktur.


Kth en küçük elemanı seçtiğimizde, seçilen Kth en küçük karma anahtardır. 3. Adımın sol bölümünde tam olarak K kelimelerinin olması gerekli değildir.
Prakash Murali

2
Değişimler yaptığı için hash tablosunda "medyanların medyanlarını" çalıştıramazsınız. Verileri hash tablosundan bir geçici diziye kopyalamanız gerekir. Yani, O (n) depolama gerekli olacaktır.
user674669

O (n) 'de K'inci en küçük elemanı nasıl seçebilirsin anlamıyorum?
Michael Ho Chum

O (n) Kth küçük elemanı bulmak için algoritma için bu kontrol - wikiwand.com/en/Median_of_medians
Piyush

Hash table + min heap kullansanız bile karmaşıklık aynıdır. herhangi bir optimizasyon görmüyorum.
Vinay

8

"Büyük kelime listeniz" yeterince büyükse, örnekleme yapabilir ve tahminler alabilirsiniz. Aksi takdirde, hash toplamayı severim.

Düzenle :

Örnek olarak, bazı sayfa alt kümelerini seçmek ve bu sayfalarda en sık kullanılan kelimeyi hesaplamaktan bahsediyorum. Sayfaları makul bir şekilde seçmeniz ve istatistiksel olarak anlamlı bir örnek seçmeniz koşuluyla, en sık kullanılan kelimelere ilişkin tahminleriniz makul olmalıdır.

Bu yaklaşım, yalnızca, hepsini işlemek biraz aptalca olacak kadar çok veriye sahipseniz gerçekten mantıklıdır. Yalnızca birkaç mega sahipseniz, bir tahminde bulunma zahmetine girmeden verileri gözden geçirip kesin bir cevabı hesaplayabilmelisiniz.


Bazen bunu defalarca yapmanız gerekir, örneğin web sitesi veya konu başına sık kullanılan kelimelerin listesini almaya çalışıyorsanız. Bu durumda, "ter dökmeden" gerçekten işe yaramaz. Yine de bunu olabildiğince verimli bir şekilde yapmanın bir yolunu bulmanız gerekiyor.
itsadok

1
Alakasız karmaşıklık sorunlarını ele almayan pratik bir cevap için +1. @itsadok: Her çalışma için: yeterince büyükse örnekleyin; değilse, o zaman bir günlük faktörü kazanmak önemsizdir.
Nikana Reklawyks

2

Kelimelerin ilk harfini kullanarak bölümlere ayırarak, ardından en büyük çok kelimeli kümeyi bir sonraki karakteri kullanarak k tek kelimelik kümeye sahip olana kadar bölümlere ayırarak zamanı daha da kısaltabilirsiniz. Yapraklarda kısmi / tam sözcük listeleri olan 256 yönlü bir ağaç kullanırsınız. Her yerde dize kopyalarına neden olmamak için çok dikkatli olmanız gerekir.

Bu algoritma O (m) 'dir, burada m, karakter sayısıdır. Büyük k için çok hoş olan k'ye bağımlılığı önler [bu arada yayınladığınız çalışma süreniz yanlış, O (n * lg (k)) olmalı ve bunun açısından ne olduğundan emin değilim m].

Her iki algoritmayı da yan yana çalıştırırsanız, asimptotik olarak optimal bir O (min (m, n * lg (k))) algoritması olduğundan oldukça emin olduğum şeyi alacaksınız, ancak benimki ortalamada daha hızlı olmalı çünkü içermiyor hashing veya sıralama.


7
Tarif ettiğiniz şeye 'trie' denir.
Nick Johnson

Merhaba Strilanc. Bölümleme sürecini ayrıntılı olarak açıklayabilir misiniz?
Morgan Cheng

1
bu nasıl sıralamayı içermez? trie'ye sahip olduğunuzda, en büyük frekanslara sahip k kelimelerini nasıl çıkarırsınız? hiç mantıklı değil
sıradan

2

Açıklamanızda bir hata var: Sayma O (n) süresi alır, ancak sıralama O (m * lg (m)) alır, burada m benzersiz kelimelerin sayısıdır . Bu genellikle toplam kelime sayısından çok daha küçüktür, bu nedenle muhtemelen hash'in nasıl oluşturulacağını optimize etmelidir.



2

Eğer peşinde olduğunuz şey, herhangi bir pratik k için metninizde en sık kullanılan k kelimelerin listesiyse ve herhangi bir doğal dil için algoritmanızın karmaşıklığı alakalı değildir.

Sadece örnek herhangi algoritma ile bu, diyelim ki, metinden birkaç milyon kelime, süreç birkaç saniye içinde ve en sık sayıları çok doğru olacaktır.

Bir yan not olarak, kukla algoritmanın karmaşıklığı (1. hepsini sayın 2. sayıları sıralayın 3. en iyiyi alın), O (n + m * log (m)), burada m, sizdeki farklı kelimelerin sayısıdır. Metin. log (m), (n / m) 'den çok daha küçük olduğundan, O (n) olarak kalır.

Pratik olarak uzun adım saymaktır.


2
  1. Kelimeleri depolamak için hafızayı verimli kullanan veri yapısını kullanın
  2. En sık kullanılan K kelimeyi bulmak için MaxHeap'i kullanın.

İşte kod

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

}   

}

İşte birim testleri

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

Daha fazla ayrıntı için bu test senaryosuna bakın


1
  1. Tüm kelime dizisini dolaşırken tüm kelimelerin sıklığını kaydetmek için bir Karma tablosu kullanın. Bu aşamada anahtar "kelime" ve değer "kelime frekansı" dır. Bu, O (n) süresi alır. Bu, yukarıda açıklanan her biri ile aynıdır.

  2. Karma haritaya eklenirken, en sık kullanılan 10 kelimeyi korumak için Treeset'i (java'ya özgü, her dilde uygulamalar vardır) 10 (k = 10) boyutunda tutun. Boyut 10'dan küçük olana kadar eklemeye devam edin. Boyut 10'a eşitse, eklenen öğe minimum öğeden büyükse, yani ilk öğe. Varsa çıkarın ve yeni eleman ekleyin

Ağaç kümesinin boyutunu kısıtlamak için bu bağlantıya bakın


0

Bir kelime dizisi "reklam" "reklam" "çocuk" "büyük" "kötü" "com" "gel" "soğuk" olduğunu varsayalım. Ve K = 2. "kelimelerin ilk harfini kullanarak bölümleme" den bahsettiğiniz gibi, ("reklam", "reklam") ("çocuk", "büyük", "kötü") ("com" "gel" "soğuk") " k tane tek sözcük kümesine sahip olana kadar bir sonraki karakteri kullanarak en büyük çoklu sözcük kümesini bölümleme. " bölümlenir ("çocuk", "büyük", "kötü") ("com" "gelir" "soğuk"), ilk bölüm ("reklam", "reklam") kaçırılırken "reklam" aslında en sık kullanılan kelime.

Belki de ne demek istediğini yanlış anladım. Bölümleme sürecinizi detaylandırır mısınız?


0

Bu sorunun bir O (n) algoritması ile çözülebileceğine inanıyorum. Sıralamayı anında yapabiliriz. Başka bir deyişle, bu durumda sıralama, geleneksel sıralama probleminin bir alt problemidir, çünkü hash tablosuna her eriştiğimizde yalnızca bir sayaç birer birer artırılır. Başlangıçta, tüm sayaçlar sıfır olduğu için liste sıralanır. Hash tablosunda sayaçları artırmaya devam ederken, aşağıdaki gibi frekansa göre sıralanan başka bir hash değerleri dizisini kaydederiz. Bir sayacı her artırdığımızda, sıralı dizide indeksini kontrol ederiz ve sayısının listedeki öncülünü aşıp aşmadığını kontrol ederiz. Eğer öyleyse, bu iki unsuru değiştiriyoruz. Böylelikle, en fazla O (n) olan bir çözüm elde ederiz, burada n orijinal metindeki kelime sayısıdır.


Bu genellikle iyi bir yön - ama bir kusuru var. sayı arttığında, sadece "selefini" kontrol etmeyeceğiz, ancak "öncüllerini" kontrol etmemiz gerekecek. örneğin, dizinin [4,3,1,1,1,1,1,1,1,1,1] olması büyük bir olasılıktır - 1'ler aynı sayıda olabilir - bu da onu daha az verimli hale getirir çünkü değiş tokuş etmek için uygun olanı bulmak için tüm öncüllere bakmamız gerekecek.
Shawn

Bu aslında O (n) 'den çok daha kötü olmaz mıydı? Daha çok O (n ^ 2) gibi, aslında oldukça verimsiz bir türdür?
dcarr622

Merhaba Shawn. Evet ben size katılıyorum. Ama bahsettiğiniz sorunun sorunun temelinden kaynaklandığından şüpheleniyorum. Aslında, sıralı bir değerler dizisi tutmak yerine, bir dizi (değer, indeks) çifti tutabilirsek, burada indeks tekrarlanan elemanın ilk oluşumuna işaret eder, problem O'da çözülebilir olmalıdır. (n) zaman. Örneğin, [4,3,1,1,1,1,1,1,1,1,1], [(4,0), (3,1), (1,2), (1 , 2), (1,2, ..., (1,2)]; endeksler 0'dan başlar
Aly Farahat

0

Ben de bununla uğraşıyordum ve @aly'den ilham alıyordum. Daha sonra sıralamak yerine, önceden sıralı bir kelime listesi tutabiliriz ( List<Set<String>>) ve kelime, X, kelimenin geçerli sayısı olduğu X konumunda kümede olacaktır. Genel olarak şu şekilde çalışır:

  1. her kelime için, geçtiği yerin haritasının bir parçası olarak saklayın: Map<String, Integer> .
  2. daha sonra, sayıya bağlı olarak, önceki sayım kümesinden çıkarın ve yeni sayım kümesine ekleyin.

Bunun dezavantajı, listenin belki büyük olmasıdır - bir TreeMap<Integer, Set<String>> - ancak bu biraz ek yük getirecektir. Nihayetinde HashMap veya kendi veri yapımızın bir karışımını kullanabiliriz.

Kod

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

Sadece bu problem için diğer çözümü buluyorum. Ama bunun doğru olduğundan emin değilim. Çözüm:

  1. Tüm kelimelerin frekansını T (n) = O (n) kaydetmek için bir Hash tablosu kullanın
  2. Karma tablonun ilk k elemanını seçin ve bunları bir arabellekte (alanı = k) geri yükleyin. T (n) = O (k)
  3. Her seferinde, öncelikle tamponun mevcut min elemanını bulmalıyız ve sadece tamponun min elemanını hash tablosunun (n - k) elemanlarıyla tek tek karşılaştırmalıyız. Karma tablosunun öğesi, bu min. Tampon öğesinden büyükse, o zaman geçerli tamponun min değerini bırakın ve karma tablosunun öğesini ekleyin. Bu nedenle, tampondaki en az birini her bulduğumuzda T (n) = O (k) 'ya ihtiyaç duyarız ve tüm hash tablosunu geçerken T (n) = O (n - k) gerekir. Dolayısıyla, bu işlem için tüm zaman karmaşıklığı T (n) = O ((nk) * k) 'dir.
  4. Tüm hash tablosunu geçtikten sonra, sonuç bu tamponda olur.
  5. Tüm zaman karmaşıklığı: T (n) = O (n) + O (k) + O (kn - k ^ 2) = O (kn + n - k ^ 2 + k). Çünkü k genel olarak n'den gerçekten daha küçüktür. Dolayısıyla bu çözüm için zaman karmaşıklığı T (n) = O (kn) 'dir . Bu doğrusal zamandır, k gerçekten küçüktür. Doğru mu? Ben gerçekten emin değilim.

0

Bu tür sorunlara yaklaşmak için özel veri yapısı düşünmeye çalışın. Bu durumda, dizeleri belirli bir şekilde depolamak için trie gibi özel bir ağaç türü çok verimli. Veya kelimeleri saymak gibi kendi çözümünüzü oluşturmanın ikinci yolu. Sanırım bu TB veri İngilizce olacaktır, o zaman genel olarak yaklaşık 600.000 kelimemiz var, bu yüzden sadece bu kelimeleri saklamak ve hangi dizelerin tekrarlanacağını saymak mümkün olacak + bu çözümün bazı özel karakterleri ortadan kaldırmak için normal ifadeye ihtiyacı olacak. İlk çözüm daha hızlı olacak, eminim.

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



0

En sık kullanılan kelimenin geçtiği yeri bulmak için en basit kod.

 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

Bu durumlarda, Java yerleşik özelliklerini kullanmanızı öneririm. Çünkü zaten iyi test edilmiş ve kararlıdırlar. Bu problemde HashMap veri yapısını kullanarak kelimelerin tekrarlarını buluyorum. Ardından, sonuçları bir dizi nesneye aktarıyorum. Nesneyi Arrays.sort () 'a göre sıralıyorum ve ilk k kelimeleri ve tekrarlarını yazdırıyorum.

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

}

Daha fazla bilgi için lütfen https://github.com/m-vahidalizadeh/foundations/blob/master/src/algorithms/TopKWordsTextFile.java adresini ziyaret edin . Umut ediyorum bu yardım eder.


Bu, soruda çizilen yaklaşımı ne şekilde iyileştirir? (Lütfen değil (. SE sunulan koddan yorumlarınızı dışarıda bırakın) I recommend to use Java built-in featuresgibi foreach döngüsünden ve işleme akışlarını ?)
ihtiyar adam

Bildiğiniz gibi verimli bir algoritma tasarlarken en önemli faktörlerden biri doğru veri yapısını seçmektir. O zaman, soruna nasıl yaklaştığınız önemlidir. Örneğin, bir soruna bölerek ve fethederek saldırmanız gerekir. Açgözlü olarak başka birine saldırmanız gerekir. Bildiğiniz gibi Oracle şirketi Java üzerine çalışıyor. Dünyanın en iyi teknoloji şirketlerinden biridir. Java'nın yerleşik özellikleri üzerinde çalışan en parlak mühendislerden bazıları var. Yani, bu özellikler iyi test edilmiş ve kurşun geçirmezdir. Onları kullanabilirsek, bence kullanmak daha iyidir.
Mohammad

0
**

C ++ 11 Yukarıdaki düşüncenin uygulanması

**

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;

}

};

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.