Meia década atrás, eu estava sentado em uma classe de estruturas de dados em que o professor oferecia crédito extra se alguém pudesse atravessar uma árvore sem usar recursão, pilha, fila etc. (ou qualquer outra estrutura de dados semelhante) e apenas alguns indicadores. Eu vim com o que eu pensava ser uma resposta óbvia a essa pergunta que foi finalmente aceita pelo professor. Eu estava sentado em uma aula de matemática discreta com outro professor do mesmo departamento - e ele afirmou que era impossível atravessar uma árvore sem recursão, pilha, fila etc., e que minha solução era inválida.
Então, é possível ou impossível? Por que ou por que não?
Edit: Para adicionar alguns esclarecimentos, eu implementei isso em uma árvore binária que tinha três elementos - os dados armazenados em cada nó e ponteiros para dois filhos. Minha solução pode ser estendida para árvores n-árias com apenas algumas alterações.
Meu professor de estruturas de dados não impôs restrições contra a mutação da árvore e, de fato, descobri mais tarde que sua própria solução era usar os ponteiros filhos para apontar de volta para a árvore em seu caminho. Meu discreto professor de matemática disse que qualquer mutação de uma árvore significa que ela não é mais uma árvore de acordo com a definição matemática de uma árvore; sua definição também impediria qualquer indicação para os pais - o que corresponderia ao caso em que eu a resolvi acima.
fonte
Respostas:
Muitas pesquisas nessa área foram realizadas, motivadas pelo método de "baratear" a travessia de árvores e estruturas gerais de listas no contexto da coleta de lixo.
Uma árvore binária encadeada é uma representação adaptada de árvores binárias onde alguns ponteiros nulos são usados para vincular a nós sucessores na árvore. Esta informação extra pode ser usada para atravessar uma árvore sem pilha. No entanto, é necessário um bit extra por nó para distinguir threads de ponteiros filhos. Wikipedia: Tree_traversal
Até onde eu sei, as árvores binárias implementadas usando ponteiros da maneira usual (ponteiro esquerdo e direito por nó) podem ser percorridas usando o método de threads, em um método atribuído a Morris . Os ponteiros NIL são temporariamente reutilizados para encadear um caminho de volta à raiz. A parte inteligente é que, durante a travessia, é possível distinguir as arestas originais dos elos de encadeamento temporários, usando a forma como eles formam ciclos na árvore).
Boa parte: sem estrutura de dados extra. Parte ruim: levemente trapaceira, a pilha está dentro da árvore de uma maneira inteligente. Muito esperto.
Uma prova da pilha oculta é mostrada em P. Mateti e R. Manghirmalani: Algoritmo transversal de árvore de Morris reconsiderado DOI: 10.1016 / 0167-6423 (88) 90063-9
JM Morris: Atravessar árvores binárias de maneira simples e barata. IPL 9 (1979) 197-200 DOI: 10.1016 / 0020-0190 (79) 90068-1
Depois, há também a digitalização Lindstrom . Este método "gira" os três ponteiros envolvidos em cada nó (pai e dois filhos). Se você deseja executar qualquer algoritmo decente de pré-encomenda ou pós-encomenda, precisa de bits extras por nó. Se você quiser apenas visitar todos os nós (três vezes, mas nunca souber qual visita realizar), isso poderá ser feito sem os bits.
G. Lindstrom: estruturas de lista de varredura sem pilhas ou bits de tag. IPL 2 (1973) 47-51. DOI: 10.1016 / 0020-0190 (73) 90012-4
Talvez a maneira mais direta seja um método de Robson . Aqui, a pilha necessária para o algoritmo clássico é enfiada através das folhas.
JM Robson: Um algoritmo aprimorado para atravessar árvores binárias sem pilha auxiliar IPL 1 (1973) 149-152. 10.1016 / 0020-0190 (73) 90018-5
IPL = Cartas de processamento de informações
fonte
fonte
Minha solução foi uma travessia de primeira geração usando loops for-aninhados para forçar brutalmente a árvore. Isso não é eficiente de forma alguma e, de fato, uma estrutura de dados recursiva como uma árvore está implorando por uma passagem recursiva, mas a questão não era se uma árvore poderia ser atravessada com eficiência, é se isso era possível.
Para os primeiros níveis, seria assim, como você pode ver, o operador bit a bit no pseudocódigo simplesmente decide uma curva à esquerda ou à direita em uma árvore binária:
Para n-ária, você usaria i% (maxChildren ** j) / j para determinar qual caminho seguir entre 0 e maxChildren.
Em cada nó no n-ário, você também precisa verificar se o número de filhos é maior que maxChildren e atualizá-lo adequadamente.
fonte
depth
exceder a largura deint
?i
temdepth
bits de largura. Sedepth
é (que poderia ser, em árvores desequilibradas), então você precisa Θ ( n ) espaço para armazenar o número inteiro , de modo que as necessidades de soluções mais do que Oi