C程序设计语言中的自引用结构——二叉树

        花了两天时间才把这段程序敲出来、跑得起来、然后看懂,包括树的创建和树的打印。

        原书内容:假设我们需要处理一个更一般化的问题:统计输入中的所有单词的出现次数。因为预先不知道单词出现的单词列表,所以无法方便地排序,并使用折半查找法,看它在前面是否已经出现,这样做,程序的执行将花费大量的时间。(更具体地说,程序的执行时间是与输入单词数目的二次方成比例的。)我们该如何组织这些数据,才能有效地处理一系列任意的单词呢?

        一种解决方法是,在读取输入中任意单词的同时,就讲它放到正确的位置,从而始终保证所有的单词是按顺序排列的。虽然这可以不用通过线性数组中移动单词来实现,但它仍然会导致程序执行时间过长。我们可以使用一种称为二叉树的数据结构来取而代之。

      每个不同的单词在树中都是一个节点,每个节点包含:

               l  一个指向该单词的指针

               l  一个统计出现次数的计数值

               l  一个指向左子树的指针

               l  一个指向右子树的指针

        任何节点最多拥有两个子树,也可能只有一个子树或一个都没有。

         对节点的所有操作要保证,任何节点的左子树只包含那些字典序小于该节点中单词的那些单词,右子树只包含字典序大于该节点中单词的那些单词。(也就是左子树<节点<右子树……)

        要查找一个新单词是否已经在树中,可以从根节点开始,比较新单词与该节点中的单词。若匹配,计数值加1。若新单词小于该节点中的单词,则在左子树中继续查找,否则在右子树中查找。若搜寻方向上无子树,则说明新单词不在树中,并且,当前的空位置就是存放新加入单词的正确位置。因为从任意节点出发的查找都要按照同样的方式查找它的一个子树,所以该过程是递归的。相应的,在插入和打印操作中使用递归过程也是很自然的。

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

#define MAXWORD 100
#define BUFSIZE 100 //缓冲区大小
char buf[BUFSIZE]; //缓冲区
int bufp = 0;
struct tnode{
    char *word;
    int count;
    struct tnode *left;
    struct tnode *right;
};
struct tnode *addtree(struct tnode *, char *);
void treeprint(struct tnode *);
int getword(char *, int);
struct tnode *talloc(void);
char *strdup(char *);
int getch(void);
void ungetch(int c);

//单词出现的频率统计
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;
}

//addtree函数:在p的位置或p的下方增加一个w节点
struct tnode *addtree(struct tnode *p, char *w)
{
   int cond;
   if (p == NULL){
    p = talloc();
    p->word = strdup(w);
    p->count = 1;
    p->left = p->right = NULL;
   } else if ((cond = strcmp(w, p->word)) == 0)
        p->count++;
    else if (cond < 0)
        p->left = addtree(p->left, w);
    else
        p->right = addtree(p->right, w);
    return p;
}

//treeprint函数:按序打印树p
void treeprint(struct tnode *p)
{
    if (p != NULL){
        treeprint(p->left);
        printf("%4d %s\n",p->count, p->word);
        treeprint(p->right);
    }
}

//talloc函数:创建一个tnode
struct tnode *talloc(void)
{
    return (struct tnode *) malloc(sizeof(struct tnode));
}

//strdup函数:把实参传入的字符串保存到某个安全位置
char *strdup(char *s)
{
    char *p;
    p = (char *)malloc(strlen(s)+1);//加1操作为了在结尾加上字符'\0'
    if (p)
        strcpy(p, s);
    return p;
}

//getword函数:从输入中读取下一个单词或字符
int getword(char *word, int lim)
{
    int c;
    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];
}

//如果如果缓冲区中有内容,就从缓冲区中读,否则从输入流中读
int getch(void)
{
    return (bufp > 0) ? buf[--bufp] : getchar();
}

//将一个字符压入缓冲区
void ungetch(int c)
{
    if (bufp >= BUFSIZE)
        printf("ungetch: too many characters\n");
    else
        buf[bufp++] = c;
}




      在递归打印树的时候,先打印左子树,再打印节点,最后打印右子树,如此循环。如图例,打印顺序如下1->7。

        需要注意的是,如果单词不是按照随机顺序到达的,树将会变得不平衡,这种情况下,程序的运行时间将大大增加。最坏的情况下,若单词已排好序,则程序模拟线性查找的开销非常大。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值