Por que em uma fila de prioridade mínima (com base em heap) é chamada de "chave de diminuição" e não apenas "chave de configuração"?

7

Quando você chama a tecla de diminuição em uma fila de prioridade mínima, basicamente está definindo a chave, pode colocar acidentalmente uma chave mais alta, certo? então por que não é chamado de "chave de ajuste" ou "chave de atualização"? por que (de acordo com a Wikipedia e outras fontes) uma fila de prioridade mínima possui "chave de diminuição" e uma fila de prioridade máxima possui "chave de aumento"? por que não ter a "chave de ajuste" e se você a diminui ou aumenta, faça o que deve fazer para manter a propriedade da pilha invariável?

Quero dizer, e se eu chamar a tecla de diminuição em um heap mínimo e fornecer um valor maior? Será que vai lançar uma exceção? Por que não chamá-lo de "chave de ajuste" e manipular qualquer tipo de valor?

Eran Medan
fonte

Respostas:

8

Boa pergunta. Uma das aplicações importantes da estrutura de dados PriorityQueue está no algoritmo de Dijkstra. Cada nó fica à distância do nó inicial, que é atualizado quando caminhos mais curtos são descobertos. Portanto, as atualizações alteram apenas a chave em uma direção.

O problema na implementação do DecreaseKey não é que ele está apenas diminuindo (em vez de atualizar o valor). Para uma pilha binária, existem métodos bastante eficientes para aumentar e diminuir. Ambos trocam nós com outros nós ao longo de um caminho na árvore (para cima ou para baixo). O problema é saber onde encontrar a chave. Você não pode procurá-lo com eficiência, portanto, um "índice" separado deve ser mantido. Quando uma atualização é feita ao longo de um caminho, não apenas a chave diminuída é alterada no indax, mas também as outras chaves ao longo do caminho.

Para estruturas de dados abstratas, queremos especificar apenas as operações que são importantes em um contexto específico. Portanto, temos apenas o DecreaseKey, dada a motivação do Dijkstra. Embora para pilhas binárias estendendo as operações possa ser elementar, isso pode não ser o caso de outras implementações do PriorityQueue, como pilhas de esquerda, pilhas de Fibonacci ou pilhas de Brodal.

O que qualquer implementação em particular fará quando o novo valor estiver na direção errada depende dessa implementação.

Hendrik Jan
fonte
1

A nomeação do método provavelmente é deliberada em alguns livros (por exemplo, CLRS ) porque:

  1. A lógica para "flutuar para baixo" ou "borbulhar" uma chave em uma pilha binária é direta se você souber qual direção (para cima / para baixo) está indo. Por exemplo, "borbulhar" uma chave é simplesmente uma chamada recursiva nos pais do nó, versus flutuar onde você precisa comparar com cada filho , antes de prosseguir recursivamente.
  2. Muitos algoritmos que usam heap máximo / min exigem que você aumente ou diminua apenas as teclas, portanto, basta fornecer o método "flutuante" ou "borbulhante" correspondente.

Observe que, se você quiser implementar um método genérico de "chave de ajuste", poderá simplesmente combinar essas duas rotinas para decidir qual direção seguir, com base na necessidade ou não de pressionar ou flutuar a chave.

Para adicionar ao que Hendrick disse, vale a pena notar que, mesmo por exemplo, nos algoritmos de Dijkstra ou Prim, onde tecnicamente é suficiente uma operação de redução de tecla , usar um heap binário não requer necessariamente que você acompanhe a posição de cada tecla.

Nesses algoritmos, uma determinada chave é extraída do heap apenas uma vez . Por exemplo, no Dijkstra, um nó gráfico é adicionado apenas uma vez à árvore de caminhos mais curtos e, no algoritmo de Prim, uma aresta pode ser adicionada apenas uma vez à árvore de abrangência mínima.

Assim, em vez de manter o controle de posições-chave (ou procurá-los quando as necessidades algoritmo para diminuí-los), no pode simplesmente inserir o novo (neste caso, diminuir ) o valor para a chave na pilha (levando a duplicações na pilha) e, depois de extrair a "chave" do heap (tecnicamente dados de satélite, pois a chave não é o que você está consumindo avidamente), você pode ignorar todas as extrações subsequentes (por exemplo, usando um conjunto com ).O(1 1)

Essas duplicatas de conjunto e chave no heap obviamente custariam em complexidade de espaço, mas você poderia pensar na solução combinada como um método de "tecla de diminuição" que não requer conhecimento ou rastreamento de posições no heap.O(n)

Amelio Vazquez-Reina
fonte