树ADT

前言
  本文在完成过程中,得到许多朋友的帮助,在此谢谢诸位。本人欢乐无限,技术有限,如有任何问题,欢迎各位不吝赐教。

1 树ADT

  和线性表一样,树的数据对象D是具有相同特性的数据元素的集合。D中元素的个数记作n。
  树和其它数据结构的主要不同体现在的元素之间的数据关系方面,而且树是一种非线性结构。树的数据关系可以递归的描述为:若数据对象集合D为空集,则称为空树;否则在D中存在唯一的称为根的数据元素r,并且当n>1时,其余结点可分为k(k>0)个互不相交的有限集T1, T2, …, Tk, 其中每一个子集本身又是一棵符合本定义的树,称为根r的子树。
  例如,下面是一棵树,其中A是根结点,A有三颗子树:T1={B, E, F}, T2={C}, T3={D, G, H, I}。结点B,C,D又分别是子树T1,T2,T3的根。根与其子树的根是用一条有向边连接(要注意的是,在图中通常画成一条自上而下的无向边)。我们把B,C,D称作是A的儿子(又称为孩子),反之A是B,C,D的父亲。因为树是递归定义,还有诸如I是G的唯一的儿子,G是I的父亲等关系。
  1
  
  除此之外,还经常用到以下定义:

  • 结点的度:该结点的子树数目。
  • 树的度:树中各节点度数的最大值。
  • 兄弟:具有相同父亲的。
  • 树叶(叶子):没有儿子的结点(度为零的结点)。
  • 分支结点:有儿子的结点(度不为零的结点) 。
  • 从n1到nk的路径:为结点n1, n2, …, nk的一个序列,其中ni是ni+1的父亲,对于1≤i<k。
  • 路径的长:该路径上边的条数。
  • 结点ni的深度:从根到ni的路径的长。
  • 树的深度:树的最深的树叶的深度。
  • 结点ni的高:从ni到最深树叶的路径的长。
  • 树的高度:根的高。
  • 结点的大小:结点拥有的子孙数(包括结点本身)。
  • 树的大小:根的大小。
      

2 树的实现

2.1 树的模型

  对于树的模型的设计,关键在于如何能够快速找到某一结点的各个儿子。但由于每个结点的儿子数目可能事先并不确定,或者即使是确定的但可能数目均不相同,所以不能把指向每一个儿子的指针直接放在结点中。简单的想法就是建立结点的儿子链表,将所有儿子存储在一个链表中。例如,对于上面图中的A结点,它由三个儿子B,C,D。初步的模型可以是下面这样:
2
  而实际上,C是B的右兄弟,D是C的右兄弟,所以可以将指向右边兄弟的指针放在模型中。至此树的模型就可以确定了,看下图:
3
  从上面的图中可以看出,结点包含两个域:数据域element和指针域leftChild,rightSibling。为简单起见假设数据域里面只有关键字没有卫星数据。指针leftChild指向最左边的儿子结点,指针rightSibling指向右边相邻的兄弟结点。于是最前面的树可以表示成下面:
4

2.2 树的遍历

  我们经常需要输出树的所有关键字。沿着某条搜索路线,依次对树中每个节点访问一次且仅访问一次,这种操作称作树的遍历(Traversal)。常见的有先序遍历、后序遍历和层序遍历。由于层序遍历有的不多,这里暂时不讨论。
  先序遍历(Preorder Traversal)总是先处理当前结点,然后再处理各个子树。由于树的递归定义,先序遍历的递归算法可以描述如下:

  • 首先访问根结点
  • 然后依次递归地先序遍历根的各个子树T1, T2, …, Tk。

\
  例如最前面的树的先序遍历输出的关键字序列为:ABEFCDGIH。
  后序遍历(Postorder Traversal)则是先处理各个子树,最后处理当前结点。后序遍历的递归算法可以描述如下:

  • 首先依次递归地后序遍历根的各个子树T1, T2, …, Tk;
  • 访问根结点。

\
  前面的树的后序遍历输出的关键字序列为:EFBCIGHDA。

2.3 树的实现

  首先是Tree.h:

typedef char ElementType;

#ifndef TREE_H
#define TREE_H

struct TreeNode;
typedef struct TreeNode *PtrToNode;
typedef PtrToNode Position;
typedef PtrToNode Tree;

int isEmpty( Tree T );
Tree makeEmpty( Tree T);
Tree createTree(Tree T);

void preorderTraversal(Tree T);
void postorderTraversal(Tree T);
ElementType retrieve(Position p);

#endif

  Fatal.h:

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

#define Error( Str )        FatalError( Str )
#define FatalError( Str )   fprintf( stderr, "%s\n", Str ), exit( 1 )

  Tree.c:

#include <stdio.h>
#include <stdlib.h>
#include "Tree.h"
#include "Fatal.h"

//left-child, right-sibling representation
struct TreeNode{
    ElementType element;
    PtrToNode leftChild;
    PtrToNode rightSibling;
};

