统计所有单词出现的次数:二叉数
来源:互联网 发布:js有几种数据类型 编辑:程序博客网 时间:2024/05/16 08:25
缘由
这是 K&R书中121页的一个例子,它本身使用来说明的C语言的自引用结构的,但是我认为其涉及一个更为重要的问题:二叉数。
故想以此将二叉数的内容也一并总结了吧。
故想以此将二叉数的内容也一并总结了吧。
分析问题
统计所有单词出现的次数的背景是:给出一篇文章,统计所有单词的数量。
这表示事先并不知道这些有哪些单词,便无法排序,更不要说使用查找了。
这表示事先并不知道这些有哪些单词,便无法排序,更不要说使用查找了。
- 书中指出:也不能分别对输入中的每个单词都执行一次线性查找,看其是否已经出现过。(程序执行的时间将会与输入单词的数量的二次方成比例)
- 折半查找的话,必须要将排序放入一个数组内(因为需要对下标进行计算),那样的话,新来的单词,即使我们找到它该存在的位置而没有它,我们打算将其插入数组,由会涉及数组元素的挪动,说到挪动,肯定是又要增加时间
- 顺序查找:那顺序查找为什么不可以呢?顺序查找可以用使用链表了了吧,我们只要在查找时找到这个新单词该出现的位置,没有找到它的话,那么我们再插入它,对于列表来说插入一个单词是非常方便的。当然,即使如此,查找的时间复杂度为O(n)
- 二叉数:如果使用二叉数来完成这个问题,我们将会得到更好的时间复杂度O(lgn),n为节点个数。也可以说,所花费的时间将会和树的高度成正比。二叉数的所有操作的时间复杂度都是这么多,包括插入、删除、建立、查找(来自:算法导论 第三版 第12章)。所以,对于一个单词而言,如果已经存放在了树中,那么就是查找,将新单词放入的就是插入。然而,实际上这两个动作应该是结合起来的,因为在其该在的位置找不到的时候,我们已经得到新单词该在的位置,所以直接插入即可。具体请看下面代码的实现。
使用二叉数,我们以字典顺序排序,为了更好的理解,我们构建的二叉数的节点大概是这样的:
对于这样一句话:now is the time for all good men to come to the aid of their party
对于这样一句话:now is the time for all good men to come to the aid of their party
针对问题的数据结构
二叉数的数据结构中基本的有三个:
- 自身数据
- 左孩子
- 右孩子
我们该题中,自身数据就是单词,而且还需要多一个统计单词次数的数值。
code如下:
code如下:
struct tnode {char *word;int count;struct tnode *left;struct tnode *right;};
面对过程的流程以及代码
作为一般讲c语言的书,很显然是会用面向过程的思维方式来完成的,由下面的main函数可以具体的流程:
int main(){struct tnode *root;char word[MAXWORD];root = NULL;while (getword(word, MAXWORD) != EOF)//读入一个单词if (isalpha(word[0]))root = addtree(root, word);//将单词加入到树中treeprint(root);//打印树return 0;}main函数中的addtree(root, word)包含了更多的流程,我们来看看addtree函数:
struct tnode *addtree(struct tnode *p, char *w){//判断是否是新节点if (p == NULL) {/* a new word has arrived */p = talloc();/* make a new node */p->word = strdup1(w);//复制w的过程,如单纯的赋值,就会使p->word和w指向了一处。p->count = 1;p->left = p->right = NULL;return p;}//不是新节点int cond;cond = strcmp(w, p->word);if (cond== 0){p->count++;/* repeated word */}else if (cond < 0){//就到左子树去找p->left = addtree(p->left, w);}else{//就到右子树去找p->right = addtree(p->right, w);}return p;}通过上述代码,我相信不仅对本题的流程有了基本的认识,并且也对二叉树的查找和插入有了一定的了解。实际上,而二叉数建立的过程就是查找和插入的过程。
其中的两个函数的代码:
/* talloc: make a tnode *//** * 为新节点分配内存 */struct tnode *talloc(void){return (struct tnode *) malloc(sizeof(struct tnode));}/** * 完成对单词的复制,避免不同指针指向同一个单词,对单词产生干扰 */char *strdup1(char *s){char *p;/* make a duplicate of s */p = (char *) malloc(strlen(s)+1); /* +1 for ’\0’ */if (p != NULL)strcpy(p, s);return p;}
读入单词
/* getword: get next word or character from input *//** * 没有调用高级的库函数使得这段代码略显复杂 * 但是这些基本的方式能够让我更深刻的体会了一些编程的高级技巧 */int getword(char *word, int lim){int c, getch(void);void ungetch(int);char *w = word;while (isspace(c = getch()));if (c != EOF)*w++ = c;if (!isalpha(c)) {*w = '\0';return c;}for ( ; --lim > 0; w++){if (!isalnum(*w = getch())) {ungetch(*w);//读的最后一个要放回去,因为最后一个不是我们要要的,但是我们又已经读了break;}}*w = '\0';return word[0];}#define BUFSIZE 100char buf[BUFSIZE];int bufp = 0;/* buffer for ungetch *//* next free position in buf */int getch(void) /* get a (possibly pushed-back) character */{return (bufp > 0) ? buf[--bufp] : getchar();}void ungetch(int c)/* push character back on input */{if (bufp >= BUFSIZE)printf("ungetch: too many characters\n");elsebuf[bufp++] = c;}
打印结果
都快忘了
- 先序遍历:先根,再左,后右
- 中序遍历:先左,再根,后右
- 后序遍历:先左,再右,后中
/* treeprint: in-order print of tree p *//** * 递归打印,采用了中序遍历。 */void treeprint(struct tnode *p){if (p != NULL) {treeprint(p->left);printf("%4d %s\n", p->count, p->word);treeprint(p->right);}}
打印实例
就拿我们编写的代码作一个测试,结果如下,不能给中文做这样测试,因为中文里面的字都没有空格
zy@zy:~/Documents/eclipseWorkSpace/test/src$ ./a.out 1 EOF 3 MAXWORD 1 NULL 2 addtree 4 char 1 count 1 ctype 1 define 2 getword 4 h 1 if 4 include 4 int 1 isalpha 1 left 1 main 1 return 1 right 5 root 1 stdio 1 stdlib 1 string 7 struct 7 tnode 2 treeprint 1 void 1 while 5 wordzy@zy:~/Documents/eclipseWorkSpace/test/src$
源代码
#include <stdio.h>#include <ctype.h>#include <string.h>#include <stdlib.h>#define MAXWORD 100struct tnode *addtree(struct tnode *, char *);void treeprint(struct tnode *);int getword(char *, int);struct tnode {char *word;int count;struct tnode *left;struct tnode *right;};int main(){struct tnode *root;char word[MAXWORD];root = NULL;while (getword(word, MAXWORD) != EOF)//读入一个单词if (isalpha(word[0]))root = addtree(root, word);//将单词加入到树中treeprint(root);//打印树return 0;}struct tnode *talloc(void);char *strdup1(char *s);struct tnode *addtree(struct tnode *p, char *w){//判断是否是新节点if (p == NULL) {/* a new word has arrived */p = talloc();/* make a new node */p->word = strdup1(w);//复制w的过程,如单纯的赋值,就会使p->word和w指向了一处。p->count = 1;p->left = p->right = NULL;return p;}//不是新节点int cond;cond = strcmp(w, p->word);if (cond== 0){p->count++;/* repeated word */}else if (cond < 0){//就到左子树去找p->left = addtree(p->left, w);}else{//就到右子树去找p->right = addtree(p->right, w);}return p;}/* treeprint: in-order print of tree p *//** * 递归打印,采用了中序遍历。 */void treeprint(struct tnode *p){if (p != NULL) {treeprint(p->left);printf("%4d %s\n", p->count, p->word);treeprint(p->right);}}/* talloc: make a tnode *//** * 为新节点分配内存 */struct tnode *talloc(void){return (struct tnode *) malloc(sizeof(struct tnode));}/** * 完成对单词的复制,避免不同指针指向同一个单词,对单词产生干扰 */char *strdup1(char *s){char *p;/* make a duplicate of s */p = (char *) malloc(strlen(s)+1); /* +1 for ’\0’ */if (p != NULL)strcpy(p, s);return p;}/* getword: get next word or character from input *//** * 没有调用高级的库函数使得这段代码略显复杂 * 但是这些基本的方式能够让我更深刻的体会了一些编程的高级技巧 */int getword(char *word, int lim){int c, getch(void);void ungetch(int);char *w = word;while (isspace(c = getch()));if (c != EOF)*w++ = c;if (!isalpha(c)) {*w = '\0';return c;}for ( ; --lim > 0; w++){if (!isalnum(*w = getch())) {ungetch(*w);//读的最后一个要放回去,因为最后一个不是我们要要的,但是我们又已经读了break;}}*w = '\0';return word[0];}#define BUFSIZE 100char buf[BUFSIZE];int bufp = 0;/* buffer for ungetch *//* next free position in buf */int getch(void) /* get a (possibly pushed-back) character */{return (bufp > 0) ? buf[--bufp] : getchar();}void ungetch(int c)/* push character back on input */{if (bufp >= BUFSIZE)printf("ungetch: too many characters\n");elsebuf[bufp++] = c;}
二叉数的删除问题
今天为此我又顺带复习了一下二叉数的一些问题,二叉树的几个基本的操作部分,我认为比较难的就是删除。
想不到算法导论还将其描述的有点复杂,所以我主要看了我考研时候一本书,个人觉得非常好:2014版数据结构高分笔记(第2版),里面对此的描述比较清楚。
其中最复杂的就是情况(D):
假如要删除z,那么我们就先找到与z数值上最接近的节点,也就是从其右孩子一直往左走,就是上图中的y,然后将y与z对换,再删除对换后的z即可,x很容易判断该放在r的哪里。(2014年6月10日:复习时,发现上面的说法和图中的画法不符合,图中的应该是实际上应该让Y成为r的父节点,然后再删除Z节点,再让Z子节点的左孩子成为Y的左孩子,Z节点的父节点成为Y的父节点即可)
想不到算法导论还将其描述的有点复杂,所以我主要看了我考研时候一本书,个人觉得非常好:2014版数据结构高分笔记(第2版),里面对此的描述比较清楚。
其中最复杂的就是情况(D):
假如要删除z,那么我们就先找到与z数值上最接近的节点,也就是从其右孩子一直往左走,就是上图中的y,然后将y与z对换,再删除对换后的z即可,x很容易判断该放在r的哪里。(2014年6月10日:复习时,发现上面的说法和图中的画法不符合,图中的应该是实际上应该让Y成为r的父节点,然后再删除Z节点,再让Z子节点的左孩子成为Y的左孩子,Z节点的父节点成为Y的父节点即可)
关于二叉树更深入的内容
- 虽然二叉树的各项操作的时间复杂度为O(lgN),但是在最坏的情况下还是O(n),那么可以进一步了解红黑树,它保证了在最坏的情况下的时间复杂是O(lgN)
- 此外还有B树,适用于二级磁盘储存器上的数据库维护
关于本题进一步扩展
刚买了unix环境高级编程,想看的慌,这三道题就留着以后做把
Exercise 6-2. Write a program that reads a C program and prints in alphabetical order each group of variable names that are identical in the first 6 characters, but different somewhere thereafter. Don’t count words within strings and comments. Make 6 a parameter that can be set from the command line.
Exercise 6-3. Write a cross-referencer that prints a list of all words in a document, and for each word, a list of the line numbers on which it occurs. Remove noise words like ‘‘the,’’ ‘‘and,’’ and so on.
Exercise 6-4. Write a program that prints the distinct words in its input sorted into decreasing order of frequency of occurrence. Precede each word by its count.
Exercise 6-3. Write a cross-referencer that prints a list of all words in a document, and for each word, a list of the line numbers on which it occurs. Remove noise words like ‘‘the,’’ ‘‘and,’’ and so on.
Exercise 6-4. Write a program that prints the distinct words in its input sorted into decreasing order of frequency of occurrence. Precede each word by its count.
0 0
- 统计所有单词出现的次数:二叉数
- 统计所有'单词'出现的次数
- 如何统计输入中所有单词出现的次数?
- Scala 统计一个文件夹下面所有单词出现的次数
- 统计单词出现的次数
- 统计单词出现的次数
- 统计单词出现的次数
- 统计单词出现的次数。
- 统计输入中所有单词出现的次数(使用二叉查找树实现:递归和非递归)
- 统计单词出现次数
- 统计英文文件中单词数和各单词出现的频率(次数)
- 统计单词出现的次数并按单词出现的次数顺序输出单词及其次数
- 统计文章中单词出现的次数
- 统计连续出现次数最多的单词
- 统计某个单词出现的次数
- 统计文章中单词出现的次数
- JS统计单词出现的次数。
- 用Java统计单词出现的次数
- AOJ-AHU-OJ-592 神奇的叶子
- 用管理员身份登录
- AOJ-AHU-OJ-61 Lake Counting(递归)
- 文件结束符 End Of File
- Install Monitoring Systems
- 统计所有单词出现的次数:二叉数
- 关于java中try catch finally的执行顺序
- python+eclipse 开发环境配置 http://pydev.org/updates
- LeetCode 150 — Evaluate Reverse Polish Notation(C++ Java Python)
- Apache+Tomcat集群配置
- Eclipse ADT 使用空格替代Tab键缩进整块整块代码
- C++ COM编程生成随机GUID值
- Qt模块化笔记之core——使用信号与槽
- 写得很好的linux学习笔记