保存二叉树,以及在程序间传递二叉树

本文介绍如何通过文件操作来保存和在不同程序间传递二叉树数据结构,利用指针实现二叉树的序列化和反序列化。

对这个标题感兴趣的话,就直接来看下边的代码吧!
是借助文件操作来传递的。

//以下代码完整地展示了将一个二叉树结构存储为文件形式,并恢复为二叉树的过程。其中展示了一个玩具版的通信过程。(很容易拓展为程序间传递二叉树的情形,只要两个程序含有相同的函数组即可。)
//适合与数据域大小固定,且不含指针的情况。
//我在此承认它效率不高,属于重复造轮子,但是我想这个东西很还是适合初学者学习的。
//请逐一测试#if 0代码块。

//:::
//可以延伸的地方:转为头文件,可以在头中定义数据域类型,需要在下边的一些函数中插入对数据域的的访问函数。
//主要用到一些文件操作函数;潜在的风险为,文件被人篡改而出错。
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
/*
树节点由数据域和指针组成。
*/
/*
数据域的数据类型:
*/
struct valkind
{
	int ival;
	double fval;
	char cval;
};
/*
获得一个数据结构体:
*/
struct valkind makevalkind(int ival, double fval, char cval)
{
	struct valkind kd;
	kd.ival = ival;
	kd.fval = fval;
	kd.cval = cval;
	return kd;
}
/*
树节点的定义:
*/
struct binode
{
	struct valkind matter;
	struct binode* left, * right;
};
/*
树指针,node与tree含义相同的。
*/
typedef struct binode* node;
typedef node tree;//特指树根节点的指针。
/*
删掉树。
*/
void deltree(tree tre)
{
	if (tre != NULL) {
		deltree(tre->left);
		deltree(tre->right);
		free(tre);
	}
}
/*
获得一个树节点。注意node为指针。
*/
node makenode(struct valkind* matter)
{
	node nod = (node)malloc(sizeof(struct binode));
	assert(nod != NULL);
	nod->matter = *matter;
	nod->left = nod->right = NULL;
	return nod;
}
/*
样例,下边要多次使用。
*/
tree sample(void)
{
	tree tre = makenode(&makevalkind(1,1.0,'1'));
	tre->left = makenode(&makevalkind(2, 2.0, '2'));
	tre->right = makenode(&makevalkind(3, 3.0, '3'));
	tre->left->right = makenode(&makevalkind(4, 4.0, '4'));
	tre->left->right->left = makenode(&makevalkind(5, 5.0, '5'));
	tre->left->right->right = makenode(&makevalkind(6, 6.0, '6'));
	tre->right->left = makenode(&makevalkind(7, 7.0, '7'));
	tre->right->left->right = makenode(&makevalkind(8, 8.0, '8'));
	return tre;
}
/******************************
以上,为实现树的基本数据结构。
下边就是实现树的文件间(或者在更长的距离间)传输的方式了。
*******************************/
/*
先看一个函数,下边再进行解释。
*/
#if 0
//使用先序遍历:
void demo_print_with_structure(tree tre/*可以用特定的访问节点函数来处理*/)
{
	if (tre != NULL)
	{
		printf("(%d,%lf,%c)", tre->matter.ival, tre->matter.fval, tre->matter.cval);
		demo_print_with_structure(tre->left);
		demo_print_with_structure(tre->right);
	}
	else
	{
		printf("(NULL)");
	}
}
int main()
{
	demo_print_with_structure(sample());
}
#endif
/************************
好好观察这个例子,
【要说明的是,这个结构实际上反映了树的结构信息(前提是直到它的遍历顺序)。】
【关键就在于(NULL)标记。】在特定的访问方法中,【null的访问顺序被唯一确定,这就是反应树的结构信息的原因。】
下边,将先介绍怎么传输,以是你对于树的程序间传输的可行性具备一定的概念。
然后再介绍如何翻译传输结果。
***************************/
/*
传输。这里采用一个我喜欢的方法:
已知结构体长度固定,【不妨将其翻译为一串数字。【直接以【定长的十六进制字符串】的方式进行传输】】
不在这里展开了。直接参看下边的例子就会明白了:将sample转化为数字,并传递到一个文件中。重要到批注再函数中标记出来。
*/
void send_as_chain_to_file(tree tre, FILE* fp)
{
	/*
	条件:fp非空,在主函数中检测。
	遍历方法:先序。
	*/
	static const char* hex = "0123456789abcdef";//作用见下边。
	if (tre != NULL)
	{
		//注意是如何数字化的。
		//直接将数据域翻译为数字。
		//fputc('(', fp);
		for (int i=0;i<sizeof(tre->matter);i++)
		{
			unsigned char ch = ((unsigned char*)&tre->matter)[i];//获得第i个字节。
			fputc(hex[ch % 16], fp);
			fputc(hex[(ch/16) % 16], fp);
			/*
			这一步是重点。而且,【请注意视图】:每两个数据字符代表一个字节,左边为低四位,右边为高四位的值(小端输出)。
			*/
		}
		//fputc(')', fp);
		send_as_chain_to_file(tre->left, fp);
		send_as_chain_to_file(tre->right, fp);
	}
	else
	{
		//fputs("(NULL)", fp);
		fputc('N', fp);
	}
}
/*
括号对于实现程序是多余的,但这样看的比较清楚。
基于这个视图,很容易将数字串恢复为数据。
而且,【这个方法明显区分了节点数据与(NULL)分隔】,因为各自使用不同的字符集。
可知,内存数据段的长度为2*sizeof(struct valkind)。
*/
#if 0
int main()
{
	const char* name = "0d1e2m3o4.txt";//起个怪名字避免覆盖。仅用于示例。
	FILE* fp = fopen(name, "wt");//ps,无所谓,反正没有换行符。
	if (fp)
	{
		send_as_chain_to_file(sample(), fp);
	}
}
#endif
/*
这样一来就实现了将数据存到了外边,【可供另一个文件读取了。】
*/
/*
将字符转为数字。要求文件没有被篡改过:
*/
inline unsigned char ctoi(int c)
{
	assert(('0' <= c && c <= '9') || ('a' <= c && c <= 'f'));
	return '0' <= c && c <= '9' ? c - '0' : 10 +c - 'a';
}
/*
从文件中再次取得值:
该示例中的方法,将在下边再次应用。
*/
#if 0
int main()
{
	const char* name = "0d1e2m3o4.txt";
	FILE* fp = fopen(name, "wt");
	if (fp)
	{
		send_as_chain_to_file(sample() , fp);
		fclose(fp);


		//:::
		fp = fopen(name, "rt");//重新打开,并读取数据。t无所谓,反正没有换行符。
		if (fp)
		{
			/*
			【前提:文件没有被篡改过。】
			*/
			int ch = 1;
			struct valkind valbuffer = { 0 };

			while ((ch = fgetc(fp)) != EOF)
			{
				if (ch == 'N')putchar('N');//在执行树的恢复时,是另一个操作。这里简单地输出一个N。
				else
				{
					int p = 0;
					/*
					字符串还原为数据:
					*/
					unsigned char chsmallendian = ctoi(ch);
					unsigned char chbigendian = ctoi(fgetc(fp));
					((unsigned char*)&valbuffer)[p++] = chsmallendian + (chbigendian << 4);
					for (int i = 0; i < 2 * sizeof(struct valkind) - 2; i+=2)/*这里一开始犯了一个错误。是两个两个读的,但是i++了。。。*/
					{
						chsmallendian = ctoi(fgetc(fp));
						chbigendian = ctoi(fgetc(fp));
						((unsigned char*)&valbuffer)[p++] = chsmallendian + (chbigendian << 4);
					}
					/*
					验证的确恢复了数据:
					*/
					printf("%d,%lf,%c", valbuffer.ival, valbuffer.fval, valbuffer.cval);
				}
			}

			fclose(fp);
			remove(name);
		}
	}
}
#endif
/*************************
以上演示展示了数据传输的方法之一,
下边,就演示,【怎么将文件中的数据恢复为树形结构。】
注意是怎么利用N符号的。
*****************************/
//
/********************************
在获得文件时使用的是先序遍历的方法。
【也要用对应的的方法来恢复。】
注意:只要转为文件的方法与转为树的方法对应,就可以恢复原来的结构。
这相当于一项程序间的协议。
**********************************/

/*
试与上边的send_as_chain_to_file进行比较。
将文件中的字符数据转化为一个树。
条件:trep为【二级指针,指向待填充的tre指针。】要求非NULL,【由更下边的封装函数change_to_tree_packed保证。】
*/
void change_to_tree(tree* trep, FILE* fp)
{
	int ch = 1;
	ch = fgetc(fp);
	if (ch == 'N')
	{
		*trep = NULL;
	}
	else
	{
		//上一个main示例中的东西,翻译出一个结构体。
		int p = 0;
		struct valkind valbuffer = { 0 };

		unsigned char chsmallendian = ctoi(ch);
		unsigned char chbigendian = ctoi(fgetc(fp));
		((unsigned char*)&valbuffer)[p++] = chsmallendian + (chbigendian << 4);
		for (int i = 0; i < 2 * sizeof(struct valkind) - 2; i += 2)
		{
			chsmallendian = ctoi(fgetc(fp));
			chbigendian = ctoi(fgetc(fp));
			((unsigned char*)&valbuffer)[p++] = chsmallendian + (chbigendian << 4);
		}

		*trep = makenode(&valbuffer);
		change_to_tree(&((*trep)->left), fp);
		change_to_tree(&((*trep)->right), fp);
	}
}
/*
上边的是实现功能的函数。下边进行封装,以便于使用。
*/
tree change_to_tree_packed(const char* name)
{
	FILE* fp = fopen(name, "rt");
	if (!fp)return NULL;
	tree tre = NULL;
	change_to_tree(&tre, fp);
	fclose(fp);
	return tre;
}
/*
顺便将send_as_chain_to_file封装。
会出现如下问题:
-1:文件已经存在。
1:打开失败。
0:正常。
*/
int send_as_chain_to_file_packed(tree tre,const char* name)
{
	FILE* isexist = fopen(name, "rt");
	if (isexist!=NULL) { fclose(isexist); return -1; }
	FILE* fp = fopen(name, "wt");
	if (!fp)return 1;
	send_as_chain_to_file(tre, fp);
	fclose(fp);
	return 0;
}
/****************************
【由此,就实现了用字符串方式存储和传输树状结构的方法。】
当然,这个方法远算不上高效,但是仍很有启发意义。
【稍加改变,就可以用这个函数族进行【数据域固定的二叉树的传输】和【保存二叉树数据(简单地存储在文件中),从而使程序具有了记忆。】】
*****************************/
/*
下边是最后一个示例。
*/
#if 1
/*
展示后序访问的结果。之所以之前没有写是因为用不上。
注意:
*/
void visittree_lrv(tree tre)
{
	if (tre)
	{
		visittree_lrv(tre->left);
		visittree_lrv(tre->right);
		printf("*|%d,%lf,%c|*\n", tre->matter.ival, tre->matter.fval, tre->matter.cval);
	}
}
#define tree_saveasfile send_as_chain_to_file_packed
#define tree_readfromfile change_to_tree_packed
int main()
{
	const char* keepas = "demo.tree";
	tree tre = sample();
	visittree_lrv(tre);
	if (tree_saveasfile(tre, keepas) == 0)
	{
		//删除。
		putchar('\n');
		deltree(tre);
		//【下边的树,是从文件中重新获得的。】
		if ((tre = tree_readfromfile(keepas)) != NULL)
		{
			visittree_lrv(tre);
			deltree(tre);
		}
		remove(keepas);
	}
	else {
		printf("file exists or cannot open to write.");
	}
}

#endif```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值