以迭代方式复制二叉树
Copy a binary tree in iterative manner
我在一次面试中被问到这个问题,这真的让我失去了一份工作: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;
}
}
两个想法:
-
您需要一个堆栈或父链接来遍历输入树(afaict)。所以让我们假设面试官会对其中一个感到满意。还有什么需要简化的?
在代码中,您还可以遍历副本,并将其节点与原始节点并行存储。相反,您可以简单地将节点添加到副本的根目录中。只要您正确地选择了对原始结构的遍历,您就会得到相同的结构。
不难看出,预购遍历会做到这一点(假设添加时没有重新平衡)。
因此,您可以按照预购遍历的方式编写副本,再加上对副本根的简单添加。这将提供更简单的代码和/或允许重用,但代价是效率较低(您有O(nlog(n))额外的"跳跃",以便在插入时在副本中找到正确的位置)。
-
对于不可变的节点,您只需要复制根(这是正常的函数样式)。
我的直觉是,(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处这是由一个适当的二叉树的性质决定的
现在以非递归方式复制二叉树。
- 基于BFS从原始树创建一个节点数组
- 指定电流=1(电流用于索引)
当前<=时执行以下操作节点阵列的大小
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;
- 从父数组测试用例构造二叉树失败
- 打印时有二叉树问题.用户输入不打印任何内容
- 试图找到二叉树的深度
- 二叉树结构平衡,使用递归时EXC_BAD_ACCESS
- 指向二叉树中新节点的指针
- 我试图用这段代码找到二叉树的高度,但它一直返回 0,有人可以告诉我为什么吗?
- C++:如何计算二叉树中其值模块高度小于 2 的节点数?
- 二叉树级别顺序遍历在leetcode中
- 运行无限循环的最小二叉树问题
- 打印二叉树中的常见元素
- 二叉树基准测试结果
- 带有矢量重复值的二叉树打印出来
- 签入二叉树的函数是平衡C++
- 我有两棵二叉树.我想在不更改输入树的情况下深度复制两个二叉树的结果
- 如何计算给定数字在二叉树中出现的次数?
- 为什么我的二叉树会覆盖其根的叶子?
- 将二叉树的节点插入链表 (C++)
- 二叉树 - 复制构造函数
- 以迭代方式复制二叉树
- 二叉树的深度复制