【发布时间】:2015-07-20 21:36:16
【问题描述】:
我需要将文本从 .text 文件复制到另一个 .txt 文件中。我唯一的限制是,如果有任何重复,我只会将这个词放在新文件中一次。分隔原始文件中单词的唯一值是空格。我正在考虑将整个文本复制到一个字符串中,然后检查是否有重复,但我不知道如何检查以及是否是个好主意。你们能帮我一些想法吗?
【问题讨论】:
-
制作二叉树(或类似trie、map(或set))
我需要将文本从 .text 文件复制到另一个 .txt 文件中。我唯一的限制是,如果有任何重复,我只会将这个词放在新文件中一次。分隔原始文件中单词的唯一值是空格。我正在考虑将整个文本复制到一个字符串中,然后检查是否有重复,但我不知道如何检查以及是否是个好主意。你们能帮我一些想法吗?
【问题讨论】:
我们需要一个可以容纳无限数量单词的数据结构,我们称之为SET。
这个结构必须有一个操作来添加一个单词,我们称之为SET::ADD。
这种操作的返回值必须表明是否添加了单词或是否有错误。
SET结构的特殊性在于一个词只能添加一次,所以SET::ADD返回了两个错误值:GENERIC_ERROR 表示内部实现错误,DUPLICATE 表示尝试插入已经存在的单词。
SET 结构也有一个初始化它的操作(SET::INIT)和一个释放它的操作(SET::FREE )。
然后我们从输入文件中读取单词,一次一个单词,并将每个单词添加到 SET 结构中。
如果插入成功我们将这样一个词写入输出文件,否则我们跳过这一步。
伪算法
1. S = SET::INIT
2. FIN = OpenFile(input_file)
3. FOUT = OpenFile(output_file);
4. FOR EACH WORD IN FIN
4.1 IF SET::ADD(S, WORD) == SUCCESS THEN
4.1.1 WriteToFile(FOUT, WORD)
4.2 END IF
5. CloseFile(FIN);
6. CloseFile(FOUT);
7. SET::FREE(S);
这里真正的艰巨工作是实现 SET 结构。
数据结构由可以对其执行的操作以及该操作的前置条件和后置条件定义。
所以理论上我们在实现SET::ADD时只需要做这些简单的事情:搜索单词是否已经存在,添加强>:
1. FUNCTION SET::ADD(S, W)
1.1 FOR EACH WORD IN S
1.1.1 IF WORD == W THEN
1.1.1.1 RETURN DUPLICATE
1.1.2 END IF
1.2 ADD W TO S
这两个步骤在很大程度上依赖于实现。
具有此要求的数据结构有多种实现方式,例如,非常简单的实现可以使用固定大小的指针数组。然而,这有严重的缺点。
更好的实现可能是链表,这让我们可以插入无限数量的单词,但插入和搜索需要线性时间!
所以我们进入了时间复杂度的领域。
我现在告诉你,这个结构可以用摊销的常数时间来实现。但让我们从头开始。
链表的下一个逻辑步骤是:使用二叉树、使用和哈希表。
两者都可以同时进行插入和搜索,即可以合并两个操作。
第一个用O(log n)插入和搜索,第二个用O(1)。
然而,第一个更加结构化让我们不仅可以进行搜索,还可以进行有序搜索。这个特性对这个问题没有用,所以我们应该选择哈希表。
我选择了树。这是因为二叉树让您可以比哈希表更好地练习指针和递归,它是一个基础良好的类型(当您参加类型理论课程时,您会感谢我的!)。
如果你不熟悉二叉树,现在就学吧!询问 Google 或您的老师!
我们树的一个节点应该包含
最后两个很简单,第一个可以实现为指针或固定大小的结构内数组。
我选择了后者,因为它使节点只需要一次调用malloc,这也得到了我们有一个单词的最大大小这一事实的支持,因为我们使用fscanf 读取它。
对代码的一些注释
add_word,这使得解决方案优雅,但手头有纸笔!strncpy、snprintf 并使用正确的 len 动态设置 fscanf 格式说明符,确保不会发生缓冲区溢出。 这是个好习惯-assert 来检查分配给格式说明符缓冲区的大小是否不够大,这是因为程序员可以计算出正确的大小,并且一旦编译代码,它就会保持固定,所以没有需要繁重的运行时检查。fscanf 使用的格式说明符的格式为 %s,其中 为 MAX_WORD_SIZE,因此例如结果为“%40s”。#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
/* Values returned by add_word */
/* The word has been added to the tree */
#define AW_ADDED 0
/* The word cannot be added as malloc failed*/
#define AW_ERROR -1
/* The word is a duplicate */
#define AW_DUPLICATE 1
/* Maximum size of a word */
#define MAX_WORD_SIZE 40
/* Structure of the binary tree node */
typedef struct node
{
struct node* left; /* Ptr to left (less than) branch */
struct node* right; /* Ptr to right (greater than) branch */
char word[MAX_WORD_SIZE+1]; /* Word stored in the node */
} node;
/*
Add a word to the tree identified by the root pointer.
This function allocate all the memory itself, the root of a tree is a pointer to a node structure.
root is a pointer to the root (a ptr to ptr since the root may be updated)
word is the word to add
*/
int add_word(node** root, const char* word)
{
int compare;
/* Traverse the tree until you find a null pointer (beware, we work with ptr to ptr) */
while (*root)
{
/* Compare the current node word with the given one */
compare = strcmp(word, (*root)->word);
/* They are equal? Easy, just return the appropriate value */
if (!compare)
return AW_DUPLICATE;
/* Move to the left of right based on the sign of the comparison */
root = compare < 0 ? &((*root)->left) : &((*root)->right);
}
/* We found a null ptr to update with a ptr to a new node */
/* Allocate memory */
if (!(*root = malloc(sizeof(node))))
return AW_ERROR;
/* Init the new node */
(*root)->left = (*root)->right = 0;
/* Copy the given word, up to MAX_WORD_SIZE byte*/
strncpy((*root)->word, word, MAX_WORD_SIZE);
/* Set a null terminator on the last byte in the case the word is exactly MAX_WORD_SIZE char*/
(*root)->word[MAX_WORD_SIZE] = 0;
return AW_ADDED;
}
/*
Free the memory used by the tree
Set the pointers to NULL.
Use recursion for didactic purpose, an iterative solution would consume less resources as
this is NOT tail recursion.
*/
void free_tree(node** root)
{
if (*root)
{
/* Go to children nodes */
free_tree(&((*root)->left));
free_tree(&((*root)->right));
/* Free current node */
free(*root);
*root = NULL;
}
}
int main()
{
/* Open the files */
FILE* fin = fopen("in.txt", "r");
FILE* fout = fopen("out.txt", "w");
/* Check the descriptors */
if (!fin)
return printf("Cannot open input file\n"), 1;
if (!fout)
return printf("Cannot open output file\n"), 2;
/* This is out tree */
node* root = NULL;
/* This is the buffer for reading word from fin*/
char new_word[MAX_WORD_SIZE+1];
/* This is the buffer for creating fscanf format specifier*/
char format[32];
/* Create the fscanf format specifier */
int char_used = snprintf(format, sizeof(format), "%%%ds", MAX_WORD_SIZE);
assert(char_used + 1 <= sizeof(format));
/* Read the file until the end */
while (!feof(fin))
{
/* Read a word and add it to the tree, if it is added, write it to new file */
if (fscanf(fin, format, new_word) && add_word(&root, new_word) == AW_ADDED)
fprintf(fout, "%s ", new_word);
}
/* Close and exit */
fclose(fin);
fclose(fout);
free_tree(&root);
return 0;
}
【讨论】:
您可以尝试使用 MD5 或类似方法对字符串进行哈希处理,然后将它们存储在二叉树中。应该具有相当低的平均时间复杂度。不过,我不确定 MD5 有多快。小字符串可能有更好的哈希算法。
如果您不关心效率,您可以将所有字符串存储在一个数组中,并在每次选择新字符串时对所有字符串使用 strcmp。
【讨论】: