1、树这种数据结构存在的意义
我们都知道最简单的数据结构分为数组,和链表,一种便于查找,一种便于插入。但是在大型存储或计算系统中,二者操作都很多,既有大规模的数据查找,又有新的数据或操作日志进行插入。因此树这种查找和插入介于两者之间的数据结构就变得十分常用。
我们所说的这些数据结构都是在内存中的。比如一颗树我们在内存中记录成以下的样子:
树这种数据结构在实际工程这种是没有下盘需求的,因为本身就是作为缓存用来加速读取和插入的。磁盘上面就是单纯的数据,对数据结构没必要保存到磁盘上。
以上述二叉树为例子,上述二叉树是一个二叉搜索树,总能保证左子树比根节点小。这些数据在磁盘上的就是直接存储的,在读取的过程中将树逐步建立起来:
14、8、15、11、10、17
是第一次读到14,构建了根节点,之后读到8,放在14的左子节点,15放在右子节点 ,11比14小,所以到左子树,11比8大,在8的右子树。。。以此类推。
2、序列化,反序列化二叉树
书接上文,面试要求将二叉树序列化,就是能够唯一颗二叉树对应一个字符串(因为有特殊符号表示空节点所以必须是字符串),然后利用这个字符串可以构造出树。其实这也有一定的需求,就是自己写小程序玩的时候,构造树的结构可以利用这个反序列化,输入一个字符串来构造一棵树,不需要逐个节点去构造了。
有的读者想,直接用前序遍历存储难道不行么?
即:
存储为14,8,11,10,15,17
可是这样,如下的树前序遍历也是这个结果:
简单来说,靠一种遍历方式无法唯一确定一棵树。因此,左右节点为空的,也要表示出来。还有,假如以字符串表示的话,每个节点前面要有特定标识,不然节点14无法和节点1加节点4区分开。
我们在每个节点前面都加上!,空节点用#表示,这棵树组成的字符串就是:
!14!8!11!#!#!10!#!#!15!#!17!#!#
这个字符就可以唯一的表示一棵树。
那么代码如何写呢?(推荐剑指offer这种的流写法)
https://github.com/zhedahht/CodingInterviewChinese2/blob/master/37_SerializeBinaryTrees/SerializeBinaryTrees.cpp
当然笔者自己也写出了一个类似的,无需流数据结构的代码:
class TreeToSequence {
public:
string toPreSequence(TreeNode* root) {
if (NULL != root)
{
string strTmp = "";
char StrIntTmp[20] = {0};
_itoa_s(root->val, StrIntTmp,20,10);
strTmp += "!";
strTmp += StrIntTmp;
strTmp += toPreSequence(root->left);
strTmp += toPreSequence(root->right);
return strTmp;
}
else
{
return "!#";
}
}
int BitNum(int n)
{
int i = 1;
while (n / 10)
{
n /= 10;
i++;
}
return i;
}
TreeNode* toTreeNode8Sequence(string Str)
{
if (Str[staticOffset] == '!')
{
staticOffset++;
}
if (Str[staticOffset] == '#')
{
staticOffset++;
return NULL;
}
TreeNode *treeTmp = new TreeNode();
treeTmp->val = atoi(&Str[staticOffset]);
staticOffset += BitNum(treeTmp->val);
treeTmp->left = toTreeNode8Sequence(Str);
treeTmp->right = toTreeNode8Sequence(Str);
return treeTmp;
}
int staticOffset = 0; //其中这个static是关键,因为每次根据字符串转换为树结构,到下次进入该函数时,不知道上次用了多少个字符,需要记录。而iostram的数据结构却可以较好的解决这个问题。
};