现在的位置: 首页 > 综合 > 正文

【老鸟学算法】二元查找树转变成排序的双向链表——算法思想及java实现

2013年12月13日 ⁄ 综合 ⁄ 共 4494字 ⁄ 字号 评论关闭

把二元查找树转变成排序的双向链表(树)(来自July大神的《算法面试100题》,果断抱大腿呀)
 题目:
输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。
要求不能创建任何新的结点,只调整指针的指向。
       10
      /    |
   6     14
 /   |     /  |
4  8 12 16
 转换成双向链表
4=6=8=10=12=14=16。

二叉查找树的特点:任意节点的左子树都要小于当前节点;右子树都要大于当前节点。
特点:查询某个值,需要的时间复杂度为O(lgN)。

现在要求将其由树状结构改造成线性结构的双向链表,感觉重点在于,获得当前节点左子树范围内最右节点(也是左子树最大值节点),以及右子树范围内最左节点(也是右子树最小值节点),然后,调整这两个节点与当前节点左右顺序。以本题为例,就是要调整8、10之间的关系,和12、10之间的关系(8的右节点为10,10的左节点为8;12的左节点为10,10的右节点为12)。
当然,这种三层关系的树看起来很简单,如果层次多了,就需要分析,如何将其中的共性抽象出来。

也是最近两天才看到July大神的这个帖子,目前还没看到答案那块。不过他的提示,结构体,猜测他用的是C++实现的。C++自己也忘的差不多了,这边就用Java实现下。

数据结构,依然沿用July的value,left,right方式。

算法思路:
1. 树根节点,分左右子树。先将“当前节点左子树范围内最右节点”leftR找出来,再将“右子树范围内最左节点”rightL找出来。(这两步放在一开始,是因为此时左右子树内的关系还没改变,先取出来,时间消耗O(lgN)。只是查找到节点,空间上只用到一个索引,不会产生新的内存分配)
2. 若左子树为叶子节点,则直接设置其右向索引指向其父节点,左向递归结束;否则,将此节点作为根节点,递归调用第1步。
3. 若右子树为叶子节点,则直接设置其左向索引指向其父节点,右向递归结束;否则,将此节点作为根节点,递归调用第1步。
4. 设置根节点的左向节点为leftR,leftR的右向节点为根节点(其左向节点,在2、3两步的递归过程中,已经赋值);设置根节点的右向节点为rightL,rightL的左向节点为根节点(其右向节点,在2、3两步的递归过程中,已经赋值)。

算法思路看起来还是比较简单的(时间复杂度O(lgN)),这里要说明的是,与简单递归思路,“只递归调用本方法”不同,采用“主方法的两个字方法递归调用主方法”实现的。这样看起来,模块清晰,易于设计。

最后,附上Java版源码:

/**
 * 自定义双向链表
 * 二元查找树转变成排序的双向链表
 * @author Maxy
 */
public class MyLinkedNode
{

    private int value;//节点值

    private MyLinkedNode left;//左值节点

    private MyLinkedNode right;//右值节点

    /**
     * 节点值只能初始化时写入
     * @param value
     */
    public MyLinkedNode(int value)
    {
        this.value = value;
    }

    public MyLinkedNode getLeft()
    {
        return left;
    }

    public void setLeft(MyLinkedNode left)
    {
        this.left = left;
    }

    public MyLinkedNode getRight()
    {
        return right;
    }

    public void setRight(MyLinkedNode right)
    {
        this.right = right;
    }

    /**
     * 只提供查询节点值
     * @return
     */
    public int getValue()
    {
        return value;
    }

    @Override
    public String toString()
    {
        return "" + value;
    }

    /**
     * 右向打印
     */
    public void printToRight()
    {
        System.out.print(value + " ");
        if (null != right)
        {
            right.printToRight();
        }
    }

    /**
     * 左向打印
     */
    public void printToLeft()
    {
        System.out.print(value + " ");
        if (null != left)
        {
            left.printToLeft();
        }
    }

