Subfaixa de uma árvore vermelha e preta


14

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.


3
@ Radu: Há um erro no recurso de edição de comentários. Se você usar o látex em um comentário e editar o comentário, verá um comportamento estranho, como duplicação etc.
Aryabhata

@ Radu Adicionei alguns parágrafos para explicar melhor minha pergunta.
Daniel C. Sobral

As árvores são imutáveis?
Tsuyoshi Ito

Além disso, você quis dizer limite superior em vez de limite inferior no último parágrafo?
Tsuyoshi Ito

2
Parece que a operação de divisão em árvores vermelho-pretas pode ser implementada no pior caso O (log n), em que n é o número de elementos em uma árvore. Esta alegação pode ser encontrada na introdução do artigo “Listas ordenadas de tempo constante puramente funcional, no pior caso possível”, de Gerth Stølting Brodal, Christos Makris e Kostas Tsichlas, ESA 2006: cs.au.dk/~gerth/pub/esa06trees.html . Como mencionei no meu comentário anterior, isso permite uma pior implementação do tempo O (log n) da operação subrange.
Tsuyoshi Ito

Respostas:


10

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.


@Radu GRIGore: Sim, a menos que esteja faltando alguma coisa.
Tsuyoshi Ito

@Radu GRIGore: Ou talvez não, agora não tenho certeza. Essa implementação da operação de divisão aloca O (log n) novos nós para a árvore de saída, mas acho que toda a operação provavelmente pode ser implementada da maneira recursiva da cauda, ​​exigindo apenas O (1) espaço de trabalho. Se isso estiver correto, a resposta para sua pergunta dependerá do que você quer dizer com “espaço extra”. #
Tsuyoshi Ito

@Radu GRIGore: Nesse caso, acho que o espaço extra é O (1), embora eu não o tenha verificado com cuidado.
Tsuyoshi Ito

@Radu GRIGore: Não vejo a razão pela qual se preocupa com a quantidade de espaço de trabalho sem se preocupar com a quantidade de espaço necessária para armazenar o resultado em si. Na teoria da complexidade, o problema geralmente especifica qual é o resultado e, portanto, o espaço necessário para armazená-lo não depende de algoritmos. No entanto, no problema atual, existem muitas maneiras de implementar a operação necessária e algumas implementações precisam de mais espaço para armazenar o resultado do que outras. Se você ignora a diferença dessa quantidade de espaço, não vejo por que você se importa com a quantidade de espaço de trabalho que precisamos.
Tsuyoshi Ito

O problema das árvores imutáveis ​​é diferente do problema das árvores mutáveis. Eu entendo a diferença, então não tinha o que perguntar. Agora, ampliando um dos dois problemas, há dois aspectos a serem discutidos: memória e tempo. Você não disse quanta memória você usa e não me parecia óbvio qual é a resposta, então eu perguntei. Não vejo como isso o fez pensar que ignoro a diferença entre os dois problemas.
Radu GRIGore

8

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


5
Bem-vindo ao site, Danny!
Suresh Venkat

2
Como isso ajudará a modificar a implementação do Scala Red Black para oferecer suporte à suborganização em menos de 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.
Daniel C. Sobral

6

(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.


Este método pressupõe que alguém já tenha ponteiros (ou "dedos") para os dois limites.
Jbapple #

3

nm[a,b]

  1. O(lgn)aa
  2. O(m)
  3. O(m)

O(m+lgn)O(n+mlgm)

o(m)Ω(lgm)klgm

Como não resolvi os detalhes, não tenho certeza de como a contabilidade extra afeta o tempo de execução.

O(1)Ω(lgm)


Pensando nisso, acho que poderia obter uma contagem aproximada O(logn), com a qual poderia evitar a matriz temporária.
Daniel C. Sobral

Você pode obter a contagem em O (lg n) armazenando os tamanhos das subárvores em suas raízes.
Radu GRIGore

... mas armazenar tamanhos em nós contraria o requisito de não usar espaço auxiliar, portanto, minha observação não trata da sua preocupação com a memória.
Radu GRIGore

1
As árvores binárias podem ser perfeitamente reequilibradas usando apenas o espaço extra CONSTANTE (além da própria árvore): eecs.umich.edu/~qstout/abs/CACM86.html
Jeffε

O(m+lgn)O(1)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.