以迭代方式复制二叉树

Copy a binary tree in iterative manner

本文关键字:二叉树 复制 方式 迭代      更新时间:2023-10-16

我在一次面试中被问到这个问题,这真的让我失去了一份工作:p面试官问,你将得到一棵树的根,你必须将根返回到复制的树,但复制应该以迭代的方式进行。我把我的代码粘贴在这里,我在那里写了同样的代码,它工作得很好。我最初是用两个叠来做的,面试官说他不喜欢,然后我用下面的方式做了。面试官对我使用另一种结构感到有点不满,这种结构可以指向原始树和最终树(参考代码)。

我想知道是否还有其他更好的方法可以做到这一点??

struct node
{
   int data;
   struct node * left;
   struct node * right;
};
struct copynode
{
   node * original;
   node * final;
};
node * copy(node *root)
{
    stack <copynode*> s;
    copynode * temp=(copynode*)malloc(sizeof(copynode));
    temp->original=root;
    temp->final=(node *)malloc(sizeof(node));
    s.push(temp);
    while(s.empty()==false)
    {
       copynode * i;
       i=s.top();
       s.pop();
       i->final=i->original;
       if(i->original->left)
       {
          copynode *left=(copynode*)malloc(sizeof(copynode));
          left->original=i->original->left;
          left->final=(node *)malloc(sizeof(node));
          s.push(left);
       }
       if(i->original->right)
       {
          copynode *right=(copynode*)malloc(sizeof(copynode));
          right->original=i->original->right;
          right->final=(node *)malloc(sizeof(node));
          s.push(right);
       }
   }  
   return temp->final;
}

如果允许在每个节点中都有父指针,那么您甚至不需要堆栈:

平行行走原始树和正在创建的树。如果原始树中的当前节点有一个左子节点,但您正在创建的树中的节点没有,请创建它并向左下降。正确的孩子也是如此。如果两个条件都不适用,请向上。

