Печать BFS (двоичное дерево) в порядке уровней с определенным форматированием

для начала, этот вопрос не является dup этот, но и опирается на него.

взяв дерево в этом вопросе, например,

    1 
   / 
  2   3
 /   / 
4   5   6

как бы вы изменили свою программу, чтобы напечатать ее так,

1
2 3
4 5 6

, а не общие

1 
2 
3 
4 
5 
6

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

идеи?

12 ответов


просто построить один уровень за раз, например:

class Node(object):
  def __init__(self, value, left=None, right=None):
    self.value = value
    self.left = left
    self.right = right

def traverse(rootnode):
  thislevel = [rootnode]
  while thislevel:
    nextlevel = list()
    for n in thislevel:
      print n.value,
      if n.left: nextlevel.append(n.left)
      if n.right: nextlevel.append(n.right)
    print
    thislevel = nextlevel

t = Node(1, Node(2, Node(4, Node(7))), Node(3, Node(5), Node(6)))

traverse(t)

редактировать: если вы хотите получить небольшую экономию в максимально потребляемой "вспомогательной" памяти (никогда не имея одновременно весь этот уровень и следующий уровень в такой "вспомогательной" памяти), вы можете, конечно, использовать collection.deque вместо list, и потреблять текущий уровень, как вы идете (через popleft) вместо того, чтобы просто зацикливаться. Идея создания одного уровня за раз (когда вы потребляете-или повторяете-предыдущий) остается нетронутым - когда вам нужно различать уровни, это просто более прямо, чем использование одного большого deque плюс вспомогательная информация (например, глубина или количество узлов, оставшихся на данном уровне).

однако список, который только добавляется (и зацикливается, а не" потребляется"), довольно эффективен, чем deque (и если вы после решений на C++, аналогично, std::vector, используя только push_back для построения его, и цикл для После этого используя его, более эффективен чем std:: deque). Поскольку все производство происходит сначала, то все итерации (или потребление), интересная альтернатива если память жестко ограничена может быть использовать список в любом случае для представления каждого уровня, а затем .reverse это прежде чем вы начнете потреблять ее (с .pop звонки) -- у меня нет больших деревьев вокруг, чтобы проверить по измерению, но я подозреваю, что этот подход все равно будет быстрее (и на самом деле меньше памяти), чем deque (предполагая, что базовый реализация list [[или std:: vector]] фактически перерабатывает память после нескольких вызовов pop [[или pop_back]] -- и с тем же предположением для deque, конечно;-).


звучит как в ширину обход для меня.

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

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


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

# built-in data structure we can use as a queue
from collections import deque

def print_level_order(head, queue = deque()):
    if head is None:
        return
    print head.data
    [queue.append(node) for node in [head.left, head.right] if node]
    if queue:
        print_level_order(queue.popleft(), queue)

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

public void printLevel(Node n) {
    Queue<Integer> q = new ArrayBlockingQueue<Integer>();
    Node sentinal = new Node(-1);
    q.put(n);
    q.put(sentinal);
    while(q.size() > 0) {
        n = q.poll();
        System.out.println(n.value + " "); 
        if (n == sentinal && q.size() > 0) {
           q.put(sentinal); //push at the end again for next level
           System.out.println();
        }
        if (q.left != null) q.put(n.left);
        if (q.right != null) q.put(n.right);
    }
}

мое решение похоже на решение Алекса Мартелли, но я отделяю обход структуры данных от обработки структуры данных. Я поместил мясо кода в iterLayers, чтобы сохранить printByLayer коротким и сладким.

from collections import deque

class Node:
    def __init__(self, val, lc=None, rc=None):
        self.val = val
        self.lc = lc
        self.rc = rc

    def iterLayers(self):
        q = deque()
        q.append(self)
        def layerIterator(layerSize):
            for i in xrange(layerSize):
                n = q.popleft()
                if n.lc: q.append(n.lc)
                if n.rc: q.append(n.rc)
                yield n.val
        while (q):
            yield layerIterator(len(q))

    def printByLayer(self):
        for layer in self.iterLayers():
            print ' '.join([str(v) for v in layer])

