树-预备知识、实现、遍历及应用

        预备知识

        定义树的一种自然的方式是递归法。一棵树是一些点的集合。这个集合可以是空集;若非空,则一棵树由称作根(root)的节点 r 以及0个或多个非空的(子)树 T_{1},T_{2},...,T_{k}组成,这些子树中每一棵的根都来自根 r 的一条有向的边(edge)所连接。每一棵子树的根叫作根 r 的儿子(child),而 r 是每一棵子树的根的父亲(parent)。这就是递归定义。

        从递归定义中我们发现,一棵树是N个节点和N-1条边的集合,其中的一个节点叫作根。每一个节点可以有任意多个儿子,也可能是零个儿子。没有儿子的节点称为树叶(leaf)。具有相同父亲的节点称为兄弟(sibling);用类似的方法可以定义祖父(grandparent)和孙子(grandchild)关系。

        从节点n_{1}n_{k}的路径(path)定义为节点 n_{1},n_{2},...,n_{k} 的一个序列,使得对于1\leqslant i< k,节点 n_{i} 是 n_{i+1} 的父亲。这个路径的长(length)为该路径上边的条数,即k-1.。从每一个节点到它自己有一条长为0的路径,注意,在一棵树中从根到每个节点恰好存在一条路径。

        对任意节点n_{i},n_{i}的深度(depth)为从根到n_{i}的唯一路径的长。因此,根的深度为0。n_{i}的高(height)是从n_{i}到一片树叶的最长路径的长。因此所有的树叶的高都是0。一棵树的高等于它的根的高。

        树的实现

        实现树的一种方法可以使在每一个节点除数据外还要有一些指针。使得该节点对于每一个儿子都有一个指针指向它。然而由于每个节点的儿子数可以变化很大而且事先不知道,因此在数据结构中建立到各儿子节点的直接链接是不可行的。实际上解法很简单:将每个节点的所有儿子都放在树节点的链表中。

        如下为典型的声明:

typedef int ElementType;
typedef struct TreeNode* PtrToNode;

struct TreeNode
{
	ElementType element;
	PtrToNode firstChild;
	PtrToNode nextSibling;
};

一个节点的所有儿子在一个链表中,这个链表或许为空,或许由第一个儿子,以及第一个儿子的兄弟组成

        树的遍历及应用

        树有很多应用。流行的用法之一是许多操作系统中的目录结构。例如文件名 /usr/mark/book/ch1.r  ,这个目录的根是/usr,mark为usr的儿子,book为mark的儿子,ch1.r为book的儿子。在第一个"/"后的每个"/"都表示一条边;结果为一全路径名。在UNIX文件系统中的目录就是含有它所有儿子的一个文件。事实上,如果将打印一个文件的标准命令应用到一个目录上,那么目录中的这些文件名能够在输出中看到。

        假设我们想要列出目录中所有文件的名字。我们的输出格式将是:深度为d_{i}的文件的名字将被d_{i}次跳格(tab)缩进后打印出来。下面给出该算法的伪代码:

static void ListDir(DirectoryOrFile d, int depth);
{
	if (d is a legitimate entry)
	{
		PrintName(d, depth);
		if (d is a directory)
			for each child, c, of d
				ListDir(c, depth + 1);
	}
}
void ListDirecotry(DirectoryOrFile d)
{
	ListDir(d, 0);
}

        算法的核心为递归程序 ListDir,为了显示根时不进行缩进,该例程需要从目录名和深度0开始。这里的深度是一个内部簿记遍历,不是主调例程期望知道的参数,因此,驱动例程 ListDirectory 用于将递归例程和外界连接起来。

        这种遍历的策略叫作先序遍历(preorder traversal)。在先序遍历中,对节点的处理工作是在它的诸儿子节点被处理之前进行的。这里就是先打印当前目录文件名,在去处理儿子节点的文件名。

        另一种遍历树的方法是后序遍历(postorder traversal)。在后序遍历中,在一个节点处的工作是在它的诸儿子节点被计算后进行的。例如我们想要知道目录的大小,就要先知道子目录的大小,要先知道子目录的大小,就要先知道子子目录的大小。当子目录都被计算完了,最后才能知道目录的大小。

        如下是实现这种遍历策略的计算目录大小的 伪例程 

static void SizeDirectory(DirectoryOrFile d)
{
	int totalSize;
	totalSize = 0;
	if (d is a legitimate entry)
	{
		totalSize = FileSize(d);
		if (d is a directory)
			for each child, c, of d
				totalSize += SizeDirectory(c);
	}
	return totalSize;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值