二叉搜索树的序列化与反序列化

注:转自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);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是用 C++ 实现最优二叉搜索树先根序列打印的示例代码: ```cpp #include <iostream> #include <vector> using namespace std; const int MAXN = 1005; const int INF = 0x3f3f3f3f; int n; int keys[MAXN]; int freqs[MAXN]; int dp[MAXN][MAXN]; int root[MAXN][MAXN]; void print_preorder(int l, int r) { if (l > r) { return; } int k = root[l][r]; cout << keys[k] << " "; print_preorder(l, k - 1); print_preorder(k + 1, r); } int main() { cin >> n; for (int i = 1; i <= n; i++) { cin >> keys[i] >> freqs[i]; } for (int i = 1; i <= n; i++) { dp[i][i] = freqs[i]; root[i][i] = i; } for (int len = 2; len <= n; len++) { for (int i = 1; i <= n - len + 1; i++) { int j = i + len - 1; dp[i][j] = INF; for (int k = i; k <= j; k++) { int cost = dp[i][k - 1] + dp[k + 1][j] + freqs[k]; if (cost < dp[i][j]) { dp[i][j] = cost; root[i][j] = k; } } } } cout << "Optimal BST cost: " << dp[1][n] << endl; cout << "Optimal BST preorder: "; print_preorder(1, n); cout << endl; return 0; } ``` 其中,`keys[i]` 表示第 i 个关键字,`freqs[i]` 表示第 i 个关键字的频率,`dp[i][j]` 表示第 i 个关键字到第 j 个关键字构成的子树的最小代价,`root[i][j]` 表示第 i 个关键字到第 j 个关键字构成的子树的根节点。 首先,我们初始化每个单独的节点的代价和根节点。然后,我们从小到大枚举子树的长度,从左到右枚举子树的起点和终点,计算每个可能的根节点的代价,选取代价最小的作为该子树的根节点。最后,我们输出最优二叉搜索树的代价和先根序列。 注意,这里的先根序列是指先输出根节点,然后递归输出左子树和右子树。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值