int isEmpty( Tree T )
{
    return T == NULL;
}

//既不是先序遍历也不是后序遍历
Tree makeEmpty( Tree T )
{
    if( T != NULL ){
        makeEmpty( T->leftChild );
        makeEmpty( T->rightSibling );
        free( T );
    }
    return NULL;
}

Tree createTree(Tree T)
{
    char c;
    scanf("%c%*c", &c);
    if(c == '#') 
        T = NULL;
    else {
        T = malloc( sizeof( struct TreeNode ) );
        if(T == NULL)
            FatalError("Out of memory!!!");
        T->element = c;

        printf("%c的第一儿子:", T->element);
        T->leftChild = createTree( T->leftChild );
        printf("%c的下一兄弟:", T->element);
        T->rightSibling = createTree( T->rightSibling );
    }
    return T;
}

void preorderTraversal(Tree T)
{
    if(T){
        printf("%c ", T->element);
        for(PtrToNode child = T->leftChild; child; child = child->rightSibling)
            preorderTraversal(child);
    }
}

void postorderTraversal(Tree T)
{
    if(T){        
        for(PtrToNode child = T->leftChild; child; child = child->rightSibling)
            postorderTraversal(child);
        printf("%c ", T->element);  
    }
}

ElementType retrieve(Position p)
{
    return p->element;
}

  main.c:

#include <stdio.h>
#include <stdlib.h>
#include "Tree.h"


int main(int argc, char *argv[]) 
{
    Tree T = makeEmpty(NULL);

    printf("请输入树根(#表示无此节点): ");
    T = createTree(T);

    printf("\n树的先序遍历:");
    preorderTraversal(T);

    printf("\n树的后序遍历:");
    postorderTraversal(T);

    printf("\n树根为:%c", retrieve(T));

    T = makeEmpty(T);
    return 0;
}

2.3 树的应用

  众所周知,UNIX、Linux和windows等操作系统的目录结构均采用树形结构。例如,下面是UNIX系统的一个目录树。
这里写图片描述

  蓝色表示目录,黑色表示文件。现在我们想对这个UNIX目录做两件事情:
  (1)采用适当的缩进方式将目录显示出来,如下图:
这里写图片描述

  (2)计算出每一个目录和文件的大小。
  下面的程序完成了上面的任务。首先是File.h:

typedef char ElementType;

#ifndef FILE_H
#define FILE_H

struct TreeNode;
typedef struct TreeNode *PtrToNode;
typedef PtrToNode File;

int isEmpty( File D );
File makeEmpty( File D );
File createFile(File D);

void listDirectory(File D);

void sizeDirectory(File D);

#endif

  fata.h

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

#define Error(Str)          FatalError(Str)
#define FatalError(Str)     fprintf(stderr, "%s\n", Str), exit(1)

  File.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "File.h"
#include "Fatal.h"

struct TreeNode{
    ElementType element[100];
    int size;
    PtrToNode leftChild, rightSibling;
};

int isEmpty( File D )
{
    return D == NULL;
}

File makeEmpty( File D )
{
    if( D != NULL ){
        makeEmpty( D->leftChild );
        makeEmpty( D->rightSibling );
        free( D );
    }
    return NULL;
}

File createFile(File D){
    char c[100];
    scanf("%s", c);    
    if(c[0] == '*') 
        D = NULL;
    else {      
        D = malloc(sizeof(struct TreeNode));
        if(D == NULL)
            FatalError("Out of memory!!!");
        strcpy(D->element, c);
        printf("输入它的大小: ");
        scanf("%d",&(D->size));

        printf("%s的第一儿子:",D->element);
        D->leftChild = createFile(D->leftChild);
        printf("%s的下一兄弟:",D->element);
        D->rightSibling = createFile(D->rightSibling);
    }
    return D;
}

static void printName(File D, int depth)
{
    for(int i = 0; i < depth; i++)
        printf("    ");
    printf("%s\n", D->element);
}

static void listDir(File D, int depth)
{
    printName(D, depth);
    if(D)       
        for(PtrToNode child = D->leftChild; child; child = child->rightSibling)
            listDir(child, depth + 1);
}

void listDirectory(File D)
{
    listDir(D, 0);
}

static int sizeDir(File D, int depth)
{
    int totalSize = D->size;

    if(D){
        for(PtrToNode child = D->leftChild; child; child = child->rightSibling)
            totalSize += sizeDir(child, depth + 1);

        printf("\t\t\t\t%d\r", totalSize);
        printName(D, depth);
    }

    return totalSize;
}

void sizeDirectory(File D)
{
    sizeDir(D, 0);
}

 
  main.c

#include <stdio.h>
#include <stdlib.h>
#include "File.h"

int main(int argc, char *argv[]) 
{
    File D = makeEmpty( NULL );
    printf("输入一个文件或者目录的名字(*表示无): ");
    D = createFile( D );

    printf("目录列表:\n");
    listDirectory( D );

    printf("目录大小:\n");
    sizeDirectory( D );
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值