Как удалить дочерние узлы дерева с одним дочерним

У меня есть массив для предварительного обхода дерева (значения узлов - это значения глубины). Все, что я хочу сделать, это минимизировать дерево, удалив дочерние элементы внутренних узлов, имеющих только один дочерний элемент.

В качестве примера (дерево с максимальной глубиной = 3) проблема визуализируется здесь
< br> Входной массив: [0, 1, 2, 3, 3, 1, 2, 3]
Выходной массив: [0, 1, 2, 2, 1]

Каким должен быть алгоритм?


person guezara    schedule 02.02.2016    source источник
comment
Что вы пробовали? Подсказка: можете ли вы воссоздать дерево из этой информации?   -  person Pham Trung    schedule 02.02.2016
comment
Извините, я не понял, что вы имели в виду. Из выходного массива невозможно создать входное дерево, но это и не моя цель. Не могли бы вы объяснить больше, пожалуйста?   -  person guezara    schedule 02.02.2016
comment
Пример не соответствует вашему описанию. Вы удаляете обоих дочерних элементов самого левого 2 узла.   -  person n. 1.8e9-where's-my-share m.    schedule 02.02.2016
comment
Согласен, надо уточнить вопрос. Я ответил для примера.   -  person kfx    schedule 02.02.2016
comment
Фактически, он сдвигается на один уровень вверх (поскольку я удаляю самый левый 1 узел - единственный узел с одним дочерним элементом). Я не удаляю узел во 2-м уровне. Извините за мое возможное плохое объяснение и английский, не могли бы вы помочь мне объяснить такую ​​​​проблему?   -  person guezara    schedule 02.02.2016
comment
@kfx, ваше решение работает. Спасибо   -  person guezara    schedule 02.02.2016


Ответы (2)


Простой алгоритм среднего случая O (nlog (n)) возникает в результате решения проблемы с помощью подхода «разделяй и властвуй».

Начните с input_level = 0, output_level=0, left=0, right=n-1.

На каждом из рекурсивных шагов подсчитайте элементы со значением input_level+1 во входном массиве A в диапазоне [left, right]. Это дети текущего узла. Если таких элементов нет, выведите output_level и вернитесь. Если такой элемент только один, "удалить" текущий узел (т.е. не печатать его), увеличить left на 1 и вызвать функцию рекурсивно. Если таких элементов два или более, выведите output_level, увеличьте output_level на 1 и рекурсивно примените функцию к каждому из интервалов, разграниченных дочерними элементами. Всегда увеличивайте input_level при выполнении рекурсивного вызова.

Для примера ввода A=[0, 1, 2, 3, 3, 1, 2, 3] сначала алгоритм найдет элементы со значением 1 по индексам 1 и 5. Затем он напечатает 0, увеличит output_level и current_level на 1 и рекурсивно вызовет себя два раза: в диапазонах [1, 4] и [5, 7].

Сложность этого составляет O(n2) в худшем случае (для вырожденного дерева, которое на самом деле является списком), но в среднем O(nlog(n)) при случайном n -арное дерево имеет высоту O(log(n)).

person kfx    schedule 02.02.2016

Следующий алгоритм работает за O(N). Думаю, на этот раз я правильно понял.

#include <algorithm>
#include <iostream>
#include <stack>
#include <tuple>
#include <utility>
#include <vector>

void mark_nodes(const std::vector<unsigned>& tree,
                std::vector<bool>& mark) {
  // {depth, index, mark?}
  using triple = std::tuple<unsigned, unsigned, bool>;
  std::stack<triple> stk;
  stk.push({0, 0, false});
  for (auto i = 1u; i < mark.size(); ++i) {
    auto depth = tree[i];
    auto top_depth = std::get<0>(stk.top());
    if (depth == top_depth) {
      stk.pop();
      if (stk.size()) std::get<2>(stk.top()) = false;
      continue;
    }
    if (depth > top_depth) {
      std::get<2>(stk.top()) = true;
      stk.push({depth, i, false});
      continue;
    }
    while (std::get<0>(stk.top()) != depth) {
      mark[std::get<1>(stk.top())] = std::get<2>(stk.top());
      stk.pop();
    }
    mark[std::get<1>(stk.top())] = std::get<2>(stk.top());
    stk.pop();
    if (stk.size()) std::get<2>(stk.top()) = false;
    stk.push({depth, i, false});
  }
  mark[0] = false;
}

std::vector<unsigned> trim_single_child_nodes(
    std::vector<unsigned> tree) {
  tree.push_back(0u);
  std::vector<bool> mark(tree.size(), false);
  mark_nodes(tree, mark);
  std::vector<unsigned> ret(1, 0);
  tree.pop_back();
  mark.pop_back();
  auto max_depth = *std::max_element(tree.begin(), tree.end());
  std::vector<unsigned> depth_map(max_depth + 1, 0);
  for (auto i = 1u; i < tree.size(); ++i) {
    if (mark[i]) {
      if (tree[i] > tree[i - 1]) {
        depth_map[tree[i]] = depth_map[tree[i - 1]];
      }
    } else {
      if (tree[i] > tree[i - 1]) {
        depth_map[tree[i]] = depth_map[tree[i - 1]] + 1;
      }
      ret.push_back(depth_map[tree[i]]);
    }
  }
  return ret;
}

int main() {
  std::vector<unsigned> input = {0, 1, 2, 3, 3, 1, 2, 3};
  auto output = trim_single_child_nodes(input);
  for (auto depth : output) {
    std::cout << depth << ' ';
  }
}
person Lingxi    schedule 02.02.2016
comment
похоже не работает должным образом. т. е. {0,1,2,1,2} дает результат {0,1,2,1}. вместо этого должно быть {0,1,1}. - person guezara; 02.02.2016
comment
@guezara Да. Случай листового узла кажется сложным. - person Lingxi; 02.02.2016
comment
@guezara Наконец-то. Думаю, на этот раз я правильно понял. - person Lingxi; 02.02.2016
comment
Решение O (N) было бы идеальным для меня @Lingxi, так как мои данные действительно огромны, но я не понял ваше решение во всех деталях. т.е. [0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5] ввод дает вывод [ 0, 1, 0, 1, 2, 2, 1, 1]. Вместо этого должно быть [ 0, 1, 1, 2, 2, 1, 1 ]. Можете ли вы догадаться, почему? Спасибо - person guezara; 03.02.2016
comment
@guezara mark_nodes(), скорее всего, правильный, что означает, что узлы должны быть обрезаны. Проблема в том, как использовать эту информацию для вычисления конечного результата. Я думаю, что я не могу продвигать это дальше себя. Извини за это. Я пытался, но потерпел неудачу. Тем не менее, я верю, что алгоритм линейного времени существует. В любом случае оставлю ответ открытым здесь. Надеюсь, более способные люди увидят это и доведут до конца. - person Lingxi; 03.02.2016