root = Node(1, Node(2, Node(4, Node(7))), Node(3, Node(5), Node(6)))
root.printByLayer()

, который печатает следующее при запуске:

1
2 3
4 5 6
7

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

def printBfsLevels(graph,start):
  queue=[start]
  path=[]
  currLevel=1
  levelMembers=1
  height=[(0,start)]
  childCount=0
  print queue
  while queue:
    visNode=queue.pop(0)
    if visNode not in path:
      if  levelMembers==0:
        levelMembers=childCount
        childCount=0
        currLevel=currLevel+1
      queue=queue+graph.get(visNode,[])
      if levelMembers > 0:
        levelMembers=levelMembers-1
        for node in graph.get(visNode,[]):
          childCount=childCount+1
          height.append((currLevel,node))
      path=path+[visNode]

  prevLevel=None

  for v,k in sorted(height):
        if prevLevel!=v:
          if prevLevel!=None:
            print "\n"
        prevLevel=v
        print k,
  return height

g={1: [2, 3,6], 2: [4, 5], 3: [6, 7],4:[8,9,13]}
printBfsLevels(g,1)


>>> 
[1]
1 

2 3 6 

4 5 6 7 

8 9 13
>>> 

другая версия, основанная на рекурсии, которая специфична для двоичного дерева

class BinTree:
  "Node in a binary tree"
  def __init__(self,val,leftChild=None,rightChild=None,root=None):
    self.val=val
    self.leftChild=leftChild
    self.rightChild=rightChild
    self.root=root
    if not leftChild and not rightChild:
      self.isExternal=True

  def getChildren(self,node):
    children=[]
    if node.isExternal:
      return []
    if node.leftChild:
      children.append(node.leftChild)
    if node.rightChild:
      children.append(node.rightChild)
    return children

  @staticmethod
  def createTree(tupleList):
    "Creates a Binary tree Object from a given Tuple List"
    Nodes={}
    root=None
    for item in treeRep:
      if not root:
        root=BinTree(item[0])
        root.isExternal=False
        Nodes[item[0]]=root
        root.root=root
        root.leftChild=BinTree(item[1],root=root)
        Nodes[item[1]]=root.leftChild
        root.rightChild=BinTree(item[2],root=root)
        Nodes[item[2]]=root.rightChild
      else:
        CurrentParent=Nodes[item[0]]
        CurrentParent.isExternal=False
        CurrentParent.leftChild=BinTree(item[1],root=root)
        Nodes[item[1]]=CurrentParent.leftChild
        CurrentParent.rightChild=BinTree(item[2],root=root)
        Nodes[item[2]]=CurrentParent.rightChild
    root.nodeDict=Nodes
    return root

  def printBfsLevels(self,levels=None):
    if levels==None:
      levels=[self]
    nextLevel=[]
    for node in levels:
      print node.val,
    for node in levels:
      nextLevel.extend(node.getChildren(node))
    print '\n'
    if nextLevel:
      node.printBfsLevels(nextLevel)  


##       1
##     2     3
##   4   5  6  7
##  8

treeRep = [(1,2,3),(2,4,5),(3,6,7),(4,8,None)]
tree= BinTree.createTree(treeRep)
tree.printBfsLevels()

>>> 
1 

2 3 

4 5 6 7 

8 None 

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

int counter=0;// to count the toatl no. of elments in the tree

void tree::print_treeupsidedown_levelbylevel(int *array)
{
    int j=2;  
    int next=j;
    int temp=0;
    while(j<2*counter)
    {
        if(array[j]==0)
        break;

        while(array[j]!=-1)
        {
            j++;
        }

        for(int i=next,k=j-1 ;i<k; i++,k--)
        {
            temp=array[i];
            array[i]=array[k];
            array[k]=temp;
        }

        next=j+1;
        j++;
    }

    for(int i=2*counter-1;i>=0;i--)
    {
        if(array[i]>0)
        printf("%d ",array[i]);

        if(array[i]==-1)
        printf("\n");
    }
}

