题目
二叉搜索树转换为双向排序链表,要求不能新增任何新的结点,只能调整树中节点的指向。
思路
二叉搜索树的左子树中结点的值总是小于根结点,右子树中结点的值总是大于根结点,所以二叉搜索树的中序遍历结果就是我们最终想要得到的链表顺序,如图1:
所以我们在转换成双向排序链表时,原先指向左子结点的指针调整为指向链表前一个结点的指针,原先指向右子结点的指针调整为指向链表后一个结点的指针。
由于需要转换之后需要是排好序的,我们可以吧二叉搜索树看成三部分,左子树,根结点右子树。我们需要先把左子树转换成排序二叉树,再在左子树的最后一个结点接上根结点,然后将右子树转换成链表,将右子树转换成的链表接在根结点之后,至于左右子树转换成链表,我们就可以采用递归来实现。解决这个问题的关键就是如何找到转换后链表的最后一个结点。如图2:
实现
构造二叉树的Java数据结构:
public class BinaryTreeNode {
int val; //节点值
BinaryTreeNode left; //左子节点指针
BinaryTreeNode right; //右子结点指针
//构造方法
public BinaryTreeNode(){}
public BinaryTreeNode(int val){
this.val=val;
}
}
测试主方法:
public static void main(String[] args) {
//构造二叉排序树
BinaryTreeNode root=new BinaryTreeNode(10);
root.left=new BinaryTreeNode(6);
root.right=new BinaryTreeNode(14);
root.left.left=new BinaryTreeNode(4);
root.left.right=new BinaryTreeNode(8);
root.right.left=new BinaryTreeNode(12);
root.right.right=new BinaryTreeNode(16);
//调用转换方法
BinaryTreeNode linkedList = treeToLinkedList(root);
}
实现一
我们传递一个lastNode指针,这个指针始终指向已构造好的链表的最后一个结点,左子树构造好时,指向左子树最后一个结点,然后指向根结点,最后指向右子树的最后一个结点,仿造《剑指offer》上的C++版实现,如下:
/**
* 定义:转换后left表示pre,right表示next
* @param root 根结点
* @return
*/
public static BinaryTreeNode treeToLinkedList(BinaryTreeNode root) {
if(root==null) return root;
//链表尾节点
BinaryTreeNode lastNode=null;
//转换
convert(root,lastNode);
//寻找返回的头结点
BinaryTreeNode head=lastNode;
while(head.left!=null) {
head = head.left;
}
return head;
}
/**
* @param root 当前子树的根结点
* @param last 保存当前链表的最后一个指针
* @return
*/
public static void convert(BinaryTreeNode root,BinaryTreeNode last) {
if(root==null) return;
BinaryTreeNode p=root;
//转换左子树
if(root.left!=null) convert(root.left,last);
//根结点的pre指向左子树的last,左子树的last的next指向根结点
p.left=last;
if(last!=null) last.right=p;
//last指向根结点
last=p;
//转换右子树
if(root.right!=null) convert(root.right,last);
}
出现的问题
运行上面的代码,发现出现空指针异常,原因是因为程序运行结束的时候lastNode为null,一步步跟进去调试发现,当子结点更新了last 的值之后,返回上层结点的时候,并不能将上层结点的变量last进行更新。这是因为当调用方法的时候,传递进去的last参数,只是传递的副本,当子方法中参数副本指向新的结点时,原来方法中的last并不会更新。
而《剑指offer》中能够实现的原因正是体现了C++与Java的区别,《剑指offer》中该方法为:
void ConverNode(BinaryTreeNode* pNode, BinaryTreeNode** pLastNodeInList)
{
... ...
}
这里的参数列表中 pLastNodeList变量使用的是“指向指针的指针”,也就是说这个变量保存的是last结点所在地址的指针,当这个变量在子结点方法中被修改时,自然也会引起上层方法的改变。因为Java中没有 指向指针的指针,所以就不能这样来实现。
实现二
因为下层修改了last变量不能传递到上层方法中,所以我们可以通过返回值可以传递last变量的值,我们对上面的代码进行如下修改,实现如下:
/**
* 定义:转换后left表示pre,right表示next
* @param root 根结点
* @return
*/
public static BinaryTreeNode treeToLinkedList(BinaryTreeNode root) {
if(root==null) return root;
//链表尾节点
BinaryTreeNode lastNode=null;
//转换
lastNode=convert(root);
//寻找返回的头结点
BinaryTreeNode head=lastNode;
while(head.left!=null) {
head = head.left;
}
return head;
}
/**
* @param root 当前子树的根结点
* @return 返回当前链表的最后一个结点
*/
public static BinaryTreeNode convert(BinaryTreeNode root) {
if(root==null) return null;
BinaryTreeNode p=root;
BinaryTreeNode last=null;
//转换左子树
if(root.left!=null) last=convert(root.left);
//根结点的pre指向左子树的last,左子树的last的next指向根结点
p.left=last;
if(last!=null) last.right=p;
//last指向根结点
last=p;
//转换右子树
if(root.right!=null) last=convert(root.right);
return last;
}
出现的问题
按照我们的想法,通过返回值就可以将下层的变量返回给上层方法了,但是运行测试方法,结果依然不对,不是我们想要的链表。经过对代码的一步步调试发现了另一个问题,当左子树与根结点跟新了last变量之后,访问右子树的时候,右子树却得不到之前的last变量指向的结点。这是因为下层方法无法得到上层结果last变量,和之前出现的问题是相反的,所以我们又对代码进行了第二次修改。
最终正确的实现
因为上层得不到下层的更新结果,我们采用了返回值的方式进行解决,但是下层又得不到上层的更新结果,我们采用参数列表来解决这个问题,通过参数将上层更新结果传递到下层中去,就能完成这个问题。
/**
* 定义:转换后left表示pre,right表示next
* @param root 根结点
* @return
*/
public static BinaryTreeNode treeToLinkedList(BinaryTreeNode root) {
if(root==null) return root;
//链表尾节点
BinaryTreeNode lastNode=null;
//转换
lastNode=convert(root,lastNode);
//寻找返回的头结点
BinaryTreeNode head=lastNode;
while(head.left!=null) {
head = head.left;
}
return head;
}
/**
* 返回当前已转换好的链表的尾结点
* @param root 当前子树的根结点
* @param last 保存当前链表的最后一个指针
* @return 返回当前链表的最后一个结点
*/
public static BinaryTreeNode convert(BinaryTreeNode root,BinaryTreeNode last) {
if(root==null) return null;
BinaryTreeNode p=root;
//转换左子树
if(root.left!=null) last=convert(root.left,last);
//根结点的pre指向左子树的last,左子树的last的next指向根结点
p.left=last;
if(last!=null) last.right=p;
//last指向根结点
last=p;
//转换右子树
if(root.right!=null) last=convert(root.right,last);
return last;
}
运行测试方法,获得了正确的双向排序链表。