题目:
已知一个搜索二叉树后序遍历的数组posArr,请根据posArr,重建出整棵树,并返回头结点
先来了解后序遍历是个什么玩意
二叉树的前序、中序、后序三种遍历方式(Java实现)
根据后序遍历的性质,我们可以知道,一个搜索二叉树如果长成下面这个鸟样
那么他后序遍历的结果就是下面这个数组
3,5,4,7,6,9,11,10,8
分析:
- 后序遍历一定是最后遍历根节点,那么可以推出根节点的位置一定是数组的最后一位,上面的数组我们可以确定根节点为8
- 同时根据后序遍历的性质,我们可以知道,一定是先遍历完左子树才会去遍历右子树,那么结果中的前半部分肯定是左子树了,后半部分是右子树(不包含最后一位,最后一位是根)
- 如何确定哪部分是左子树哪部分是右子树呢?题目是二叉搜索树,根据二叉搜索树的性质,根节点左边的所有元素必定小于根节点,根节点右边的所有元素必定大于根节点,那从数组起始位置找到最后一个小于根节点的元素的位置,假设位置为n,0···n之间的元素就是根节点左子树的所有元素,n到根节点前面一位的元素就是右子树的所有元素
- 并且二叉树的左右子树也都满足后序遍历的性质,那么我们将数组分为左右两部分之后,可以将切分后的两个子数组分别视为两个完整的后序遍历的结果
那么经过上面的推理,我们大概有了下面的思路
1、先取出根节点
2、将剩余数组切分为左右两个子树的后序遍历的结果
3、再分别对切分的数组进行建树的操作,直到数组切分的只有一个元素了,直接返回
4、将根节点的左右子节点指针指向上一步建好的树的根节点
那么根据题意,我们可以想到
先定义节点,二叉树的节点不必多说了
static class Node<T>{
private Node left;
private Node right;
private T data;
public Node(T t){
this.data = t;
}
}
根据上面的思路写出如下方法
代码:
public class PosArr {
static class Node<T>{
private Node left;
private Node right;
private T data;
public Node(T t){
this.data = t;
}
}
private static Node head;
/**
* 最基本的重建树的方法
* @param arr
* @param left
* @param right
* @return
*/
private static Node process(int[] arr, int left, int right){
if(left > right){
return null;
}
Node head = new Node(arr[right]);
if(left == right){
return head;
}
int mid = left - 1;
for (int i = left; i < right; i++){
if(arr[i] < arr[right]){
mid = i;
}
}
//递归建立左子树和右子树
head.left = process(arr, left, mid);
head.right = process(arr, mid + 1, right - 1);
return head;
}
public static Node posArray2BST(int arr[]){
head = process(arr, 0, arr.length - 1);
return head;
}
}
上面的查找最后一个小于根节点的值使用的遍历的方式,我们还可以将其优化成二分查找的方式,这个二分查找和有序的二分查找不一样,我们需要有一个标记位来记录,当查找到的数值小于根节点的时候,记录一次,直到二分到不能再分的时候,标记位记录的就是左子树的最后一个节点,如果二叉树记录不平衡,比如只有右子树,那么标记位一次都不会记录,为根节点前面数组的起始位置,有了这个思路就可以开始写代码了
/**
* 优化后的重建树的方法(使用二分查找)
* @param arr 要建树的数组
* @param left 数组的最左端
* @param right 数组的最右端
* @return
*/
private static Node process2(int[] arr, int left, int right){
if(left > right){
return null;
}
Node head = new Node(arr[right]);
if(left == right){
return head;
}
int mid = left - 1;//mid为标记位,如果没有左子树,那么建立左树的时候就会返回null
//二分查找
int l = left,r = right - 1;
while (l <= r){
int m = l + ((r - l) >> 1);
if(arr[m] < arr[right]){
mid = m;
left = m + 1;
}else {
right = m - 1;
}
}
//递归建立左子树和右子树
head.left = process2(arr, left, mid);
head.right = process2(arr, mid + 1, right - 1);
return head;
}
二分真是一个好东西,好多东西我们都可以使用二分来优化
至此