2018-06-16 18:53:36
序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。反序列化顾名思义就是通过信息流对对象进行重建的过程。
一般来说序列化和反序列化有如下的作用:
1、以某种存储形式使自定义对象持久化;
2、将对象从一个地方传递到另一个地方。
3、使程序更具维护性。
本篇文章主要讨论的是二叉树的序列化和反序列化,分别讨论了普通二叉树和二叉搜索树的情况。
一、普通二叉树的序列化和反序列化
问题描述:
问题求解:
序列化和反序列化二叉树本质上其实是问如何将二叉树描述成字符串形式,如何从字符串中提取并重建二叉树。和以往建立二叉树是一样的,如果我们通过前序遍历的方式对二叉树所有节点进行表示,那么在重建二叉树的时候,就可以完全按照前序遍历的方式进行重建。问题在于,如何对数据进行表示,由于各个数的位数不同,所以一般来说会使用空格作为分隔符,同时采用"#“来表示空节点。
但是这样的序列化方式显然是比较浪费空间的,如果单纯的使用其10进制的表示,对于十几亿的数字,单纯将其转化成字符串,那么就会有11个char,占用的字节数可以会达到11个字节,这样对空间和效率都会产生不利的影响。那么如果提高空间的利用率呢?因为一个整形数本身是由4个字节来表示的,我们可以对每8位做一次转化,将之转成char,这样就可以用4个字节来表示一个整数。
事实上,下面的解法就是使用上述的思路,使用4个字节来表示一个整形数,同时可以再加上一个字节来表示是否为空节点,这样对空间的占用就会小很多,另外,由于归一化了长度,即所有数据的长度都为5个字节,那么就不需要使用空格来作为分隔符了,这样又进一步减少了空间的开销,因此,采用这种方法来序列化和反序列化二叉树是比较可取的。
public class SerializeandDeserializeTree {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuffer sb = new StringBuffer();
preOrder(root, sb);
return sb.toString();
}
private void preOrder(TreeNode root, StringBuffer stringBuffer) {
if (root == null) {
stringBuffer.append("00000");
}
else {
byte[] bytes = intToByte(root.val);
stringBuffer.append('1').append((char) bytes[0]).append((char) bytes[1]).append((char) bytes[2]).append((char) bytes[3]);
preOrder(root.left, stringBuffer);
preOrder(root.right, stringBuffer);
}
}
private byte[] intToByte(int val) {
return new byte[]{
(byte) (val >> 24),
(byte) (val >> 16),
(byte) (val >> 8),
(byte) val
};
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
return helper(data.toCharArray(), new int[]{0});
}
private TreeNode helper(char[] data, int[] pos) {
if (pos[0] >= data.length) return null;
if (data[pos[0]] == '0') {
pos[0] += 5;
return null;
}
int val =
(data[pos[0] + 1]) << 24 & 0xff000000 |
(data[pos[0] + 2]) << 16 & 0x00ff0000 |
(data[pos[0] + 3]) << 8 & 0x0000ff00 |
(data[pos[0] + 4]) << 0 & 0x000000ff;
pos[0] += 5;
TreeNode root = new TreeNode(val);
root.left = helper(data, pos);
root.right = helper(data, pos);
return root;
}
}
精简版:
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuffer sb = new StringBuffer();
preOrder(root, sb);
return sb.toString();
}
private void preOrder(TreeNode root, StringBuffer sb) {
if (root == null) sb.append("00000");
else {
int val = root.val;
sb.append('1').append((char) (val >> 24)).append((char) ((val >> 16) & (0x00ff))).append((char) ((val >> 8) & (0x0000ff))).append((char) (val & (0x000000ff)));
preOrder(root.left, sb);
preOrder(root.right, sb);
}
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
return helper(data.toCharArray(), new int[1]);
}
private TreeNode helper(char[] chs, int[] pos) {
if (pos[0] >= chs.length) return null;
if (chs[pos[0]] == '0') {
pos[0] += 5;
return null;
}
int val = chs[pos[0] + 1] << 24 | chs[pos[0] + 2] << 16 | chs[pos[0] + 3] << 8 | chs[pos[0] + 4];
TreeNode root = new TreeNode(val);
pos[0] += 5;
root.left = helper(chs, pos);
root.right = helper(chs, pos);
return root;
}
}
二、 二叉搜索树的序列化和反序列化
问题描述:
问题求解:
二叉搜索树是二叉树的特例,当然是可以使用上面的思路直接AC的,但是本题中特别要求了,序列化的字符串要尽可能的紧凑,考虑到二叉搜索树的性质,我们可以进一步的简化序列化的长度。正如上面提到的,对于整形数使用4个字节表示可以大大简化存储数量,这个是毋庸置疑的,只是在二叉搜索树中,我们不再需要额外存储空节点信息,因为对于二叉搜索树来说,其性质是左子树所有节点都要比根节点小,右子树所有节点都要比根节点大,因此我们可以利用这个性质来判断当前流数据是否为此根节点的子树。
综上所述,我们需要维护一下每个根节点左右子树的大小范围以判断当前的流数据是否应该挂在当前根节点下,如果不在范围内,直接返回空值即可。因而,对于整个前序遍历的序列化字符串来说,我们只需要4个字节来表述整形数即可。
public class SerializeandDeserializeBST {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuffer sb = new StringBuffer();
preOrder(root, sb);
return sb.toString();
}
byte[] intToByte(int val) {
return new byte[]{
(byte)(val >> 24),
(byte)(val >> 16),
(byte)(val >> 8),
(byte)val
};
}
void preOrder(TreeNode root, StringBuffer sb) {
if (root != null) {
byte[] tmp = intToByte(root.val);
sb.append((char) tmp[0]).append((char) tmp[1]).append((char) tmp[2]).append((char) tmp[3]);
preOrder(root.left, sb);
preOrder(root.right, sb);
}
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
return helper(data.toCharArray(), new int[]{0}, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
private TreeNode helper(char[] data, int[] pos, int low, int high) {
if(pos[0] >= data.length) return null;
int val =
data[pos[0] + 0] << 24 & (0xff000000) |
data[pos[0] + 1] << 16 & (0x00ff0000) |
data[pos[0] + 2] << 8 & (0x0000ff00) |
data[pos[0] + 3] << 0 & (0x000000ff);
if(val < low || val > high) return null;
TreeNode root = new TreeNode(val);
pos[0] += 4;
root.left = helper(data, pos, low, val);
root.right = helper(data, pos, val, high);
return root;
}
int byteToInt(byte[] bytes) {
return
bytes[0] & (0xff000000) |
bytes[1] & (0x00ff0000) |
bytes[2] & (0x0000ff00) |
bytes[3] & (0x000000ff);
}
}