    //测试桩
    public static void main(String[] args)
    {
        //初始化2叉查找树:左子树节点值>本节点值>右子树节点值
        MyLinkedNode v10 = new MyLinkedNode(10);
        MyLinkedNode v6 = new MyLinkedNode(6);
        MyLinkedNode v14 = new MyLinkedNode(14);
        MyLinkedNode v4 = new MyLinkedNode(4);
        MyLinkedNode v8 = new MyLinkedNode(8);
        MyLinkedNode v12 = new MyLinkedNode(12);
        MyLinkedNode v16 = new MyLinkedNode(16);

        //原题基础上扩展一层叶子节点
        MyLinkedNode v3 = new MyLinkedNode(3);
        MyLinkedNode v5 = new MyLinkedNode(5);
        MyLinkedNode v7 = new MyLinkedNode(7);
        MyLinkedNode v9 = new MyLinkedNode(9);
        MyLinkedNode v11 = new MyLinkedNode(11);
        MyLinkedNode v13 = new MyLinkedNode(13);
        MyLinkedNode v15 = new MyLinkedNode(15);
        MyLinkedNode v17 = new MyLinkedNode(17);

        //构建二叉树关系
        v10.setLeft(v6);
        v10.setRight(v14);

        v6.setLeft(v4);
        v6.setRight(v8);

        v14.setLeft(v12);
        v14.setRight(v16);

        //扩展层
        v4.setLeft(v3);
        v4.setRight(v5);
        v8.setLeft(v7);
        v8.setRight(v9);
        v12.setLeft(v11);
        v12.setRight(v13);
        v16.setLeft(v15);
        v16.setRight(v17);

        //达到题目要求,因为已经有序,只需要对叶子节点以及其直接父节点的调整
        processTreeToLinked(v10);

        //最后,如果处理成功,则右向打印v3时,会升序打印出来
        System.out.print("升序打印:");
        v3.printToRight();

        System.out.println();

        //左向打印v17,会降序打印
        System.out.print("降序打印:");
        v17.printToLeft();

    }

    /**
     * 递归主方法
     * 不借助新的节点,对叶子节点以及其直接父节点的调整
     * @param node
     */
    private static void processTreeToLinked(MyLinkedNode node)
    {
        //为空不操作
        if (null == node)
        {
            return;
        }

        //获得左子树的最右节点(最大值)
        MyLinkedNode leftR = getMostRightNode(node.getLeft());
        //获得右子树的最左节点(最小值)
        MyLinkedNode rightL = getMostLeftNode(node.getRight());

        //左子树非空,递归处理左子树
        if (null != node.getLeft())
        {
            processLeftNode(node.getLeft(), node);
        }

        //右子树非空,递归处理右子树
        if (null != node.getRight())
        {
            processRightNode(node, node.getRight());
        }

        //若左子树最右节点非空,调整与根节点相邻
        if (null != leftR)
        {
            leftR.setRight(node);
            node.setLeft(leftR);
        }

        //若右子树的最左节点非空,调整与根节点相邻
        if (null != rightL)
        {
            rightL.setLeft(node);
            node.setRight(rightL);
        }
    }

    /**
     * 递归左子方法
     * 处理左子树
     * @param left
     * @param node
     */
    private static void processLeftNode(MyLinkedNode left, MyLinkedNode node)
    {
        //若左子树为叶子节点,直接将其右向索引指向父节点,并返回
        if (isLeafNode(left))
        {
            left.setRight(node);
            return;
        }

        //本节点当做根节点,递归调用
        processTreeToLinked(left);
    }

    /**
     * 递归右子方法
     * 处理右子树
     * @param node
     * @param right
     */
    private static void processRightNode(MyLinkedNode node, MyLinkedNode right)
    {
        //若右子树为叶子节点,直接将其左向索引指向父节点,并返回
        if (isLeafNode(right))
        {
            right.setLeft(node);
            return;
        }

        //本节点当做根节点,递归调用
        processTreeToLinked(right);
    }

    /**
     * 判断节点是否为叶子节点
     * @param node
     * @return
     */
    private static boolean isLeafNode(MyLinkedNode node)
    {
        return (null == node.getLeft()) && (null == node.getRight());
    }

    /**
     * 获取最右节点
     * @param left
     * @return
     */
    private static MyLinkedNode getMostRightNode(MyLinkedNode left)
    {
        //若节点为空,直接返回空
        if (null == left)
        {
            return null;
        }

        //若此节点右节点为空,直接返回此节点
        if (null == left.getRight())
        {
            return left;
        }

        return getMostRightNode(left.getRight());
    }

    /**
     * 获取最左节点
     * @param left
     * @return
     */
    private static MyLinkedNode getMostLeftNode(MyLinkedNode right)
    {
        //若节点为空,直接返回空
        if (null == right)
        {
            return null;
        }

        //若此节点的左节点为空,直接返回此节点
        if (null == right.getLeft())
        {
            return right;
        }

        return getMostLeftNode(right.getLeft());
    }
}

欢迎拍砖。

抱歉!评论已关闭.