二叉查找树转双向链表JAVA实现

1、问题描述java

输入一棵二叉搜索树,如今要将该二叉搜索树转换成一个排序的双向链表。并且在转换的过程当中,不能建立任何新的结点,只能调整树中的结点指针的指向来实现。node

 

2、实现思路算法

在二叉搜索树中,每一个结点都有两个分别指向其左、右子树的指针,左子树结点的值老是小于父结点的值,右子树结点的值老是大于父结点的值。而在双向链表中,每一个结点也有两个指针,它们分别指向前一个结点和后一个结点。因此这两种数据结构的结点是一致,二叉搜索树之因此为二叉搜索树,双向链表之因此为双向链表,只是由于两个指针的指向不一样而已,经过改变其指针的指向来实现是彻底可能的。数据结构

 

例如以下的二叉搜索树,app

 

若采用中序遍历,其遍历顺序为1-2-3-4-5-6-7,经过适当的指针变换操做,可变成的双向有序链表以下:函数

 

 

从上图,咱们能够看出,为了减小指针的变换次数,并让操做更加简单,在转换成排序双向链表时,原先指向左子结点的指针调整为链表中指向前一个结点的指针,原先指向右子结点的指针调整为链表中指向下一个结点的指针。例如对于上面的值为2的指点,调整后,它的前一个结点为1,后一个结点为3,而结点2的左子结点原本就为1,右子结点原本就为3.测试

 

对于树的操做,一般是在遍历树的各个结点的过程当中,经过对结点实施某些操做来完成的,这个算法也不例外。因为要求转换后的双向链表也是有序的,而咱们从上面也能够看到,当咱们以中序遍历二叉搜索树时,其遍历的结点就是有序的,因此在这里我位采用的遍历顺序应该是中序。this

 

那么咱们应该如何调整指针,让二叉搜索树变成一个双向有序链表呢?当遍历到根结点时,咱们能够把树当作三个部分:根结点,根的左子树和根的右子树。如上图的二叉排序树,就分红了根结点四、以结点2为根的左子对和以结点6为根的右子树。从变换的链表中咱们能够看到,应当把结点4的left指针指向结点3,把结点3的right指针指向结点4,而因为咱们采用的是中序遍历,因此当咱们遍历到结点4时,结点4的左子树已经转化为一个有序的双向链表,而结点3是这个已经转化的双向链表的尾结点,因此咱们应该用一个变量last_node来保存最后一个结点的指针,以便在与根结点连续时使用。而后把这个变量last_node的值更新为指向根结点4。对于结点4的右子树,采起类似的操做。至于具体的实现,咱们只须要对全部的子树递归地执行上述操做便可。其操做过程以下:spa

 

a] view plain copy.net

