前言
本文在完成过程中,得到许多朋友的帮助,在此谢谢诸位。本人欢乐无限,技术有限,如有任何问题,欢迎各位不吝赐教。
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的父亲等关系。
除此之外,还经常用到以下定义:
- 结点的度:该结点的子树数目。
- 树的度:树中各节点度数的最大值。
- 兄弟:具有相同父亲的。
- 树叶(叶子):没有儿子的结点(度为零的结点)。
- 分支结点:有儿子的结点(度不为零的结点) 。
- 从n1到nk的路径:为结点n1, n2, …, nk的一个序列,其中ni是ni+1的父亲,对于1≤i<k。
- 路径的长:该路径上边的条数。
- 结点ni的深度:从根到ni的路径的长。
- 树的深度:树的最深的树叶的深度。
- 结点ni的高:从ni到最深树叶的路径的长。
- 树的高度:根的高。
- 结点的大小:结点拥有的子孙数(包括结点本身)。
- 树的大小:根的大小。
2 树的实现
2.1 树的模型
对于树的模型的设计,关键在于如何能够快速找到某一结点的各个儿子。但由于每个结点的儿子数目可能事先并不确定,或者即使是确定的但可能数目均不相同,所以不能把指向每一个儿子的指针直接放在结点中。简单的想法就是建立结点的儿子链表,将所有儿子存储在一个链表中。例如,对于上面图中的A结点,它由三个儿子B,C,D。初步的模型可以是下面这样:
而实际上,C是B的右兄弟,D是C的右兄弟,所以可以将指向右边兄弟的指针放在模型中。至此树的模型就可以确定了,看下图:
从上面的图中可以看出,结点包含两个域:数据域element和指针域leftChild,rightSibling。为简单起见假设数据域里面只有关键字没有卫星数据。指针leftChild指向最左边的儿子结点,指针rightSibling指向右边相邻的兄弟结点。于是最前面的树可以表示成下面:
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;
}