注:转自http://blog.csdn.net/sgbfblog/article/details/7774347
(1)二叉搜索树的序列化与反序列化:
(1.0)背景:二叉树的序列化,即将二叉树按照某种遍历方法将各个节点的 值保持到文件中;
二叉树的反序列化,即根据文件中的值或者给出一个数组,然后来构造一个二叉树;
(1.1)问题:
设计一个算法,将一棵二叉搜索树(Binary Search Tree,BST)保存到文件中,需要能够从文件中恢复原来的二叉搜索树。注意算法的时空复杂度。
(1.2)思路:
二叉树遍历算法有先序遍历、中序遍历、后序遍历算法等。但是它们中间只有一种遍历算法符合题目条件,用于保存BST到文件中并从文件中恢复原来的BST。
假定我们要保存的BST如下:
_ 30_
/ \
20 40
/ / \
10 35 50
1.2.1)中序遍历
如果我们对该BST进行中序遍历,可以得到10 20 30 35 40 50,但是我们无法从中推断出原始的二叉搜索树结构。输出为10 20 30 35 40 50,一种可能的BST结构如下所示,这是一棵不平衡的BST,显然这不是原来的BST。
_50
/
40
/
35
/
30
/
20
/
10
1.2.2)后序遍历
既然中序遍历不能满足条件,那么看看后序遍历如何?后序遍历在打印出父结点之前打印叶子结点。后序遍历该BST可以得到:10 20 35 50 40 30 。读取这些结点并构造出原来的BST是个难题,因为在构造二叉树时是先构造父结点在插入孩子结点,而后序遍历序列是先读取到孩子结点然后才是父结点,所以也不符合条件。
1.2.3)先序遍历
中序遍历与后序遍历都不满足条件,只有先序遍历是可以满足条件的。BST的先序遍历结果为:30 20 10 40 35 50。我们观察到重要的一点就是:
一个结点的父亲结点总是在该结点之前输出。
有了这个观察,我们从文件中读取BST结点序列后,总是可以在构造孩子结点之前构造它们的父结点。将BST写入到文件的代码跟先序遍历一样。
(1.3) 反序列化:
1.3.1) 现在的问题是,如何从读取的结点序列中重新构造BST?
简单的办法就是对于每个结点,使用二叉搜索树insert方法执行N次插入操作,每次插入需要时间O(lgN),这样总共需要O(NlgN)的时间。这个方法不够高效。
即相当于是给定一个数组,根据该数组构造一个二叉搜索树;
解法:
回顾前面文章,判定一棵二叉树是否是二叉排序树 中的解法2采用了范围判定来判断每个节点是否符合条件。
这里采用类似的思想给出一个更为高效的解法用于从文件中重新构建原来的二叉搜索树。我们从父结点传递一个有效的范围到孩子结点。
当我们要插入结点时,判断该插入结点是否在有效范围,如果是则插入,否则寻找一个新的位置进行插入。整个时间复杂度为O(N)。
void readBSTHelper(int min, int max, int &insertVal,
struct node *&p, ifstream &fin)
{
if (insertVal > min && insertVal < max) {
int val = insertVal;
p = newNode(val);
if (fin >> insertVal) {
readBSTHelper(min, val, insertVal, p->left, fin);
readBSTHelper(val, max, insertVal, p->right, fin);
}
}
}
void readBST(struct node *&root, ifstream &fin)
{
int val;
fin >> val;
readBSTHelper(INT_MIN, INT_MAX, val, root, fin);
}