在CODE上查看代码片派生到个人代码片

  1. /** 
  2. public class TreeNode { 
  3.     int val = 0; 
  4.     TreeNode left = null; 
  5.     TreeNode right = null; 
  6.  
  7.     public TreeNode(int val) { 
  8.         this.val = val; 
  9.  
  10.     } 
  11.  
  12. */  
  13. public class Solution {  
  14.     private TreeNode head=null;  
  15.     private TreeNode tail=null;  
  16.     public TreeNode Convert(TreeNode pRootOfTree) {  
  17.         visit(pRootOfTree);  
  18.         return head;  
  19.     }  
  20.     public void visit(TreeNode root) {    
  21.         if (root == null) {    
  22.             return;    
  23.         }    
  24.         visit(root.left);    
  25.         createList(root);    
  26.         visit(root.right);    
  27.     }    
  28.     public void createList(TreeNode cur){    
  29.         cur.left=tail;//把当前的节点接到链表的尾部     
  30.         if(tail!=null){//双向链接     
  31.             tail.right=cur;     
  32.         }else{     
  33.             head=cur;     
  34.         }     
  35.         tail=cur;//更新尾结点为当前结点,或者说:尾结点后移     
  36.     }    

 

 

 

 

时间复杂度与空间复杂度

该算法首先从根要点一直向左走,找到最左边的结点,其时间复杂度为O(logN),而后对二叉排序树中的每一个结点遍历一次,进行指针变换,其时间复杂度为O(N),因此总的时间复杂度为O(N)。

 

至于空间复杂度,因为ConvertNode函数进行递归调用,其函数有两个开参,而函数栈中的函数调用层数不会超过树高,因此其空间复杂度为O(logN)。

 

 

分析问题

   

首先须要明白二叉搜索树也是一种排序的数据结构,它的中序遍历就是一个不递减的顺序排列

因此若是要转换成一个排序好的双向链表,那么仅须要改变原来指向左子节点和右子节点的指针,让他们分别指向前节点和后节点便可,如图所示

   

   

调整指针

   

原先指向左子节点的指针调整为链表中指向前一个节点的指针

   

原先指向右子节点的指针调整为链表中指向后一个节点的指针

   

如何调整

   

考虑根节点和左右子树的根本状况,由于若是用递归,这种根本状况考虑就能够去将一样的方法用到左右子树上

   

   

对于这种基本状况,能够分红三个部分来看,根节点10,左子树,右子树,须要作的就是将10与左子树中的最大值连起来,而后把10与右子树中的最小值连起来

   

如今有个问题就是咱们并不知道左子树中的最大值和右子树中的最小值,若是咱们知道就行了。可是想到递归,递归到左子树中,若是左子树已转换为双向链表,那么双向链表的最后一个节点就是咱们想要的,而右子树中的第一个节点也是咱们想要的

   

转换代码

   

public static BinaryTreeNode baseconvert(BinaryTreeNode root, BinaryTreeNode lastNode) {

if (root == null)

return lastNode;

BinaryTreeNode current = root;

if (current.leftNode != null)

lastNode=baseconvert(current.leftNode, lastNode);

current.leftNode = lastNode;

if (lastNode != null)

lastNode.rightNode = current;

lastNode = current;

if (current.rightNode != null)

lastNode=baseconvert(current.rightNode, lastNode);

return lastNode;

}

   

要说明的一点是,剑指Offer书上是用C++写的,Java写的时候有点不一样,就是baseconvert也要指定返回值,否则会报nullpointer的错误,估计是由于java中只是对象的引用,否则每次返回的lastNode都是空

   

上面的代码中有两个参数,一个是根节点,一个是已经转换好的链表的最后一个节点,由于二叉搜索树中序遍历的特性,当遍历到根节点的时候,左子树已经排好序了,因此会有一个左子树已经转换好的链表,而这个链表的最后一个节点便是咱们须要和根节点左连的节点

   

最开始的时候lastNode为null

   

current为当前子树的根节点

   

若是左子树存在,那么转换左子树,递归下去,递归返回以后,说明找到了链表的第一个节点,也就是4那个节点,将4的前面节点置为null,此时current为4那个节点,这个时候因为6的4这个左子树已遍历完了,因此须要往上层返回,返回以前须要将current赋值给lastNode,说明4这个左子树的最后一个节点就是4

   

因为往上返回了一层,此时的current已是6了,将6的左节点赋值为以前返回的4,判断以前返回的lastNode是否为null,不为空说明须要把根节点和lastNode连起来,其实lastNode为null的状况就只有第一个节点会出现,其余的时候都不会出现。如今已排好序的包括6的左子树以及6自己了,因此此时的lastNode为6

   

6和4的双向链接就完成了,因为6的右子树存在,又会递归到右边子树去,因为8不存在左右子树,递归下去一层以后current就是8这个节点,但它的左孩子为空,因此不会左边递归下去,将8的左链接与以前的lastNode链接起来,创建双向链接的一条链接,而后因为lastNode不为空,因此又把lastNode的右链接与8链接起来,至此双向链接创建,此时lastNode为8

   

因此468都已排好序,此时lastNode为8,返回到上一层,也就是根节点10了,在这一层current为10,将current的左链接与lastNode链接起来,若是lastNode存在,将lastNode的右链接与10链接一块儿,以此创建双向链接。至此就将根节点和左子树都链接起来了,而后就是去转换右子树,如今的lastNode为10,current为14,14有左孩子,因此须要递归到下一层,下一层的current为12,12没有左孩子,因此不用在坐递归,因此12是12这棵子树转换成双向链表的最左边的节点,将lastNode与12链接,也就是10与12链接,此时的lastNode就变成了12,再将12的右子树递归,因为没有右子树,因此直接返回到上一层,上一层的current是14,14与已排好序的lastNode链接,也就是12与14链接,而后lastNode变为14,递归到14的右子树,也就current变为16,16再递归左子树,无左子树,将16与14链接,此时的lastNode变为16,递归右子树,无右子树,因此整个递归工做完成

   

找到头节点

   

以前的转换只是把引用乾坤大挪移了一下,其实能够发现引用的个数并无变化,并且头尾节点只有一个出度的引用,而如今咱们若是要使用这个双向链表,须要找到头节点

   

public static BinaryTreeNode convert(BinaryTreeNode root) {

BinaryTreeNode lastNode = null;

lastNode=baseconvert(root, lastNode);

BinaryTreeNode headNode = lastNode;

while (headNode.leftNode != null)

headNode = headNode.leftNode;

return headNode;

}

   

测试代码

   

public static void main(String[] args) {

// TODO Auto-generated method stub

BinaryTreeNode root = new BinaryTreeNode(10);

BinaryTreeNode six=new BinaryTreeNode(6);

BinaryTreeNode four=new BinaryTreeNode(4);

BinaryTreeNode eight=new BinaryTreeNode(8);

BinaryTreeNode fourteen=new BinaryTreeNode(14);

BinaryTreeNode twelve=new BinaryTreeNode(12);

BinaryTreeNode sixteen=new BinaryTreeNode(16);

root.leftNode=six;

root.rightNode=fourteen;

six.leftNode=four;

six.rightNode=eight;

four.leftNode=null;

four.rightNode=null;

eight.leftNode=null;

eight.rightNode=null;

fourteen.leftNode=twelve;

fourteen.rightNode=sixteen;

twelve.leftNode=null;

twelve.rightNode=null;

sixteen.rightNode=null;

sixteen.leftNode=null;

BinaryTreeNode result=convert(root);

//                BinaryTreeNode result=baseconvert(root, null);

while (result!=null) {

System.out.println(result.data);

result=result.rightNode;

}

相关文章
相关标签/搜索