二叉树转链表

二叉树转单链表

leetcode 114. 二叉树展开为链表

https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/

给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树

    1
   / \
  2   5
 / \   \
3   4   6

将其展开为:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {

    }
};

递归解法

可以发现展开的顺序其实就是二叉树的前序遍历。

  1. 所以最简单的方式是,用前序遍历的方式遍历这棵树,将遍历结果依次保存到一个容器中,最后把这个容器中的每个元素串成单链表即可。这种方式简单是简单,但空间复杂度肯定不是很好。

这种就不写了,就是基本的前序框架,把输出节点值,改为像容器尾插入当前节点指针即可。最后遍历容器,串成单链表。
时间复杂度:O(N)
空间复杂度:O(N)

  1. 不用容器保存所有遍历结果的方式,直接改造二叉树前序遍历的框架

内容来自:https://leetcode.wang/leetcode-114-Flatten-Binary-Tree-to-Linked-List.html
只是下面改为C++实现

题目其实就是将二叉树通过右指针,组成一个链表。

1 -> 2 -> 3 -> 4 -> 5 -> 6

我们知道题目给定的遍历顺序其实就是先序遍历的顺序,所以我们能不能利用先序遍历的代码,每遍历一个节点,就将上一个节点的右指针更新为当前节点。

先序遍历的顺序是1 2 3 4 5 6。

遍历到2,把1的右指针指向2。1 -> 2 3 4 5 6。

遍历到3,把2的右指针指向3。1 -> 2 -> 3 4 5 6。

… …

但是我们把1的右指针指向2,那么1的原本的右孩子就丢失了,也就是5 就找不到了。

解决方法的话,我们可以逆过来进行。

我们依次遍历6 5 4 3 2 1,然后每遍历一个节点就将当前节点的右指针更新为上一个节点。
遍历到5,把5的右指针指向6。6 <- 5 4 3 2 1。

遍历到4,把4的右指针指向5。6 <- 5 <- 4 3 2 1。

… …

    1
   / \
  2   5
 / \   \
3   4   6

这样就不会有丢失孩子的问题了,因为更新当前的右指针的时候,当前节点的右孩子已经访问过了。

而6 5 4 3 2 1的遍历顺序其实变形的后序遍历,遍历顺序是右子树->左子树->根节点
改造一下后序遍历的框架,同时利用一个全局变量pre,更新当前根节点的右指针为pre,左指针为null。

    TreeNode* pre=nullptr;
    //后序遍历的变形 “右左根”
    void flatten(TreeNode* root) {
        if(root==nullptr)   return ;

        flatten(root->right);
        flatten(root->left);
        //当前节点跟前一个结点连起来
        root->right=pre;
        //当前节点left置nullptr
        root->left=nullptr;
        //保存当前节点
        pre=root;
    }

相应的左孩子也要置为null,同样的也不用担心左孩子丢失,因为是后序遍历,左孩子已经遍历过了。

后序遍历改为迭代实现如下:

待续

前面提到如果用前序遍历的话,会丢失掉右孩子,除了用后序遍历,还有没有其他的方法避免这个问题。
我们知道利用栈,可以将递归的前序遍历改为迭代实现。
由于栈是先进后出,所以我们先将右节点入栈。提前将右孩子保存到栈中,这种遍历方式就可以防止右孩子的丢失了。

//前序遍历的迭代实现
    void flatten(TreeNode* root) {
        if(root==nullptr)   return ;
        stack<TreeNode*> s;
        s.push(root);
        TreeNode* pre=nullptr;

        while(!s.empty()){
            TreeNode* temp=s.top();
            s.pop();
            if(temp->right!=nullptr)
                s.push(temp->right);
            if(temp->left!=nullptr)
                s.push(temp->left);

            if(pre!=nullptr){
                pre->right=temp;
                pre->left=nullptr;
            }
            pre=temp;        
        }
    }

非递归解法

  1. 将左子树插入到右子树的地方
  2. 将原来的右子树接到左子树的最右边节点
  3. 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 nullptr
    1
   / \
  2   5
 / \   \
3   4   6

//将 1 的左子树插入到右子树的地方
    1
     \
      2         5
     / \         \
    3   4         6        
//将原来的右子树接到左子树的最右边节点
    1
     \
      2          
     / \          
    3   4  
         \
          5
           \
            6
            
 //将 2 的左子树插入到右子树的地方
    1
     \
      2          
       \          
        3       4  
                 \
                  5
                   \
                    6   
        
 //将原来的右子树接到左子树的最右边节点
    1
     \
      2          
       \          
        3      
         \
          4  
           \
            5
             \
              6         
  
  ......
void flatten(TreeNode* root) {
        while(root!=nullptr){
            if(root->left!=nullptr){
                //找到左子树的最右边节点
                TreeNode* temp=root->left;
                while(temp->right)
                temp=temp->right;
                //将原来的右子树接到左子树的最右边节点
                temp->right=root->right;
                // 将左子树插入到右子树的地方
                root->right=root->left;
                root->left=nullptr;
                //root移到下一个节点,等待下次循环分析
                root=root->right;
            }else{
                root=root->right;
            }
        }
    }

