题目:现有一个数组,已知该数组是搜索二叉树的后续遍历结果,请还原出原二叉树的结构。
针对这道题,最简单的算法:假设数组长度为n,数组的最后一位必然是根节点,之后,遍历数组,找到第一个大于根节点的位置m,数组下标为0~m-1的递归建立左树,m~n-2的递归建立右树。这种算法的时间复杂度取决于遍历数组找m的算法,比如折半查找法,总体时间复杂度为O(nlogn),这种算法就不贴代码了,比较简单,读者可以自行实现。
但是这种算法并不是最优算法,让我们来想想:
我们能不能读取一个值就把这个值给放到正确的位置呢?
由于是后续遍历的,数组的整体情况是小的在前,大的在后,根节点在最后。
假设有如下树:
后续遍历结果为 [17,19,31,63, 50, 88, 69, 64, 37, 36]
你会怎么还原呢?
我会这么做,先拿到最后一个数字36,这个是根节点没跑了
然后拿出倒数第二个数字37,大于36那么是36的右节点没跑了
同理拿出64,大于37(只需要和上一个节点做比较),那么是37的有几点没跑了
同理69和88也都能找到确定合理的位置
当我们拿到50时,还是和前一个比,小于88,但是他不能作为88的左节点,需要往上看一层,小于69,同样再往上看一层,小于64,在网上看一层,大于37,那么可以确定50是64的左节点了
拿出63,大于50,作为50的右节点
拿出31,小于63、50、64、37、36,那么作为36的左节点
到这里似乎一切都很顺利,也很合理,但是当我们拿出19的时候,小于31,那么需要往上回溯吗?显然不需要,所以当我们拿到的节点小于上一个节点时,直接往上回溯的逻辑是不对的,我们需要知道当前节点是不是小于一路下来的最小节点,如果小于,回溯,没有,不回溯。
于是19作为31的左节点,17作为19的左节点。
至此,我们总结出了规律:
1.从最后一个数指针逐渐前移
2.如果当前节点大于上一个节点,作为上一个节点的右节点
3.如果当前节点小于上一个节点,查看上一个节点是其父节点的左节点还是右节点
3.1如果是左节点,当前节点作为上一节点的左节点
3.2如果是右节点,当前节点和上一节点的父节点比较
3.2.1如果大于,作为上一节点的左节点
3.2.2如果小于,往上回溯一层
易于理解的方式实现可以使用List保存节点路径
List<Integer> nodes = new ArrayList();
nodes的具体内容如下,读者可根据内容自行实现,本文最后给我个人的实现,没有使用List保存节点,而是使用了递归
1.获取36,nodes=[36],根节点
2.获取37,nodes=[36,37],36的右节点
3.获取64,nodes=[36,37,64],37的右节点
4.获取69,nodes=[36,37,64,69],64的右节点
5.获取88,nodes=[36,37,64,69,88],69的右节点
6.获取50,小于88,也小于69,88大于69,nodes=[36,37,64,69]
7.50,小于69,也小于64,69大于64,nodes=[36,37,64]
8.50,小于64,大于67,nodes=[36,37,64,50],64的左节点
9.获取63,大于50,nodes=[36,37,64,50,63],50的右节点
10.获取31,小于63,也小于50,nodes=[36,37,64,50]
中间省略3步
14.31,小于36,36是根节点,nodes=[36,31],36的左节点
15.获取19,小于31,也小于36,但是31小于36,nodes=[36,31,19],31的左节点
16.获取17,小于19,也小于31,但是19小于31,nodes=[36,31,19,17],17的左节点
代码实现如下:
//数据结构
public class SearchTree { //左节点 private SearchTree left; //右节点 private SearchTree right; //内容 private int num; public SearchTree(){} public SearchTree(int num){ this.num = num; } //添加节点 public void add(int num){ if(this.num > num){ addLeft(num); }else if(this.num < num){ addRight(num); } } //添加右节点 private void addRight(int num) { if(this.right == null){ this.right = new SearchTree(); this.right.setNum(num); }else{ this.right.add(num); } } //添加左节点 private void addLeft(int num) { if(this.left == null){ this.left = new SearchTree(); this.left.setNum(num); }else{ this.left.add(num); } } public int getNum() { return num; } public void setNum(int num) { this.num = num; } //后续遍历 public List<Integer> houxu(){ List<Integer> list = new ArrayList<>(); if(this.left != null){ list.addAll(this.left.houxu()); } if(this.right != null){ list.addAll(this.right.houxu()); } list.add(num); // System.out.println(num); return list; } public List<Integer> zhongxu(){ List<Integer> list = new ArrayList<>(); if(this.left != null){ list.addAll(this.left.zhongxu()); } list.add(num); if(this.right != null){ list.addAll(this.right.zhongxu()); } return list; } @Override public boolean equals(Object obj) { if(obj == this){ return true; } if(obj == null){ return false; } if(!(obj instanceof SearchTree)){ return false; } SearchTree tree = (SearchTree) obj; return compare(tree, this); } //比较两棵树,要当前节点num一样,并且左右树递归num一样 private boolean compare(SearchTree tree, SearchTree tree1){ if(tree == tree1){ return true; } if(tree == null){ return false; } if(tree.num != tree1.num){ return false; } return compare(tree.left, tree1.left) && compare(tree.right, tree1.right); } public SearchTree getLeft() { return left; } public void setLeft(SearchTree left) { this.left = left; } public SearchTree getRight() { return right; } public void setRight(SearchTree right) { this.right = right; } }
//测试类及还原算法
public class ReverseTest { public static void main(String[] args) { //创建树 SearchTree tree = createTree(new int[]{17, 19, 64, 37, 36, 31, 63, 50, 88, 69}); //后序遍历 List<Integer> houxu = tree.houxu(); Integer[] is = houxu.toArray(new Integer[houxu.size()]); //还原树 SearchTree searchTree = reverseTree(is); //比较 System.out.println(tree.equals(searchTree)); } private static SearchTree reverseTree(Integer[] arr){ SearchTree tree = new SearchTree(); //设置根节点 tree.setNum(arr[arr.length - 1]); if(arr.length >= 2){ add(null, tree,arr, new AtomicInteger(arr.length - 2)); } return tree; } /** * * @param pOfLast 上一节点的父节点 * @param last 上一节点 * @param arr 数组 * @param index 当前节点的位置 */ private static void add(SearchTree pOfLast, SearchTree last,Integer[] arr, AtomicInteger index) { if(index.get() == -1){ return; } SearchTree now = new SearchTree(arr[index.get()]); if(now.getNum() > last.getNum()){ //大于当前,走右节点逻辑 last.setRight(now); index.decrementAndGet(); add(last, now, arr, index); } //走完右节点逻辑可能所有数据都走完了,不需要再走了 if(index.get() == -1){ return; } now = new SearchTree(arr[index.get()]); //如果上一节点为根节点,或者当前节点大于上一节点的父节点,或者上一节点小于其父节点,设置为左节点 if(pOfLast == null || now.getNum() > pOfLast.getNum() || last.getNum() < pOfLast.getNum()){ last.setLeft(now); index.decrementAndGet(); add(last, now, arr, index); } } private static SearchTree createTree(int[] arr){ SearchTree tree = new SearchTree(arr[arr.length - 1]); for (int i = arr.length - 2; i >= 0; i--) { tree.add(arr[i]); } System.out.println(tree.houxu()); return tree; } }
17, 31, 19, 63, 50, 88, 69, 64, 37, 36