参考:Mark Allen Weiss 著《数据结构与算法分析——C语言描述》(第二版)
1 树的定义
一棵树是一些节点的集合。这个集合可以是空集;若非空,则是一棵树由称作根的节点r以及0个或者多个非空的子树组成。这些子树中每一棵的根都被来自根 r的一条有向的边所连接。图1 就是一棵具体的树,
图1 一棵具体的树
2 树中的基本术语
2.1 叶子节点
没有儿子的节点
2.2 兄弟节点
具有相同父亲的节点
2.3 深度(deep)
对任意节点n的深度 = 根到节点n的唯一路径的长
补充:根的深度为0
2.4 高度
节点X处的高度是从X到一片树叶的最长路径的长
补充:一棵树的高度 = 它的根的高
2.5 举例说明
在图1中,根节点为A,A的儿子节点为B、C、D、E、F、G。叶子节点有B、C、H、I、P、Q、K、L、M、N。互为兄弟节点的有K、L和M。K、L和M的父节点为F。图1 是一棵高度为3的树。图中还有其它关系,这里就不一一举例了,有兴趣的话,可以自己找一找。
3 树的实现
3.1 实现思路
每一个节点除数据外还要一些指针,使得该节点的每一个儿子都有一个指针指向它。由于每个节点的儿子数可以变化很大且事先不知道。
因此,在数据结构中建立到各儿子节点直接的链接是不可行的,此方法会浪费太多的空间。可以将每个节点的所有儿子都放在树节点的链表中。
3.2 实现的伪代码
typedef struct TreeNode *PtrToNode; struct TreeNode { ElementType Element; //存储数据 PtrToNode FirstChild; //指向第一个儿子节点的指针 PtrToNode NextChild; //指向下一兄弟节点的指针 };
4 树的基本操作
如果一个树只能存储而无法读取,也就无法使用。那么我们还设计这种结构还有什么意义!遍历又分为先序遍历(preorder traversal)和后序遍历(postorder traversal)两种。
(1)先序遍历(preorder traversal)思想:对节点的处理工作是在它的诸儿子节点被处理之前进行的。
(2)后序遍历(postorder traversal)思想:在一个节点处的工作是在它的诸儿子节点被计算后进行的
5 树的应用
包括UNIX、VAX/VMS 和DOS在内的许多常用操作系统中的目录结构。下面列举两个案例来说明:“列出分级文件系统中目录的例程”和“计算一个目录大小的例程”。
5.1 伪代码
(1)“列出分级文件系统中目录的例程”的伪代码如下,
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 ListDirectory(DirectoryOrFile D) { ListDir(D, 0); }
(2)“计算一个目录大小的例程”的伪代码如下,
static int SizeListDirectory(DirectoryOrFile D) { int TotalSize; TotalSize = 0; if(D is a directiry) for each child, C of D: TotalSize += SizeListDirectory(C); return TotalSize; }
5.2 python语言实现
(1)“列出分级文件系统中目录的例程”的python语言实现代码如下,
1 import os 2 3 4 # 按照一定格式打印文件或目录名称 5 def listDir(dir, num): 6 """ 7 :param dir: absolute path 8 :param num: the number of "/" before your firstinput path 9 :return: None 10 """ 11 # 1.列举出,当前给定的文件夹下的所有文件,以及子文件 12 file_list = os.listdir(dir) 13 14 # 2. 获取当前路径下文件夹名称并打印 15 # 2.1 获取当前文件夹名称 16 index = dir.rfind("/") 17 if index == -1: 18 file = dir 19 file = dir[index + 1:] 20 # 2.2 打印文件名及 21 print(file) 22 23 # 3.针对于列举出的列表,进行遍历 24 for fname in file_list: 25 # 3.1 拼接当前文件的路径 26 new_fname = dir + "/" + fname 27 # 3.2 控制每行打印时的段前空格数 28 sizedepth = new_fname.count("/") - num 29 30 # 3.3 判断是否为目录 31 if os.path.isdir(new_fname): 32 print(end="\t" * sizedepth) 33 listDir(new_fname, num) 34 else: 35 # 打印文件名称 36 print("\t" * (sizedepth) + fname) 37 38 # 测试代码 39 40 41 if __name__ == '__main__': 42 path = "F:/python_AI/python基础/python文件操作/files1" 43 num = path.count("/") 44 print("num = ", num) 45 listDir(path, num)
执行后结果如图2:
图2 打印目录结果
(2)“计算一个目录大小的例程”的python语言实现代码如下,
(i)文件大小计算模块sizedir.py文件,文件内容如下:
1 import os 2 3 totalSize = 0 4 # 计算文件或目录下的所占大小 5 def cal_size(path): 6 """ 7 :param path: an absolute path 8 :return: the size of the input path(file or directory) 9 """ 10 if not os.path.isdir(path): 11 print('Error:"', path, '" is not a directory or does not exist.') 12 return 13 global totalSize 14 for lists in os.listdir(path): 15 sub_path = os.path.join(path, lists) 16 # print(sub_path) 17 if os.path.isfile(sub_path): 18 totalSize = totalSize+os.path.getsize(sub_path) # 文件总大小 19 elif os.path.isdir(sub_path): 20 cal_size(sub_path) # 递归遍历子文件夹 21 return totalSize 22 23 # K\B\G单位的转换 24 def sizeConvert(size): # 单位换算 25 """ 26 :param size: a number,int 27 :return: a number,float,Keep three decimal places after the decimal point. 28 """ 29 K, M, G = 1024, 1024**2, 1024**3 30 if size >= G: 31 return str(round(size/G, 3))+' G Bytes' 32 elif size >= M: 33 return str(round(size/M, 3))+' M Bytes' 34 elif size >= K: 35 return str(round(size/K, 3))+' K Bytes' 36 else: 37 return str(size)+' Bytes'
(ii)打印文件名称及大小模块my_size_dir.py,文件内容如下,
1 import os 2 import sizedir 3 4 # 打印文件或目录名称及所占内存大小 5 def listDir(dir, num): 6 7 # 1.列举出,当前给定的文件夹下的所有文件,以及子文件 8 file_list = os.listdir(dir) 9 10 # 2. 获取当前路径下文件夹名称并打印 11 # 2.1 获取当前文件夹名称 12 index = dir.rfind("/") 13 if index == -1: 14 file = dir 15 file = dir[index + 1:] 16 # 2.2 计算当前目录下的文件总大小 17 dir_size = sizedir.cal_size(dir) 18 # 2.3 打印文件名及文件大小 19 print(file, "\t(%s)" % sizedir.sizeConvert(dir_size)) 20 21 # 3.针对于列举出的列表,进行遍历 22 for fname in file_list: 23 # 3.1 拼接当前文件的路径 24 new_fname = dir + "/" + fname 25 # 3.2 控制每行打印时的段前空格数 26 sizedepth = new_fname.count("/") - num 27 28 # 3.3 判断是否为目录 29 if os.path.isdir(new_fname): 30 print(end="\t" * sizedepth) 31 listDir(new_fname, num) 32 else: 33 # 打印文件名称及大小 34 fsize = sizedir.sizeConvert(os.path.getsize(new_fname)) 35 print("\t" * (sizedepth) + fname, "\t(%s)" % fsize) 36 37 # 测试代码 38 if __name__ == '__main__': 39 path = "F:/python_AI/python基础/python文件操作/files1" 40 num = path.count("/") 41 # print(num) 42 listDir(path, num)
上面代码执行结果如图3:
图3 打印目录及大小结果
6 树的分类
树的分类如图4。
图4 树的分类