二叉搜索树转换为双向链表的Java实现 出现的一些问题及解决

题目

二叉搜索树转换为双向排序链表,要求不能新增任何新的结点,只能调整树中节点的指向。

思路

二叉搜索树的左子树中结点的值总是小于根结点,右子树中结点的值总是大于根结点,所以二叉搜索树的中序遍历结果就是我们最终想要得到的链表顺序,如图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;
    }

运行测试方法,获得了正确的双向排序链表。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值