实现过程涉及到树的基本数据结构及平衡性判断,文件的序列化和反序列化方法。 代码实现是在Linux环境下,包含部分的系统调用比如文件的读写和关闭。

目标

  1. 在内存中构建一棵十叉非完全树
  2. 采用一定的存储结构实现将该树存储到文件
  3. 将文件中存储的树重构到内存,并可以判断树的平衡性

设计思路及实现

树的存储于重建主要是数据结构的选择,代码中使用的是一个节点类,其中包括了数据域,高度,节点子树的2的幂的和。当当前节点的所有孩子节点不为空的时候,child_num有如下的计算式:

其中i是当前节点的第i个孩子,branch_num是节点的最大节点数。当当前节点的孩子节点存在空节点的时候,如第i个孩子为空的时候,直接跳过2^i。

例如对于如下的三叉树而言,节点8的所有孩子都为空,所以其child_num为0 。节点1三个孩子都不为空,child_num = 2^0 + 2^1 + 2^2为7.根据数字7可以断定三个孩子的位置情况。
此外,节点类还包含了节点的父节点,以及branch_num个子树节点指针。

    class TNode{public:int data;int height;int child_num;//child_num 根据孩子节点的数目生成的2的幂的和TNode *parent;TNode * child[BRANCH_NUM];public:TNode(){data = 0;height = 0;child_num = 0;parent = NULL;for(int i=0;i<BRANCH_NUM;i++)child[i] = NULL;}~TNode(){}bool Serialize(int fd){}bool Deserialize(int fd){}};

上述的树根节点定义与普通的树相同,只是多了一个int型的child_num值。Child_num在序列化和反序列化的时候计算过程如下:

    int rear = 0;int queue[BRANCH_NUM];memset(queue,0,BRANCH_NUM);//根据child_num的十进制值,准换为2进制并存储在queue中//用来判断当前节点的那个子节点不为空//queue[i] 为1代表当前节点的第i个子树不为空//根据queue[]的取值,依次对不为空的子节点进行但序列化while(t_child_num > 0){queue[rear] = t_child_num%2 ;t_child_num = t_child_num/2 ;rear++;}

在重构树的时候,根据从文件中读取的节点的child_num的值,计算当前节点的叶节点的存在情况,结果存在于一个queue[]长度为branch_num的0,1数组中。
在序列化到文件的时候,直接根据节点是否为空,将节点存入文件。
该值用于计算当前叶子节点的有无,便于序列化和反序列化,同时在存储文件的时候后,可以减少空的节点空间,提高空间利用率。对于一个有n个节点的树而言,这种结构会多占用4n个字节的存储空间,但是文件存储过程中不会出现空余的节点,节点之间是相邻的,这样对于多叉树,单支树,不平衡树等的存储效率较高,而且可重构性好。当前节点的叶子节点信息都保存在了一个int型数值当中。 除了树的结构设计以及添加一个int型数据成员之外,其他的存储重构方法和普通树的存储重构方法相同。

完整的代码实现过程如下:

    #include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/stat.h>#include<time.h>#define HEIGHT 7 //树的高度#define BRANCH_NUM 10 //树的子树个数#define NODE_NUM 8 //节点数目//使用一个简单的二维测试数组来按照高度和子树个数的要求生成一个树//其中数组的第一维是当前节点存入的值,第二维是当前节点的子树节点按位的二进制值的和//其中第一个子树为2^0 第二棵子树为2^1 …… 依次类推并求和,就是当前节点的child_numint array_tree[NODE_NUM][2] = {{1,7},{2,5},{3,0},{4,0},{5,6},{6,0},{7,0},{8,0}};int array_index ;/***树的节点定义:*成员变量分别是:节点存放的内容data; 当前节点所在的高度height*                当前节点的子树child_num; 当前节点的双亲节点指针parent        *               当前节点的孩纸节点指针,为一个指针数组child[]*成员函数:无参构造函数TNode()*           析构函数*           节点序列化函数bool Serialize(int fd);参数为文件描述符*           将当前节点序列化到文件fd,成功为true*           节点反序列化函数bool Deserialize(int fd);参数为文件描述符*/class TNode{public:int data;int height;int child_num;//child_num 根据孩子节点的数目生成的2的幂的和TNode *parent;TNode * child[BRANCH_NUM];public:TNode(){data = 0;height = 0;child_num = 0;parent = NULL;for(int i=0;i<BRANCH_NUM;i++)child[i] = NULL;}~TNode(){}bool Serialize(int fd){if(fd == -1){printf("Serilize: file not open correctly\n");return false;}if(write(fd, &data, sizeof(int)) == -1){printf("Serilize: write data failed\n");return false;}if(write(fd, &height, sizeof(int)) == -1){printf("Serilize: write height failed\n");return false;}if(write(fd, &child_num, sizeof(int)) == -1){printf("Serilize: write child_num failed\n");return false;}return true;}bool Deserialize(int fd){if(fd == -1){printf("Deserialize: file not open correctly\n");return false;}int r = -1;r = read(fd, &data, sizeof(int));if((0 == r) || (-1 == r)){printf("Deserialize: read data failed\n");return false;}r = read(fd, &height, sizeof(int));if((0 == r) || (-1 == r)){printf("Deserialize: read height failed\n");return false;}r = read(fd, &child_num, sizeof(int));if((0 == r) || (-1 == r)){printf("Deserialize: read child_num failed\n");return false;}return true;}};/***树的序列化函数:*   TNode *t 当前的树节点,若不为空就将其序列化到文件*   int fd 文件描述符* 树的所有节点按照先序遍历递归的序列化到文件fd* 其中节点与节点之间不存在空闲存储空间*/void Serialize_Tree(TNode *t,int fd){if(t!=NULL){if(t->Serialize(fd) == false){printf("Serialize node failed\n");}for(int i=0;i<BRANCH_NUM;i++){//递归调用节点的序列化函数,将树的节点按先序存入文件中Serialize_Tree(t->child[i],fd);}}}/***树的反序列化(重构到内存):*   TNode *t 当前的树节点*   int fd 文件描述符**   从当前的文件中读取一个节点,判断是否是树的节点,然后根据child_num值*   确定哪个子节点不为空,然后先序反序列化其对应的子节点*/TNode * Deserialize_Tree(TNode *t,TNode *p,int fd){t = (TNode *)malloc(sizeof(TNode));if(t->Deserialize(fd) == false)return NULL;t->parent = p;int t_child_num = t->child_num;//这里的rear和queue是不能作为全局变量的//因为递归调用会将多个子树的结果写入这个//全局变量中,造成满树的反序列化结果变为一棵单支树//递归函数中的变量的特点int rear = 0;int queue[BRANCH_NUM];memset(queue,0,BRANCH_NUM);//根据child_num的十进制值,准换为2进制并存储在queue中//用来判断当前节点的那个子节点不为空//queue[i] 为1代表当前节点的第i个子树不为空//根据queue[]的取值,依次对不为空的子节点进行但序列化while(t_child_num > 0){queue[rear] = t_child_num%2 ;t_child_num = t_child_num/2 ;rear++;}//用child_num的值确定当前节点的子树节点是否都已经反序列化完毕if(t->child_num > 0){for(int i=0;i<BRANCH_NUM;i++){if(queue[i] == 1){t->child[i] = Deserialize_Tree(t->child[i],t,fd);}else{t->child[i] = NULL;}}}//返回反序列化成功的树的根节点return t;}/***按照给定的一个数组构造一棵满足高度和子树个数要求的树*/TNode * init_Tree_by_array(TNode *t,TNode *p,int h,int array[][2]){if(array_index >= NODE_NUM)return NULL;t = (TNode *)malloc(sizeof(TNode));t->parent = p;t->data = array[array_index][0];t->child_num = array[array_index][1];t->height = h;printf("array index :%d\n",array_index);array_index++;int t_child_num = t->child_num;int rear = 0;int queue[BRANCH_NUM];memset(queue,0,BRANCH_NUM);//根据child_num的十进制值,准换为2进制并存储在queue中while(t_child_num > 0){queue[rear] = t_child_num%2 ;t_child_num = t_child_num/2 ;rear++;}//根据queue的值递归初始化子树for(int i=0;i<BRANCH_NUM;i++){if(queue[i] == 1){t->child[i] = init_Tree_by_array(t->child[i],t,h+1,array);}else{t->child[i] = NULL;}}return t;}/***默认的初始化函数:调用树节点的默认构造函数,初始化一个满树*/TNode * init_Tree(TNode *t,TNode *p,int h){if(h > HEIGHT){return NULL;}t = (TNode *)malloc(sizeof(TNode));t->height = h;t->data = h;t->parent = p;//这里默认生成的是完全树,所以child_num在init_Tree阶段暂时未确定//当改用数组初始化构造树的时候,可以根据孩子节点的数目,计算出child_sumfor(int i=0;i<BRANCH_NUM;i++){t->child[i] = init_Tree(t->child[i],t,h+1);}return t;}/***自定义的计算幂运算的函数 my_pow(int a,int b)*结果返回a的b次幂*/int my_pow(int a,int b){int result = 1;for(int i=0;i<b;i++)result = result*a;return result;}/***计算子树子节点2^i次幂的和,其中i是子树位于当前节点的第i个节点*calc_child_num(TNode **t)传入参数是一棵树节点的指针的指针,修改该指针指向的*树的各个节点的child_num值*/void calc_child_num(TNode **t){if((*t) != NULL){for(int i=0;i<BRANCH_NUM;i++){if((*t)->child[i] != NULL){(*t)->child_num = (*t)->child_num + my_pow(2,i);}}for(int j=0;j<BRANCH_NUM;j++)calc_child_num(&((*t)->child[j]));}}/***void Print_Tree(TNode *t,int h):参数是树根节点和节点的当前高度*按照缩进打印一棵树,子节点比双亲节点多缩进两个空格*/void Print_Tree(TNode *t,int h){if(t != NULL){printf("%*s",2*h,"");printf("%d \n",t->data);for(int j=0;j<BRANCH_NUM;j++){Print_Tree(t->child[j],h+1);  }}}/***void get_child_list(TNode **t,TNode **r):获取当前树的叶节点的链表*参数为树节点的指针的指针,以及一个尾节点指针*输出结果是使用当前给定树的所有叶节点的最右孩子的指针,将叶节点串联起来*形成一个叶节点链*/void get_child_list(TNode **t,TNode **r){if((*t) != NULL){bool flag = true;int i = 0;for(i=0;i<BRANCH_NUM;i++){if((*t)->child[i] != NULL){flag = false;}if(flag == false)break;}if(flag == true){(*r)->child[BRANCH_NUM - 1] = *t;*r = *t;//    printf("%d ",(*r)->data);}for(i=0;i<BRANCH_NUM;i++){get_child_list(&((*t)->child[i]),r);}}}/***bool is_balance_tree(TNode **t):*判断当前给定的树是否是一个平衡树*根据给定的树,遍历得到所有叶节点的height值,找出差值最大的*若最大差值小于1,那么就是平衡的,返回TRUE否则返回false*/bool is_balance_tree(TNode **t){TNode *temp;temp = (TNode *)malloc(sizeof(TNode));TNode *cl,*r;cl = temp;r = temp;get_child_list(t,&r);TNode *p = temp;int max = 0;int min = HEIGHT;while(p != r){//printf("%d  ",p->child[BRANCH_NUM-1]->height);if(max < p->child[BRANCH_NUM-1]->height)max = p->child[BRANCH_NUM-1]->height;if(min > p->child[BRANCH_NUM-1]->height)min = p->child[BRANCH_NUM-1]->height;p = p->child[BRANCH_NUM-1];}//printf("\n");printf("max height %d  min height %d \n",max,min);if(((max-min) >=0 )&& ((max-min)<=1)){printf("this is a balance tree\n");return true;}printf("this is not a balance tree\n");return false;}int main(){printf("running.....\n");time_t start ,end;float time_len;TNode *root;//默认初始化为满树root = init_Tree(root,NULL,1);//计算树的所有节点的child_num值calc_child_num(&root);//序列化前,输出树//printf("Befor serialize:\n");//Print_Tree(root,1);//将树序列化到文件int fd = 0;fd = open("tree.dat", O_RDWR | O_CREAT | O_TRUNC, 0);Serialize_Tree(root,fd);close(fd);start = time(NULL);//再将其反序列化到内存TNode *d_root;fd = open("tree.dat",O_RDONLY);d_root = Deserialize_Tree(d_root,NULL,fd);close(fd);end = time(NULL);//反序列化之后,将其输出,与序列化之前进行对比//printf("Deserialize result:\n");//Print_Tree(d_root,1);is_balance_tree(&d_root);time_len = end - start;printf("run  %f seconds\n",time_len);// 试验部分的代码/*TNode *a_root;array_index = 0;//根据给定的初始化数组构建一棵树a_root = init_Tree_by_array(a_root,NULL,1,array_tree);printf("Init by array:\n");Print_Tree(a_root,1);//序列化到tree_1.dat文件中int fd = 0;fd = open("tree_1.dat", O_RDWR | O_CREAT | O_TRUNC, 0);Serialize_Tree(a_root,fd);close(fd);start = time(NULL);//从tree_1.dat中重构处一棵树TNode *d_root;fd = open("tree_1.dat",O_RDONLY);d_root = Deserialize_Tree(d_root,NULL,fd);close(fd);end = time(NULL);printf("Deserialize result:\n");Print_Tree(d_root,1);//判断是否是平衡树is_balance_tree(&d_root);time_len = end - start;printf("run %f seconds\n",time_len);*/}

结果验证

对于上述的实现代码,使用了两个用例进行了测试。
第一种测试是无输出的十叉树,高度为7.实现了树的初始化构造,序列化到文件,以及从文件中重构到内存和最后对其是否是平衡树进行了判定.根据树的节点大小以及十叉7层满树节点个数得到的序列化文件大小如下图:

其中7层19叉满树一共有:1+10+100+…+1000000 = 1111111个节点,每个节点存放3个int型共12字节的数据,总共文件大小为:13333332个字节。程序运行时间为4.000000秒。

第二种测试是带有简单输出的测试,树的高度为3深度为3.树在序列化与反序列化之后都进行了输出。根据输出的格式,得到了预期的结果。根据一个二维数组构造的3叉3层非满的平衡树的测试输出结果为:

【Linux】【C/C++】十叉非完全树的构造和重构相关推荐

  1. Linux运维十年面试总结

    Linux运维十年面试总结 一.有文件 file1 1.查询 file1 里面空行的所在行号 awk '{if(KaTeX parse error: Expected group after '^' ...

  2. 嵌入式 Linux 入门(十、Linux 下的 C 编程)

    嵌入式 Linux 入门第十课,聊聊 linux 下的 C 编程...... 矜辰所致 插一句,问题讨论群在文末的推广,以后大家提问可以在群中,即便我不在也能看到历史记录. 目录 前言 一.C 语言编 ...

  3. 【Linux + Makefile】十分钟教你学会Makefile的FORCE

    相信大家在使用Linux环境编程的时候,一定接触过Makefile这个玩意.Makefile在搭建自定义的编译环境,尤其是自动化编译.多功能一键编译等功能上,还是发挥了很大的作用.如果接触过Linux ...

  4. linux函数的阻塞与非阻塞IO及错误处理

    linux函数的阻塞与非阻塞IO及错误处理 1.阻塞是指进程等待某一个事件的发生而处于等待状态不往下执行,如果等待的事件发生了则会继续执行该进程.调用系统阻塞函数可能会导致进程阻塞进入睡眠状态. 2. ...

  5. linux网络编程--阻塞与非阻塞

    linux网络编程--阻塞与非阻塞 建立连接 接受连接 无阻塞的设置方式 read() write() 读操作 写操作 Linux fcntl函数详解 功能描述 函数原型 fcntl()函数五种功能 ...

  6. 十大非著名之父:手机,黑莓,iPod,FORTRAN,远程办公,鼠标,垃圾邮件,DSL,Java,WIFI说,我爸是...-asp.net关注...

    不为人知的科技之父们 - Lzhi's Views 如今,当你打着手机.听着iPod.在电脑前点击鼠标,你知道这些都是谁发明创始的吗?岁末年初,当我们回首盘点,别搞错了,iPod并非著名的史蒂夫· 乔 ...

  7. linux中ftp的工作原理,Linux系统学习 十二、VSFTP服务—简介与原理

    1.简介与原理 互联网诞生之初就存在三大服务:WWW.FTP.邮件 FTP主要针对企业级,可以设置权限,对不同等级的资料针对不同权限人员显示. 但是像网盘这样的基本没有权限划分. 简介: FTP(Fi ...

  8. 关于linux中socket阻塞与非阻塞

    关于linux中socket阻塞与非阻塞,网上有很多.这里我只说说我个人的体会: INT send(...INT nSendSize)函数: 阻塞: 如果内核缓冲区有足够大的缓冲区(>= nSe ...

  9. linux学习第十周总结

    linux学习第十周总结 接着上周的mysql,重点终结日志管理 ,备份还原,主从复制和mysql集群 一.mysql日志管理 事务日志 transaction log 错误日志 error log ...

最新文章

  1. 使用回调函数实现图像阈值分析。程序运行后在屏幕中输入阈值,通过改变滑动条实现不同类型的二值化图。
  2. 面试官:给我说一下 Spring MVC 拦截器的原理?
  3. git中的删除命令操作
  4. 金士顿sd卡恢复软件_手机上使用SD内存卡会让手机性能变差?
  5. 使用IKE预共享密钥配置IPsec
  6. mysql 索引条件推送_MySQL 处理where条件 index condition pushdown索引条件下推
  7. LeetCode题库5:最长回文子串——JavaScript解答
  8. 我的本科毕业论文——Messar即时通讯系统
  9. Tortoise SVN 如何汉化(最简单的处理方式,一看就会)
  10. struts2 中 Actionsupport类的作用
  11. matpower和pandapower数据的转化
  12. DC-DC LLC转换器 matlab simulink仿真显示了一个DC-DC LLC功率转换器与频率控制
  13. 计算机如何格式化和重装系统,怎样格式化c盘重新安装系统_重装系统时如何格式化C盘...
  14. Python北京二手房房价数据集分析
  15. 普通最小二乘法的两种推导方法
  16. java的 内省机制_Java内省机制
  17. NOIP2017初赛试题
  18. 《我是歌手》网上报名评审
  19. 微软证书漏洞CVE-2020-0601 高危漏洞加固指南
  20. 区块链如何赋能传统会员积分体系,产生溢出效应

热门文章

  1. mysql模糊查询 汉字为何不起作用_mysql中文模糊查询遇到的有关问题,各位救急...
  2. c语言程序设计分段定时器,单片机C语言程序设计:按键控制定时器选播多段音乐...
  3. 【vue插件篇】vue-form-check 表单验证
  4. iPhoneX-关于底部的那个一个横条的问题
  5. ApiCloud云端管理平台(v.20151022)
  6. Oracle 基础之数据库管理
  7. #Note# 极客与团队-软件工程师的生存秘笈
  8. 敏捷开发的艺术读书笔记
  9. 最烦人的正则表达式记忆口诀
  10. Silverlight实用窍门系列:22.Silverlight使用WebService调用C++,Delphi编写的DLL文件【实例源码下载】...