剑指Offer:36 二叉搜索树与双向链表

20.5.4

将 二叉搜索树 转换成一个 “排序的循环双向链表” ,其中包含三个要素:

排序链表: 节点应从小到大排序,因此应使用 中序遍历 “从小到大”访问树的节点;
双向链表: 在构建相邻节点(设前驱节点 pre ,当前节点 cur )关系时,不仅应 pre.right = cur,也应 cur.left = pre
循环链表: 设链表头节点 head 和尾节点 tail ,则应构建 head.left = tail 和 tail.right = head。

class Solution {
private:
    Node *head, *pre;
public:
    Node* treeToDoublyList(Node* root) {
        //中序遍历 + pre与cur互相连接 + 头结点与尾结点连接
        if(!root) return nullptr;
        head = nullptr; pre = nullptr;
        InOrder(root);
        head->left = pre;
        pre->right = head;
        return head;
    }
    void InOrder(Node* root) {
        if(!root) return;
        InOrder(root->left);
        
        if(pre) pre->right = root;
        else head = root;
        root->left = pre;
        pre = root;

        InOrder(root->right);
    }
};

 


这道题看完书上的思路之后,自己尝试写了一下,运行结果:程序使用了超过限制的内存

然后自己以一颗3层的完全二叉树为例来分析运行流程,发现果然是不对的:左子树的根节点和右子树的根节点在变换后是不会和整个树的根节点相邻的,而下面的程序中却是相邻的,明显不对;

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        TreeNode* pResult = pRootOfTree;
        ConvertEachNode(pRootOfTree);
        while(pResult!=nullptr && pResult->left!=nullptr){
            pResult = pResult->left;
        }
        
        return pResult;
    }
    
    void ConvertEachNode(TreeNode* pRootOfTree)
    {
        if(pRootOfTree!=nullptr){
            ConvertEachNode(pRootOfTree->left);
            if(pRootOfTree->left!=nullptr) (pRootOfTree->left)->right = pRootOfTree;
            if(pRootOfTree->right!=nullptr) (pRootOfTree->right)->left = pRootOfTree;
            ConvertEachNode(pRootOfTree->right);
        }
    }
};

 看了书上的代码,基本上是照着写了一遍:

对于pLastNode为什么要把引用传进去:

1、因为要改变的是pLastNode指针本身(也就是让它指向下一个节点),而不是改变pLastNode指针指向的节点。

2、因为pLastNode是在Convert里面建立的变量,要在调用函数里面使用它,就要传递引用。也可以在类中声明为成员变量,就可以不加引用了。

普通传参递归层数高的不会传给层数低,这个引用可以让递归层数高的改变的pre作用到层数低的函数中。

解释:在第一次递归到底,函数返回之前,pLastNode被更新为pCurr,也就是指向二叉树最左的叶子节点的指针,不再是null,但是函数返回到上一层函数之后,上一层函数调用时传入的参数pLastNode并没有被改变,依然是null。

因此,需要用&

另外,不用指针用引用的原因是:

  • 指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
  • 引用传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        TreeNode* pResult = pRootOfTree;
        
        TreeNode* pLastNode = nullptr;
        ConvertEachNode(pRootOfTree, &pLastNode); 
        
        while(pResult!=nullptr && pResult->left!=nullptr){
            pResult = pResult->left;
        }
        
        return pResult;
    }
    
    void ConvertEachNode(TreeNode* pRootOfTree, TreeNode** pLastNode)
    {
        if(pRootOfTree!=nullptr){
            
            TreeNode* pCurr = pRootOfTree;
            
            if(pCurr->left!=nullptr) ConvertEachNode(pCurr->left, pLastNode);
            
            //一个新的节点入链
            pCurr->left = *pLastNode;
            if(*pLastNode!=nullptr) (*pLastNode)->right = pCurr; //if(*pLastNode!=nullptr)漏写了*导致一直段错误
            *pLastNode = pCurr;
            
            if(pCurr->right!=nullptr) ConvertEachNode(pCurr->right, pLastNode);
        }
    }
};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值