Enquanto tentava consertar um bug em uma biblioteca, procurei artigos sobre como encontrar subfaixas em árvores vermelhas e negras sem sucesso. Estou pensando em uma solução usando zíperes e algo semelhante à operação de acréscimo usual usada em algoritmos de exclusão para estruturas de dados imutáveis, mas ainda estou imaginando se há uma abordagem melhor que não consegui encontrar ou mesmo algum limite mínimo de complexidade. em tal operação?
Só para esclarecer, estou falando de um algoritmo que, dada uma árvore vermelha e preta e dois limites, produzirá uma nova árvore vermelha e preta com todos os elementos da primeira árvore que pertencem a esses limites.
Obviamente, um limite superior para a complexidade seria a complexidade de atravessar uma árvore e construir a outra adicionando elementos.
fonte
Respostas:
Esta resposta combina alguns dos meus comentários à pergunta e os expande.
A operação de subfaixa em árvores vermelho-pretas pode ser executada no pior momento O (log n), em que n é o número de elementos na árvore original. Como a árvore resultante compartilhará alguns nós com a árvore original, essa abordagem será adequada apenas se as árvores forem imutáveis (ou as árvores forem mutáveis, mas a árvore original não for mais necessária).
Primeiro observe que a operação de subfaixa pode ser implementada por duas operações de divisão. Aqui, a operação de divisão pega uma árvore vermelha e preta T e uma chave x e produz duas árvores L e R, de modo que L consiste em todos os elementos de T menores que xe R os elementos de T maiores que x. Portanto, nosso objetivo agora é implementar a operação de divisão em árvores vermelho-pretas no pior caso O (log n).
Como realizamos a operação de divisão em árvores vermelho-pretas no tempo O (log n)? Bem, descobriu-se que havia um método bem conhecido. (Eu não sabia, mas não sou especialista em estruturas de dados.) Considere a operação de junção , que utiliza duas árvores L e R de modo que todo valor em L seja menor que todo valor em R e produz uma árvore que consiste em todos os elementos. valores em L e R. A operação de junção pode ser implementada no pior caso O (| r L- r R | +1), em que r L e r Rsão as fileiras de L e R, respectivamente (ou seja, o número de nós pretos no caminho da raiz para cada folha). A operação de divisão pode ser implementada usando a operação de junção O (log n) vezes, e o tempo total de pior caso ainda é O (log n) considerando uma soma telescópica.
As seções 4.1 e 4.2 de um livro [Tar83] de Tarjan descrevem como implementar as operações de junção e de divisão em árvores vermelho-pretas no pior caso O (log n). Essas implementações destroem árvores originais, mas é fácil convertê-las em implementações imutáveis e funcionais, copiando nós em vez de modificá-los.
Como observação lateral, os módulos Set e Map do Objective Caml fornecem a operação de divisão, bem como outras operações padrão em árvores de pesquisa binária equilibrada (imutáveis). Embora eles não usem árvores vermelho-pretas (eles usam árvores de pesquisa binária equilibradas com a restrição de que a altura esquerda e a altura certa diferem no máximo 2), observar suas implementações também pode ser útil. Aqui está a implementação do módulo Set .
Referências
Robert Tarre Tarjan. Estruturas de dados e algoritmos de rede . Volume 44 da Série de Conferências Regionais CBMS-NSF em Matemática Aplicada , SIAM, 1983.
fonte
A solução é não usar árvores vermelho-pretas. Nas árvores splay e AVL, o código para dividir e unir é muito simples. Refiro-lhe as seguintes URLs com código java para árvores splay e árvores AVL que suportam isso. Vá para o seguinte URL e confira Set.java (avl trees) e SplayTree.java (splay trees).
ftp://ftp.cs.cmu.edu/usr/ftp/usr/sleator/splaying/
--- Danny Sleator
fonte
O(n)
? Não perguntei que tipo de árvore possui implementações simples de subintervalo porque esse não é um problema que tenho. Essa resposta, apesar de bem-intencionada, é fora de tópico e inútil para o problema em questão.(Isso deve ser um comentário, mas sou novo demais para deixar um comentário.)
Quero apenas observar que você também pode estar interessado na operação "excisão", que retorna o subintervalo como uma nova árvore e a árvore de entrada sem o subintervalo como outra. Você precisa ter controle sobre a representação subjacente da árvore, já que o método conhecido se baseia em links de nível. A excisão é executada no tempo logarítmico ao tamanho da árvore menor , embora no sentido amortizado ("amortizado" é iirc, porque eu não tenho mais acesso ao papel) Veja:
K. Hoffman, K. Mehlhorn, P. Rosenstiehl e RE Tarjan, classificando sequências de Jordan em tempo linear usando árvores de pesquisa de nível vinculado, Information and Control, 68 (1986), 170–184
PS A citação acima veio do artigo de Seidel. As guloseimas também suportam a excisão.
fonte
Como não resolvi os detalhes, não tenho certeza de como a contabilidade extra afeta o tempo de execução.
fonte
O(logn)
, com a qual poderia evitar a matriz temporária.