带有链表的哈希表中的前 10 个频率

Posted

技术标签:

【中文标题】带有链表的哈希表中的前 10 个频率【英文标题】:Top 10 Frequencies in a Hash Table with Linked Lists 【发布时间】:2010-10-25 02:10:48 【问题描述】:

下面的代码会将它在我的哈希表(其中是一堆链表)中可以找到的最高频率打印 10 次。我需要我的代码来打印我的哈希表中的前 10 个频率。我不知道该怎么做(代码示例会很棒,简单的英语逻辑/伪代码也一样好)。

    我创建了一个名为“tmp”的临时哈希列表,它指向我的哈希表“hashtable” while 循环然后遍历列表并查找最高频率,即 int 'tmp->freq' 循环将继续使用变量“topfreq”复制它找到的最高频率的过程,直到它到达哈希表上链表的末尾。

我的“节点”是一个由变量“freq”(int)和“word”(128 个字符)组成的结构。当循环没有其他内容可搜索时,它会在屏幕上打印这两个值。

问题是,我不知道如何从我刚刚找到的数字中找到下一个最小的数字(这可能包括另一个具有相同频率值的节点,所以我必须检查那个词也不一样)。

void toptenwords()

    int topfreq = 0;
    int minfreq = 0;
    char topword[SIZEOFWORD];

    for(int p = 0; p < 10; p++) // We need the top 10 frequencies... so we do this 10 times
    
        for(int m = 0; m < HASHTABLESIZE; m++) // Go through the entire hast table
        
            node* tmp;
            tmp = hashtable[m];

            while(tmp != NULL) // Walk through the entire linked list
            
                if(tmp->freq > topfreq) // If the freqency on hand is larger that the one found, store...
                
                    topfreq = tmp->freq;
                    strcpy(topword, tmp->word);
                
                tmp = tmp->next;
            
        
        cout << topfreq << "\t" << topword << endl;
    

非常感谢任何和所有帮助:)

【问题讨论】:

请将您的制表符更改为 4 个空格。那会伤害眼睛,而且护目镜什么都不做! ;) 如果这是作业添加作业标签。 @all:这是我自己设定的任务......所以不,不是家庭作业。 【参考方案1】:

做到这一点的绝对最快方法是使用SoftHeap。使用 SoftHeap,您可以在 O(n) 时间内找到前 10 个项目,而此处发布的所有其他解决方案都需要 O(n lg n) 时间。

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

这篇***文章展示了如何使用软堆在 O(n) 时间内找到中位数,前 10 名只是中位数问题的一个子集。然后,您可以按顺序对前 10 项中的项目进行排序,并且由于您总是最多对 10 项进行排序,因此仍然是 O(n) 时间。

【讨论】:

【参考方案2】:

如果目标是累积单词频率,则包含单词链接列表的哈希表似乎是一种特殊的数据结构。

尽管如此,获得 10 个最高频率节点的有效方法是将每个节点插入优先级队列/堆,例如斐波那契堆,其插入时间为 O(1),删除时间为 O(n)。假设哈希表迭代很快,该方法的运行时间为 O(n×O(1) + 10×O(n)) ≡ O(n)。

【讨论】:

+1。两件事:我知道的所有堆(包括斐波那契)的删除时间是 O(log n),而不是 O(n);如果 n >> 10 保持最小堆(而不是最大堆)会快得多,您可以在其中插入下一个元素并在每次迭代中删除最小的元素(这样堆最多只包含10 个元素)。 O(log(n)) ≡ O(n) 是它的摊销删除时间 - 因为这个算法只涉及删除一些摊销分析无效的键。斐波那契堆的最坏情况删除是 O(n)。 好点。所以在一般情况下,当我们想要前 k 个频率时,您的算法将是 O(kn) - 即与运行选择排序的前 k 个迭代的明显解决方案相同。请参阅我对使用普通二进制堆(而不是斐波那契堆,实现起来更棘手)的 O(nlog(k)) 算法的回答。【参考方案3】:

假设总共有 n 个词,我们需要出现频率最高的 k 个词(这里,k = 10)。

如果 nk 大很多,我知道的最有效的方法是维护一个最小堆(即顶部元素具有 minimum 堆中所有元素的频率)。在每次迭代中,将下一个频率插入堆中,如果堆现在包含 k+1 个元素,则删除 最小的。这样,堆始终保持在 k 个元素的大小,在任何时候都包含到目前为止看到的 k 个最高频率的元素。在处理结束时,按递增顺序读出 k 个最高频率的元素。

时间复杂度:对于每个n个单词,我们做两件事:插入一个大小最多为k的堆中,然后删除最小元素。每个操作花费 O(log k) 时间,因此整个循环花费 O(nlog k) 时间。最后,我们从一个大小为 k 的堆中读出 k 个元素,耗时 O(klog k),总时间为 O((n+k )日志 k)。由于我们知道 k n,O(klog k) 最坏的情况是 O(nlog k),所以这可以简化为 O(nlog k)。

【讨论】:

【参考方案4】:

第 1 步(低效):

通过插入排序将向量移动到已排序的容器中,但插入到大小为 10 的容器(例如链表或向量)中,并删除掉出列表底部的所有元素。

第 2 步(高效):

与步骤 1 相同,但要跟踪列表底部项的大小,如果当前项太小,则完全跳过插入步骤。

【讨论】:

【参考方案5】:

保留一个包含 10 个节点指针的数组,并将每个节点插入到数组中,保持数组有序。数组中的第 11 个节点在每次迭代时都会被覆盖并包含垃圾。

void toptenwords()

        int topfreq = 0;
        int minfreq = 0;
        node *topwords[11];
        int current_topwords = 0;

        for(int m = 0; m < HASHTABLESIZE; m++) // Go through the entire hast table
        
                node* tmp;
                tmp = hashtable[m];

                while(tmp != NULL) // Walk through the entire linked list
                
                        topwords[current_topwords] = tmp;
                        current_topwords++;
                        for(int i = current_topwords - 1; i > 0; i--)
                        
                                if(topwords[i]->freq > topwords[i - 1]->freq)
                                
                                        node *temp = topwords[i - 1];
                                        topwords[i - 1] = topwords[i];
                                        topwords[i] = temp;
                                
                                else break;
                        
                        if(current_topwords > 10) current_topwords = 10;
                        tmp = tmp->next;
                
        

【讨论】:

【参考方案6】:

在遍历哈希表(然后遍历其中包含的每个链表)时,将自平衡二叉树 (std::set) 作为“结果”列表。当您遇到每个频率时,将其插入到列表中,如果列表中的条目超过 10 个,则将其截断。完成后,您将获得一组(排序列表)前十个频率,您可以根据需要对其进行操作。

在哈希表本身中使用集合而不是链表可能会获得性能提升,但您可以自己解决。

【讨论】:

作为要求之一,我不能使用二叉树。非常感谢您的建议,非常感谢! @tmhai:“要求”之一是不允许使用二叉树?这是作业吗? (如果是这样,很好,但这样标记你的问题。)【参考方案7】:

我会维护一组已使用的单词并更改最里面的 if 条件以测试频率是否大于先前的最高频率和 tmp->单词不在已使用的单词列表中。

【讨论】:

以上是关于带有链表的哈希表中的前 10 个频率的主要内容,如果未能解决你的问题,请参考以下文章

141. 环形链表简单哈希双指针

剑指offer-之-哈希表

Leetcode练习(Python):哈希表类:第138题: 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。 要求返回这个链表的 深拷贝。 我们用一个

在 C 中寻找数组(与链表)哈希表实现

环形链表(哈希表链表)寻找两个正序数组的中位数(数组二分查找)二进制求和(位运算数学)

数组链表哈希表