在代码(C#)中:

public static Node Clone(Node original)
{
    if (original == null)
        return null;
    var root = new Node(original.Data, null);
    var clone = root;
    while (original != null)
    {
        if (original.Left != null && clone.Left == null)
        {
            clone.Left = new Node(original.Left.Data, clone);
            original = original.Left;
            clone = clone.Left;
        }
        else if (original.Right != null && clone.Right == null)
        {
            clone.Right = new Node(original.Right.Data, clone);
            original = original.Right;
            clone = clone.Right;
        }
        else
        {
            original = original.Parent;
            clone = clone.Parent;
        }
    }
    return root;
}

第一个代码段是解决方案。第二段是一个文件,您可以复制、粘贴和运行该文件以查看正在运行的解决方案。

解决方案:

public Node clone() {
    if(null == root)
        return null;
    Queue<Node> queue = new LinkedList<Node>();
    queue.add(root);
    Node n;
    Queue<Node> q2 = new LinkedList<Node>();
    Node fresh;
    Node root2 = new Node(root.data);
    q2.add(root2);
    while(!queue.isEmpty()) {
        n=queue.remove();
        fresh = q2.remove();
        if(null != n.left) {
            queue.add(n.left);
            fresh.left = new Node(n.left.data);
            q2.add(fresh.left);
        }
        if(null != n.right) {
            queue.add(n.right);
            fresh.right= new Node(n.right.data);
            q2.add(fresh.right);
        }           
    }       
    return root2;
}//

程序文件:

import java.util.LinkedList;
import java.util.Queue;
public class BST {
Node root;
public BST() {
    root = null;
}
public void insert(int el) {
    Node tmp = root, p = null;
    while (null != tmp && el != tmp.data) {
        p = tmp;
        if (el < tmp.data)
            tmp = tmp.left;
        else
            tmp = tmp.right;
    }
    if (tmp == null) {
        if (null == p)
            root = new Node(el);
        else if (el < p.data)
            p.left = new Node(el);
        else
            p.right = new Node(el);
    }
}//
public Node clone() {
    if(null == root)
        return null;
    Queue<Node> queue = new LinkedList<Node>();
    queue.add(root);
    Node n;
    Queue<Node> q2 = new LinkedList<Node>();
    Node fresh;
    Node root2 = new Node(root.data);
    q2.add(root2);
    while(!queue.isEmpty()) {
        n=queue.remove();
        fresh = q2.remove();
        if(null != n.left) {
            queue.add(n.left);
            fresh.left = new Node(n.left.data);
            q2.add(fresh.left);
        }
        if(null != n.right) {
            queue.add(n.right);
            fresh.right= new Node(n.right.data);
            q2.add(fresh.right);
        }           
    }       
    return root2;
}//
private void inOrder(Node n) {
    if(null == n) return;
    inOrder(n.left);
    System.out.format("%d;", n.data);
    inOrder(n.right);
}//
public static void main(String[] args) {
    int[] input = { 50, 25, 75, 10, 35, 60, 100, 5, 20, 30, 45, 55, 70, 90,
            102 };
    BST bst = new BST();
    for (int i : input)
        bst.insert(i);
    Node root2 = bst.clone();
    bst.inOrder(root2);
}
}
class Node {
public int data;
public Node left;
public Node right;
public Node(int el) {
    data = el;
}
}

两个想法:

  1. 您需要一个堆栈或父链接来遍历输入树(afaict)。所以让我们假设面试官会对其中一个感到满意。还有什么需要简化的?

    在代码中,您还可以遍历副本,并将其节点与原始节点并行存储。相反,您可以简单地将节点添加到副本的根目录中。只要您正确地选择了对原始结构的遍历,您就会得到相同的结构。

    不难看出,预购遍历会做到这一点(假设添加时没有重新平衡)。

    因此,您可以按照预购遍历的方式编写副本,再加上对副本根的简单添加。这将提供更简单的代码和/或允许重用,但代价是效率较低(您有O(nlog(n))额外的"跳跃",以便在插入时在副本中找到正确的位置)。

  2. 对于不可变的节点,您只需要复制根(这是正常的函数样式)。

我的直觉是,(1)可能是他想要的,考虑到"树的特性"。

一种没有递归的方法是遍历每个节点,如果节点包含右分支,则将该右节点推到堆栈上。当路径上的节点用完时(只跟随那些有左分支的节点),将顶部节点从堆栈中弹出,并使用相同的规则(向右推,然后向左推)。重复此循环,直到堆栈为空。这应该只需要一个堆栈。

编辑:你有没有问面试官他在寻找什么方法?如果没有,你应该这样做,以表明你愿意学习新事物,你应该试着让它成为一种双向对话。我相信你确实有学习新事物的意愿(或者你不会在这里发帖),但你的面试官知道吗?请记住,面试官通常不是在寻找问题的具体答案,而是在评估你解决问题的方法,而不是纯粹以技术的方式。如果你不以这种方式参与,面试官就不可能认为你什么都不知道,但有能力学习,可能会对他们的团队产生很大的影响。

这是我的工作代码:

对于每个节点,先向左推,再推左节点,再推右节点,然后重复同样的操作。

#include <iostream>
#include <stack>
using namespace std;
struct Node {
    Node() 
    : left(NULL), right(NULL) {}
    int val;
    Node *left;
    Node *right;
};
struct Wrap {
    Wrap()
    : oldNode(NULL), newNode(NULL), flags(0) {}
    Node *oldNode;
    Node *newNode;
    int flags;
};
void InOrder(Node *node) {
    if(node == NULL) {
        return;
    }
    InOrder(node->left);
    cout << node->val << " ";
    InOrder(node->right);
}
Node *AllocNode(int val) {
    Node *p = new Node;
    p->val = val;
    return p;
}
Wrap *AllocWrap(Node *old) {
    Wrap *wrap = new Wrap;
    Node *newNode = new Node;
    newNode->val = old->val;
    wrap->oldNode = old;
    wrap->newNode = newNode;
    return wrap;
}
Wrap* PushLeftNodes(stack<Wrap*> &stk) {
    Wrap *curWrap = stk.top();  
    while(((curWrap->flags & 1) == 0) && (curWrap->oldNode->left != NULL)) {
        curWrap->flags |= 1;
        Wrap *newWrap = AllocWrap(curWrap->oldNode->left);
        stk.push(newWrap);
        curWrap->newNode->left = newWrap->newNode;
        curWrap = newWrap;
    }
    return curWrap;
}
Node *Clone(Node *root) {
    if(root == NULL) {
        return NULL;
    }
    Node *newRoot = NULL;
    stack<Wrap*> stk;
    Wrap *wrap = AllocWrap(root);
    stk.push(wrap);
    while(!stk.empty()) {
        wrap = PushLeftNodes(stk);
        if(((wrap->flags & 2) == 0) && (wrap->oldNode->right != NULL)) {
            wrap->flags  |= 2;
            Wrap *newWrap = AllocWrap(wrap->oldNode->right);
            stk.push(newWrap);
            wrap->newNode->right = newWrap->oldNode;
            continue;
        }
        wrap = stk.top();
        newRoot = wrap->newNode;
        stk.pop();
        delete wrap;
    }
    return newRoot;
}
int main() {
    Node *root = AllocNode(10);
    Node *curNode = root;
    curNode->left = AllocNode(5);
    curNode->right = AllocNode(15);
    curNode = curNode->left;
    curNode->left = AllocNode(14);
    curNode->right = AllocNode(17);
    curNode = curNode->right;
    curNode->left = AllocNode(18);
    curNode->right = AllocNode(45);
    curNode = root->right;
    curNode->right = AllocNode(33);
    curNode = curNode->right;
    curNode->right = AllocNode(46);
    cout << "Original tree : " << endl;
    InOrder(root);
    Node *newRoot = Clone(root);
    cout << "New tree : " << endl; 
    InOrder(newRoot);
    return 0;
}

考虑以下树:

                      10
                      / 
                    20   30
                    /
                  40  50
                      /
                     60 70

现在基于BFS,如果你创建一个节点数组(数组用于更快的访问),它会是这样的:

节点阵列:10 20 30 40 50 N N N N 60 70 N N N

指数:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

请注意,为了更容易计算,索引是基于1的,叶节点的子节点表示为N表示NULL。

现在,如果您注意到,对于索引i处的节点,其左子节点位于索引2*i处,其右子节点位于索引来2*i+1处这是由一个适当的二叉树的性质决定的

现在以非递归方式复制二叉树。

  1. 基于BFS从原始树创建一个节点数组
  2. 指定电流=1(电流用于索引)
  3. 当前<=时执行以下操作节点阵列的大小

    a。创建新节点(父节点)

    b。从当前节点指定值parent->val。

    c。将当前(索引)处的原始节点替换为新节点。//这一步是为了能够遍历新节点。

    d。创建一个新节点,分配给parent->left,并分配Lindex=2*current中的值。

    e。用新节点替换Lindex上的原始节点。

    f。创建一个新节点,分配给parent->right,并从Rindex=2*current+1分配值。

    e。用新节点替换Rindex上的原始节点。

    f。将电流增加1。

现在谈到时间复杂性,

由于在数组中,我们还考虑了外部节点的NULL子节点,因此我们需要计算数组的总大小。

如果原始树中的节点数为n,则根据二叉树的性质,即二叉树外部节点的数量总是比内部节点的数量多1,

 a. Number of external nodes is (n + 1) /2.
 b. Number of children of external nodes (count of all null nodes) is 2 * (n + 1)/2 = n + 1.
 c. Thus the total size of array is number of nodes + number of null nodes = n + n + 1 = 2n + 1

由于在整个算法中,我们已经遍历了整个数组,因此该方法的时间复杂度为O(n)。

Java代码可以工作,并且有一个相对简单的方法:

static Node cloneTree(Node original){
    if(original == null) return null;
    Node temp = original;
    Node root = temp;
    if(original.left!=null)
      temp.left = original.left;
    if(original.right!=null)
      temp.right = original.right;
   cloneTree(original.left);
   cloneTree(original.right);
  return root;
}

迭代执行此操作非常愚蠢,除非树是巨大的,这就引出了为什么要首先复制它的问题。这里是一个C++尝试,它使用复制构造函数,因此隐式递归。

    struct Node
    {  
        Node *left, *right;
        int value;
        Node() : left(NULL), right(NULL) {}
        Node(const int &_v) : left(NULL), right(NULL), value(_v) {}
        Node(const Node &o) 
        : left(NULL), right(NULL), value(o.value)
        {
            if (o.left)
                left = new Node(*o.left);
            if (o.right)
                right = new Node(*o.right);
        }
        ~Node() { delete left; delete right; }
    };

换句话说,你有一棵像这样的树

Node *root = make_a_tree();

然后你这样复制:

if (root)
    Node *copy = new Node(*root);
else
    Node *copy = NULL;