void tree::BFS()
{
    queue<node *>p;

    node *leaf=root;

    int array[2*counter];
    for(int i=0;i<2*counter;i++)
    array[i]=0;

    int count=0;

    node *newline=new node; //this node helps to print a tree level by level
    newline->val=0;
    newline->left=NULL;
    newline->right=NULL;
    newline->parent=NULL;

    p.push(leaf);
    p.push(newline);

    while(!p.empty())
    {
        leaf=p.front();
        if(leaf==newline)
        {
            printf("\n");
            p.pop();
            if(!p.empty())
            p.push(newline);
            array[count++]=-1;
        }
        else
        {
            cout<<leaf->val<<" ";
            array[count++]=leaf->val;

            if(leaf->left!=NULL)
            {
                p.push(leaf->left);
            }
            if(leaf->right!=NULL)
            {
                p.push(leaf->right);
            }
            p.pop();
        }
    }
    delete newline;

    print_treeupsidedown_levelbylevel(array);
}

здесь, в моем коде, функция BFS печатает дерево по уровням, которое также заполняет данные в массиве int для печати дерева вверх ногами. (обратите внимание, что при печати дерева вверх ногами используется небольшая замена, которая помогает достичь нашей цели). Если замена не выполняется, то для дерева типа

                    8
                   /  \
                  1    12
                  \     /
                   5   9
                 /   \
                4     7
                     /
                    6

o / p будет

  6
  7 4
  9 5
  12 1
  8

но o / p должен будь

  6
  4 7
  5 9
  1 12
  8

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


class TNode:
  def __init__(self, data, left=None, right=None):
    self.data = data
    self.left = left
    self.right = right

class BST:
  def __init__(self, root):
    self._root = root

  def bfs(self):
    list = [self._root]
    while len(list) > 0:
        print [e.data for e in list]
        list = [e.left for e in list if e.left] + \
               [e.right for e in list if e.right]
bst = BST(TNode(1, TNode(2, TNode(4), TNode(5)), TNode(3, TNode(6), TNode(7))))
bst.bfs()

следующий код выведет каждый уровень двоичного дерева в новую строку:

public void printbylevel(node root){
    int counter = 0, level = 0;
    Queue<node> qu = new LinkedList<node>();

    qu.add(root);
    level = 1;
    if(root.child1 != null)counter++;
    if(root.child2 != null)counter++;

     while(!qu.isEmpty()){
         node temp = qu.remove();
         level--;
         System.out.print(temp.val);
         if(level == 0 ){
             System.out.println();

             level = counter;
             counter = 0;
         }
        if(temp.child1 != null){
            qu.add(temp.child1);
            counter++;
        }
        if(temp.child2 != null){
            qu.add(temp.child2);
            counter++;
        }
     }
}

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


Я думаю, что вы ожидаете распечатать узлы на каждом уровне, разделенные пробелом или запятой, а уровни будут разделены новой строкой. Вот как я бы закодировал алгоритм. Мы знаем, что когда мы делаем широтно-первый поиск на графике или дереве и вставляем узлы в очередь, все узлы в очереди будут либо на том же уровне, что и предыдущий, либо на новом уровне, который является родительским уровнем + 1, и ничего больше.

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

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

предполагая, что дерево начинается с корня.

queue = [(root, 0)]  # Store the node along with its level. 
prev = 0
while queue:
  node, level = queue.pop(0)
  if level == prev:
    print(node.val, end = "")
  else:
    print()
    print(node.val, end = "")
  if node.left:
    queue.append((node.left, level + 1))
  if node.right:
    queue.append((node.right, level + 1))
  prev = level

В конце все, что вам нужно, это очереди на обработку.


версию, которая не требует дополнительного хранения:

std::deque<Node> bfs;
bfs.push_back(start);
int nodesInThisLayer = 1;
int nodesInNextLayer = 0;
while (!bfs.empty()) {
    Node front = bfs.front();
    bfs.pop_front();
    for (/*iterate over front's children*/) {
        ++nodesInNextLayer;
        nodes.push_back(child);
    }
    std::cout << node.value;
    if (0 == --nodesInThisLayer) {
        std::cout << std::endl;
        nodesInThisLayer = nodesInNextLayer; 
        nodesInNextLayer = 0;
    } else {
        std::cout << " ";
    }
}

P. S. простите за код на C++, я не очень свободно говорит на языке Python еще.