Копирование двоичного дерева итеративным способом

мне задали этот вопрос в интервью, и это буквально стоило мне работы :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;
}

7 ответов


Если вам разрешено иметь родительские указатели в каждом узле, вам даже не нужен стек:

пройдите по исходному дереву и дереву, которое вы создаете параллельно. Если текущий узел в исходном дереве имеет левый дочерний элемент, а узел в создаваемом дереве-нет, создайте его и спуститесь влево. То же самое с правильными детьми. Если ни то, ни другое условие не применимо, поднимайтесь.

в коде (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;
}

две идеи:

  1. вам нужны либо стек, либо родительские ссылки, чтобы пересечь входное дерево (afaict). поэтому предположим, что интервьюер будет доволен одним из них. что осталось упростить?

    в вашем коде вы также пересекаете скопировать хранение своих узлов параллельно с вашим оригиналом. вместо этого вы можете просто добавить узлы в корень копии. пока вы правильно выбрали обход оригинала, вы в конечном итоге получите же структура.

    и нетрудно увидеть, что обход предварительного заказа сделает это (при условии отсутствия повторной балансировки при добавлении).

    таким образом, вы можете написать свою копию с точки зрения обхода предварительного заказа плюс простое добавление к корню копии. это дало бы более простой код и / или позволило бы повторно использовать, за счет снижения эффективности (у вас есть O(nlog(n)) дополнительные "прыжки", чтобы найти правильные места в вашей копии при вставке).

  2. для неизменяемых узлов, вы только нужно скопировать корень (это нормальный функциональный стиль).

мое чутье подсказывает, что (1) может быть тем, что он искал, учитывая ссылку на "свойства дерева".


первый сегмент кода является решением. Второй сегмент-это файл, который можно скопировать, вставить и запустить для просмотра решения на работе.

устранение:

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;
}
}

одним из методов без рекурсии было бы пройти через каждый узел, и если узел содержит правую ветвь, НАЖМИТЕ этот правый узел на стек. Когда у вас закончатся узлы вдоль вашего пути (следуя только тем узлам с левыми ветвями), вытащите верхний узел из стека и следуйте ему, используя те же правила (нажмите правую, затем следуйте левой). Повторяйте этот цикл, пока ваш стек не опустеет. Для этого требуется только один стек.

Edit: вы спросили интервьюера, какой метод он искал? Если нет, вы должны были сделать это таким образом, чтобы показать свою готовность учиться новому, и вы должны были попытаться сделать это двусторонним диалогом. Я уверен, что у вас есть такая готовность учиться новым вещам (или вы бы не разместили здесь), но знал ли об этом ваш интервьюер? Имейте в виду-интервьюеры, как правило, не ищут конкретный ответ на вещи, а скорее они хотят оценить ваш подход к их решению, а не чисто техническим образом. Если вы не занимайтесь этим способом, вы сделали невозможным для интервьюера считать, что вы не знаете всего, но можете учиться и можете сделать отличное дополнение к своей команде.


вот мой рабочий код :

для каждого узла продолжайте нажимать сначала влево, один сделал нажатие левых узлов, нажмите правый узел, а затем повторите то же самое.

#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;
}

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;
}

рассмотрим ниже дерево:

                      10
                      / \
                    20   30
                    /\
                  40  50
                      /\
                     60 70

теперь на основе BFS, если вы создадите массив узлов (массив используется для более быстрого доступа), это будет так:

массив узлов: 10 20 30 40 50 N N N N 60 70 N N N N

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

обратите внимание, что индексирование основано на 1 для упрощения расчета, а дочерние узлы листовых узлов обозначаются как N для НОЛЬ.

теперь, если вы заметите, что для узла с индексом i его левый дочерний элемент находится в индексе 2*i, а его правый дочерний элемент-в индексе 2 * i + 1. это свойство правильного двоичного дерева.

теперь, чтобы скопировать двоичное дерево не рекурсивно.

  1. создайте массив узлов из исходного дерева на основе BFS.
  2. назначить current = 1 (current для индекса),
  3. сделайте ниже, пока текущий

    a. создайте новый узел (Родительский)

    b. Назначьте значение parent - >val из узла в текущий момент.

    c. Замените исходный узел на текущий (индекс) с новым узлом. // Этот шаг должен быть в состоянии пройти через новые узлы.

    d. создайте новый узел, назначьте parent - >left и назначьте значение из Lindex = 2 * current.

    e. Замените исходный узел в Lindex новым узлом.

    Ф. создайте новый узел, назначьте родительскому - >right и назначьте значение из Rindex = 2 * current + 1.

    e. Замените исходный узел в Rindex новым узлом.

    f. Приращение тока на 1.

теперь перейдем к сложности времени,

поскольку в массиве мы также учитывали нулевые дочерние элементы внешних узлов, нам нужно вычислить общий размер массива.

Если количество узлов в исходном дереве равно 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).