Алгоритм преобразования плоских иерархических данных (с ParentID) в отсортированный плоский список с уровнями отступов - PullRequest
5 голосов
/ 20 мая 2010

У меня есть следующая структура:

MyClass {
  guid ID
  guid ParentID
  string Name
}

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

Например:

ID     Name     ParentID
------------------------
1      Cats     2
2      Animal   NULL
3      Tiger    1
4      Book     NULL
5      Airplane NULL

По сути, это приведет к созданию следующих объектов:

// Array is an array of all the elements sorted by the way you would see them in a fully expanded tree
Array[0] = "Airplane"
Array[1] = "Animal"
Array[2] = "Cats"
Array[3] = "Tiger"
Array[4] = "Book"

// IndentationLevel is a hash of GUIDs to IndentationLevels.
IndentationLevel["1"] = 1
IndentationLevel["2"] = 0
IndentationLevel["3"] = 2
IndentationLevel["4"] = 0
IndentationLevel["5"] = 0

Для ясности, вот как выглядит иерархия:

Airplane
Animal
  Cats
    Tiger
Book

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

Две цели:

  1. Сохранить хэш идентификатора до уровня отступа.
  2. Сортировка списка, в котором содержатся все объекты, по левым значениям.

Когда я получаю список элементов, они не располагаются в определенном порядке. Братья и сестры должны быть упорядочены по свойству Name.

Обновление: Может показаться, что я сам не пытался найти решение и просто хотел, чтобы другие делали работу за меня. Тем не менее, я попытался придумать три разных решения, и я застрял на каждом. Одной из причин может быть то, что я пытался избежать рекурсии (возможно, неправильно). Пока я не публикую частичные решения, которые у меня есть, поскольку они неверны и могут плохо влиять на решения других.

Ответы [ 3 ]

4 голосов
/ 20 мая 2010

Мне нужен был похожий алгоритм для сортировки задач по зависимостям (каждая задача могла иметь родительскую задачу, которую нужно было выполнить сначала). Я нашел топологическая сортировка . Вот итерационная реализация в Python с очень подробными комментариями.

Уровень отступа может быть рассчитан при выполнении топологической сортировки. Просто установите уровень отступа узла равным уровню отступа родительского узла + 1, так как он добавляется к топологическому порядку.

Обратите внимание, что может существовать много допустимых топологических порядков. Чтобы гарантировать, что результирующий топологический порядок группирует родительские узлы с дочерними узлами, выберите алгоритм топологической сортировки, основанный на прохождении графа в глубину графика, полученного с помощью информации частичного упорядочения.

Википедия дает еще два алгоритма для топологической сортировки . Обратите внимание, что эти алгоритмы не так хороши, потому что первый - обход в ширину, а второй - рекурсивный.

2 голосов
/ 20 мая 2010

Для иерархических структур вам почти наверняка понадобится рекурсия (если вы допустите произвольную глубину). Я быстро взломал некоторый код ruby, чтобы проиллюстрировать, как вы можете достичь этого (, хотя я не сделал отступ ):

# setup the data structure
class S < Struct.new(:id, :name, :parent_id);end

class HierarchySorter

    def initialize(il)
        @initial_list = il
        first_level = @initial_list.select{|a| a.parent_id == nil}.sort_by{|a| a.name }
        @final_array = subsort(first_level, 0)
    end

    #recursive function
    def subsort(list, indent_level)
        result = []
        list.each do |item|
            result << [item, indent_level]
            result += subsort(@initial_list.select{|a| a.parent_id == item.id}.sort_by{|a| a.name }, indent_level + 1)
        end
        result
    end

    def sorted_array
        @final_array.map &:first
    end

    def indent_hash
        # magick to transform array of structs into hash
        Hash[*@final_array.map{|a| [a.first.id, a.last]}.flatten]
    end

end

hs = HierarchySorter.new [S.new(1, "Cats", 2), S.new(2, "Animal", nil), S.new(3, "Tiger", 1), S.new(4, "Book", nil),
    S.new(5, "Airplane", nil)]

puts "Array:"
puts hs.sorted_array.inspect

puts "\nIndentation hash:"
puts hs.indent_hash.inspect

Если ты не говоришь на рубине, я могу перестроить его в другое.

Редактировать: Я обновил код выше для вывода обеих структур данных.

Выходы:

Array:
[#<struct S id=5, name="Airplane", parent_id=nil>, #<struct S id=2, name="Animal", parent_id=nil>, #<struct S id=1, name="Cats", parent_id=2>, #<struct S id=3, name="Tiger", parent_id=1>, #<struct S id=4, name="Book", parent_id=nil>]

Indentation hash:
{5=>0, 1=>1, 2=>0, 3=>2, 4=>0}
1 голос
/ 23 мая 2010

Пост Wonsungi очень помог, однако это для общего графика, а не дерева.Поэтому я немного изменил его, чтобы создать алгоритм, разработанный специально для дерева:

// Data strcutures:
nodeChildren: Dictionary['nodeID'] = List<Children>;
indentLevel: Dictionary['nodeID'] = Integer;
roots: Array of nodes;
sorted: Array of nodes;
nodes: all nodes

// Step #1: Prepare the data structures for building the tree
for each node in nodes
  if node.parentID == NULL
    roots.Append(node);
    indentLevel[node] = 0;
  else
    nodeChildren[node.parentID].append(node);

// Step #2: Add elements to the sorted list
roots.SortByABC();
while roots.IsNotEmpty()
  root = roots.Remove(0);
  rootIndentLevel = indentLevel[root];
  sorted.Append(root);
  children = nodeChildren[root];
  children.SortByABC();
  for each child in children (loop backwards)
    indentLevel[child] = rootIndentLevel + 1
    roots.Prepend(child)
...