分治思想:

  1. 先把root的左子树转化为单向链表,表头指针left,再把右子树转化为单向链表,表头指针right
  2. 然后把left指向的单链表,right指向的单链表,root,三者连接起来。
    具体为令root->left=nullptr, root->right=left,然后找到左子树转化后的单链表的尾指针leftTail,最后令leftTail->right=right;

定义一个函数,将root指向的二叉树转化为单链表,返回转化后单链表的表头

 TreeNode* binaryTreeToList(TreeNode* root);

代码实现上看,分治思想有点后序遍历的感觉

class Solution {
public:
    TreeNode* binaryTreeToList(TreeNode* root){
        //base case
        if(root==nullptr)    return nullptr;
        TreeNode* leftTail=nullptr;
        //分
        TreeNode* left=binaryTreeToList(root->left);
        TreeNode* right=binaryTreeToList(root->right);
        //治
        //根据left是否为nullptr,
        //将left,right,root连接起来的操作不同
        if(left!=nullptr){
            leftTail=left;
            //找到left指向的单链表最后一个指针
            while(leftTail->right)
                leftTail=leftTail->right;
            root->right=left;
            root->left=nullptr;
            leftTail->right=right;
        }else{
            root->left=nullptr;
            root->right=right;
        }

        return root;        
    }
    
    void flatten(TreeNode* root) {
        binaryTreeToList(root);
    }
};

二叉树转双向链表

《剑指offer》面试题36

输入一颗二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建新的结点,只能调整树中结点指针的指向。
在这里插入图片描述

在这里插入图片描述

递归实现

  • 很明显的中序遍历框架,根据上面的类似的分析,就是把中序遍历的序列连接起来,只是节点的左右指针都得连接起来,同时,必须先找到最左节点作为表头返回。
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(pRootOfTree==nullptr)    return nullptr;
        //找到转换后的头部,即最左节点
        TreeNode* cur=pRootOfTree;
        while(cur->left)
            cur=cur->left;
        //中序遍历将二叉搜索树转双向链表
        inorderRecursion(pRootOfTree);
        //返回转换后的双向链表头
        return cur;
    }
    
    //中序遍历框架
    TreeNode* pre=nullptr;
    void inorderRecursion(TreeNode* root){
        if(root==nullptr)    return ;
        inorderRecursion(root->left);
        root->left=pre;
        //记住访问指针指向的空间中的元素时,得先判断指针非nullptr
        if(pre!=nullptr)
            pre->right=root;
        pre=root;
        inorderRecursion(root->right);
    }
};
  • 根据中序遍历,上面利用一个辅助函数void inorderRecursion(TreeNode* root);来实现,主要要用一个外部变量TreeNode* pre,来保存中序遍历序列的前一个结点。

如果直接用题目提供的接口函数TreeNode* Convert(TreeNode* pRootOfTree);,也可以利用中序遍历框架来实现,只是不像上面那样好理解。可以先看看下面的分治思想,应该有助于理解这里的代码。

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(pRootOfTree==nullptr)    return nullptr;
        TreeNode* temp=nullptr;
        //left为左子树转换成双向链表后的链表头
        TreeNode* left=Convert(pRootOfTree->left);
        //根据left是否为nullptr,将left与pRootOfTree连接起来
        if(left!=nullptr){
            temp=left;
            while(temp->right)
                temp=temp->right;
            temp->right=pRootOfTree;
            pRootOfTree->left=temp;
            
        }else{
            pRootOfTree->left=nullptr;
        }
        //right为右子树转换成双向链表后的链表头
        TreeNode* right=Convert(pRootOfTree->right);
        //根据right是否为nullpr,将pRootOfTree与right连接起来
        if(right!=nullptr)
            right->left=pRootOfTree;
        pRootOfTree->right=right;
        
        //根据left是否为nullptr,决定最终返回
        if(left!=nullptr)
            return left;
        else
            return pRootOfTree;
    }

};

分治思想

用分治思想分析这题

  1. 先把root的左子树转化为双向链表,表头指针left,再把右子树转化为双向链表,表头指针right
  2. 然后把left指向的双向链表,right指向的双向链表,root,三者连接起来。
    具体为
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(pRootOfTree==nullptr)    return nullptr;
        TreeNode* temp=nullptr;
        //分
        TreeNode* left=Convert(pRootOfTree->left);
        TreeNode* right=Convert(pRootOfTree->right);
        //治
        //根据left是否为nullptr,具体连接操作不一样
        if(left!=nullptr){
           //先找到左子树转换成双向链表后的尾节点
           temp=left;
            while(temp->right)
                temp=temp->right;
            temp->right=pRootOfTree;
            pRootOfTree->left=temp;
            pRootOfTree->right=right;
            if(right!=nullptr)
                right->left=pRootOfTree;
            
            return left;
        }else{
            pRootOfTree->left=nullptr;
            pRootOfTree->right=right;
            if(right!=nullptr)
                right->left=pRootOfTree;
       
            return pRootOfTree;
        }
    }

};
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值