【问题标题】:Word Frequency Statistics in C (not C++)C(非 C++)中的词频统计
【发布时间】:2012-01-04 17:08:33
【问题描述】:

给定一个字符串由一个空格分隔的单词组成,按照单词在字符串中出现的次数降序打印。

例如,输入字符串“ab bc bc”将生成以下输出:

bc : 2
ab : 1

如果使用 C++ 数据结构(如地图),问题将很容易解决。但是如果这个问题只能用普通的旧 C 来解决,那看起来要困难得多。

我应该在这里使用什么样的数据结构和算法?请尽可能详细。我在 DS 和 Algo 方面很弱。 :-(

【问题讨论】:

  • 你可以使用结构的链表。
  • 没时间细说,看tries.

标签: c algorithm data-structures word-frequency


【解决方案1】:

您可以使用的一种数据结构是一个简单的二叉树,其中包含您可以使用 strcmp 比较的单词。 (我现在将忽略案例问题)。

您需要确保树在生长过程中保持平衡。为此,请在维基百科或其他地方查找 AVL 树或 1-2 树或红黑树。

除了创建二叉树结构外,我不会提供太多细节,每个节点都有一个左右子节点,可以为空,而对于叶节点,两个子节点都为空。为了使其更简单,请使用具有值和两个子节点的“侵入式”节点。比如:

struct Node
{
  char * value;
  size_t frequency; 
  struct Node * left;
  struct Node * right;
};

显然,作为 C 语言,您需要进行所有内存管理。

您将拥有一个沿树递归、比较并根据需要向左或向右移动的函数。如果找到它只会提高频率。如果不是,您的函数应该能够确定插入节点的位置,然后是您的插入和重新平衡逻辑。当然,新节点将包含频率为 1 的单词。

最后,您将需要一种通过树递归打印结果的方法。在您的情况下,这可以是一个递归函数。

请注意,另一种数据结构是某种哈希表。

如果您正在寻找最有效的解决方案并且手头有大量内存,那么您可以使用一种数据结构,在遇到每个字母时都对其进行分支。所以“a”给你所有以a开头的单词,然后移到第二个字母“b”等。对于不了解数据结构的人来说,实现起来相当复杂,所以我建议你去用简单的二叉树。

请注意,在打印输出时,它不会按频率倒序排列,因此您必须先对结果进行排序。 (在使用 map 的 C++ 中,您也不会按该顺序获取它们)。

【讨论】:

  • 谢谢你,现金牛。这个想法很清楚。我会尝试实现它,这对我来说有点困难(从未认真接触过树的东西)。顺便说一句,在完全设置后有什么建议来整理三个(以便按频率降序打印单词)?
  • @Qiang Xu:要排序,请看我回复中的示例,在其中您将了解如何使用 qsort()。
  • 为了对结果进行排序,只需使用 qsort,它是 C 标准的一部分。
【解决方案2】:

我会为此使用三叉树。以下由 Jon Bentley 和 Robert Sedgewick 介绍数据结构的文章有一个 C 语言示例。

http://www.cs.princeton.edu/~rs/strings/

【讨论】:

  • 只是想知道 Jon Bentley 的“Programming Peals”第 2 版是否涵盖了这个主题?听说很经典,但一直没机会接触。
  • 我家里有——我看看今晚能不能记得查一下。
【解决方案3】:

这是我如何做的一个示例。 findWord() 中的搜索可以优化。分配的数量也可以通过分配字块而不是一次分配一个来减少。也可以为这种情况实现一个链表。它缺少内存释放。希望这能让你继续前进。

    #include <stdio.h>
    #include <assert.h>
    #include <stdlib.h>

    #define MAXWORDLEN 128

    const char* findWhitespace(const char* text)
    {
        while (*text && !isspace(*text))
            text++;
        return text;
    }

    const char* findNonWhitespace(const char* text)
    {
        while (*text && isspace(*text))
            text++;
        return text;
    }

    typedef struct tagWord
    {
        char word[MAXWORDLEN + 1];
        int count;
    } Word;

    typedef struct tagWordList
    {
        Word* words;
        int count;
    } WordList;

    WordList* createWordList(unsigned int count);

    void extendWordList(WordList* wordList, const int count)
    {
        Word* newWords = (Word*)malloc(sizeof(Word) * (wordList->count + count));
        if (wordList->words != NULL) {
            memcpy(newWords, wordList->words, sizeof(Word)* wordList->count);
            free(wordList->words);
        }
        for (int i = wordList->count; i < wordList->count + count; i++) {
            newWords[i].word[0] = '\0';
            newWords[i].count = 0;
        }
        wordList->words = newWords;
        wordList->count += count;
    }

    void addWord(WordList* wordList, const char* word)
    {
        assert(strlen(word) <= MAXWORDLEN);
        extendWordList(wordList, 1);
        Word* wordNode = &wordList->words[wordList->count - 1];
        strcpy(wordNode->word, word);
        wordNode->count++;  
    }

    Word* findWord(WordList* wordList, const char* word)
    {
        for(int i = 0; i < wordList->count; i++) {
            if (stricmp(word, wordList->words[i].word) == 0) {
                return &wordList->words[i];
            }
        }
        return NULL;
    }

    void updateWordList(WordList* wordList, const char* word)
    {
        Word* foundWord = findWord(wordList, word);
        if (foundWord == NULL) {
            addWord(wordList, word);
        } else {
            foundWord->count++;
        }
    }

    WordList* createWordList(unsigned int count)
    {
        WordList* wordList = (WordList*)malloc(sizeof(WordList));
        if (count > 0) {
            wordList->words = (Word*)malloc(sizeof(Word) * count);
            for(unsigned int i = 0; i < count; i++) {
                wordList->words[i].count = 0;
                wordList->words[i].word[0] = '\0';
            }
        }
        else {
            wordList->words = NULL;
        }
        wordList->count = count;    
        return wordList;
    }

    void printWords(WordList* wordList)
    {
        for (int i = 0; i < wordList->count; i++) {
            printf("%s: %d\n", wordList->words[i].word, wordList->words[i].count);
        }
    }

    int compareWord(const void* vword1, const void* vword2)
    {
        Word* word1 = (Word*)vword1;
        Word* word2 = (Word*)vword2;
        return strcmp(word1->word, word2->word);
    }

    void sortWordList(WordList* wordList)
    {
        qsort(wordList->words, wordList->count, sizeof(Word), compareWord);
    }

    void countWords(const char* text)
    {
        WordList   *wordList = createWordList(0);
        Word       *foundWord = NULL;
        const char *beg = findNonWhitespace(text);
        const char *end;
        char       word[MAXWORDLEN];

        while (beg && *beg) {
            end = findWhitespace(beg);
            if (*end) {
                assert(end - beg <= MAXWORDLEN);
                strncpy(word, beg, end - beg);
                word[end - beg] = '\0';
                updateWordList(wordList, word);
                beg = findNonWhitespace(end);
            }
            else {
                beg = NULL;
            }
        }

        sortWordList(wordList);
        printWords(wordList);
    }

int main(int argc, char* argv[])
{
    char* text = "abc 123 abc 456 def 789 \tyup this \r\ncan work yup 456 it can";
    countWords(text);
}

【讨论】:

  • 谢谢你,杰克!有空我会仔细看看你的示例代码。
  • 要在文本中查找单词,可以使用现有的 strtok。由于您可能不想修改原始文本(并且 strtok 将分隔符更改为空终止符),您可以首先将整个缓冲区复制到私有缓冲区中。尽管 POSIX 上有一个 strtok_r,但代码当然不会是可重入的(线程安全的)。或者,只需复制整个缓冲区,在副本中将空格转换为空终止符,同时将单词的开头保持在不同的指针中。
  • @CashCow:非常感谢您对微妙部分的详细解释。
  • @Jack:查看您的代码后,了解了解决方案背后的基本思想。相当整洁,虽然正如你提到的,还有改进的余地。非常感谢这个例子!
猜你喜欢
  • 2012-01-30
  • 2019-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多