Encontrei essa questão em um livro de algoritmos ( Algorithms, 4th Edition, de Robert Sedgewick e Kevin Wayne).
Fila com três pilhas. Implemente uma fila com três pilhas para que cada operação da fila tome um número constante (no pior caso) de operações da pilha. Atenção: alto grau de dificuldade.
Eu sei como fazer uma fila com 2 pilhas, mas não consigo encontrar a solução com 3 pilhas. Qualquer ideia ?
(Ah, e isso não é lição de casa :))
algorithm
data-structures
DuoSRX
fonte
fonte
Respostas:
RESUMO
DETALHES
Há duas implementações por trás deste link: http://www.eecs.usma.edu/webs/people/okasaki/jfp95/index.html
Um deles é O (1) com três pilhas, mas utiliza execução lenta, o que na prática cria estruturas de dados intermediárias extras (fechamentos).
Outro deles é O (1), mas usa SEIS pilhas. No entanto, funciona sem execução lenta.
ATUALIZAÇÃO: O artigo de Okasaki está aqui: http://www.eecs.usma.edu/webs/people/okasaki/jfp95.ps e parece que ele realmente usa apenas 2 pilhas para a versão O (1) que tem uma avaliação lenta. O problema é que ele é realmente baseado em avaliações preguiçosas. A questão é se ele pode ser traduzido para um algoritmo de 3 pilhas sem avaliação lenta.
ATUALIZAÇÃO: Outro algoritmo relacionado é descrito no artigo "Stacks versus Deques", de Holger Petersen, publicado na 7ª Conferência Anual de Computação e Combinatória. Você pode encontrar o artigo no Google Livros. Verifique as páginas 225-226. Mas o algoritmo não é realmente uma simulação em tempo real, é uma simulação em tempo linear de uma fila dupla em três pilhas.
gusbro: "Como o @Leonel disse há alguns dias, pensei que seria justo verificar com o Prof. Sedgewick se ele sabia uma solução ou se havia algum erro. Então, escrevi para ele. Acabei de receber uma resposta (embora não de ele mesmo, mas de um colega de Princeton), então eu gostaria de compartilhar com todos vocês. Ele basicamente disse que não conhecia algoritmos usando três pilhas E as outras restrições impostas (como não usar avaliação lenta). 6 pilhas, como já sabemos, olhando para as respostas aqui. Acho que a pergunta ainda está aberta para encontrar um algoritmo (ou provar que um não pode ser encontrado). "
fonte
rotate
, parece que afront
lista é atribuída a ambosoldfront
ef
, e estes são modificados separadamente.Ok, isso é realmente difícil, e a única solução que eu consegui me lembrar é a solução Kirks para o teste Kobayashi Maru (de alguma forma enganada): A idéia é que usamos pilhas de pilhas (e usamos isso para modelar uma lista ) Eu chamo as operações en / dequeue e push e pop, então obtemos:
(StackX = StackY não é uma cópia do conteúdo, apenas uma cópia da referência. É apenas para descrevê-lo facilmente. Você também pode usar uma matriz de 3 pilhas e acessá-las via índice; aí, você apenas alteraria o valor da variável index ) Tudo está em O (1) em termos de operação de pilha.
E sim, eu sei que é discutível, porque implicamos mais de três pilhas, mas talvez isso dê a outras pessoas boas idéias.
EDIT: Exemplo de explicação:
Eu tentei aqui com um pouco de arte ASCII para mostrar o Stack1.
Cada elemento é agrupado em uma única pilha de elementos (portanto, temos apenas uma pilha segura de pilhas).
Você vê que, para remover, primeiro exibimos o primeiro elemento (a pilha que contém os elementos 1 e 2). Em seguida, pop o próximo elemento e desembrulhe lá o 1. Depois dizemos que a primeira pilha exibida agora é o nosso novo Stack1. Para falar um pouco mais funcional - estas são listas implementadas por pilhas de 2 elementos em que o elemento superior é cdr e o primeiro / abaixo do elemento superior é car . Os outros 2 estão ajudando pilhas.
O mais complicado é a inserção, pois você precisa de alguma forma mergulhar profundamente nas pilhas aninhadas para adicionar outro elemento. É por isso que o Stack2 está lá. Stack2 é sempre a pilha mais interna. A adição é apenas inserir um elemento e depois inserir um novo Stack2 (e é por isso que não podemos tocar no Stack2 em nossa operação de desenfileiramento).
fonte
Vou tentar uma prova para mostrar que isso não pode ser feito.
Suponha que exista uma fila Q que seja simulada por 3 pilhas, A, B e C.
Asserções
ASRT0: = Além disso, suponha que Q possa simular operações {fila, desenfileiramento} em O (1). Isso significa que existe uma sequência específica de push / pops de pilha para cada operação de fila / desenfileiramento a ser simulada.
Sem perda de generalidade, assuma que as operações da fila são determinísticas.
Permita que os elementos enfileirados em Q sejam numerados 1, 2, ..., com base em sua ordem de fila, com o primeiro elemento enfileirado em Q definido como 1, o segundo como 2 e assim por diante.
Definir
Q(0) :=
O estado de Q quando existem 0 elementos em Q (e, portanto, 0 elementos em A, B e C)Q(1) :=
O estado de Q (e A, B e C) após uma operação na fila emQ(0)
Q(n) :=
O estado de Q (e A, B e C) após n operações na fila emQ(0)
Definir
|Q(n)| :=
o número de elementos emQ(n)
(portanto|Q(n)| = n
)A(n) :=
o estado da pilha A quando o estado de Q éQ(n)
|A(n)| :=
o número de elementos emA(n)
E definições semelhantes para as pilhas B e C.
Trivialmente,
|Q(n)|
é obviamente ilimitado em n.Portanto, pelo menos um de
|A(n)|
,|B(n)|
ou|C(n)|
é ilimitado em n.WLOG1
, suponha que a pilha A seja ilimitada e as pilhas B e C sejam limitadas.Defina *
B_u :=
um limite superior de B *C_u :=
um limite superior de C *K := B_u + C_u + 1
WLOG2
, para um n tal que|A(n)| > K
, selecione K elementos deQ(n)
. Suponha que um desses elementos esteja emA(n + x)
, para todosx >= 0
, ou seja, o elemento esteja sempre na pilha A, não importa quantas operações de fila sejam realizadas.X :=
esse elementoEntão podemos definir
Abv(n) :=
o número de itens na pilhaA(n)
que está acima de XBlo(n) :=
o número de elementos na pilhaA(n)
abaixo de X| A (n) | = Abv (n) + Blo (n)
ASRT1 :=
O número de pops necessários para remover a fila de XQ(n)
é pelo menosAbv(n)
De (
ASRT0
) e (ASRT1
),ASRT2 := Abv(n)
deve ser delimitado.Se
Abv(n)
for ilimitado, se forem necessários 20 desenfileiramentos para desenfileirar X deQ(n)
, será necessário pelo menosAbv(n)/20
pops. O que é ilimitado. 20 pode ser qualquer constante.Portanto,
deve ser ilimitado.
WLOG3
, podemos selecionar os elementos K na parte inferior deA(n)
e um deles éA(n + x)
para todosx >= 0
X(n) :=
esse elemento, para qualquer dado nSempre que um elemento é enfileirado em
Q(n)
...WLOG4
, suponha que B e C já estejam preenchidos nos limites superiores. Suponha que o limite superior dos elementos acimaX(n)
tenha sido atingido. Então, um novo elemento entra em A.WLOG5
, suponha que, como resultado, o novo elemento deve entrar abaixoX(n)
.ASRT5 :=
O número de pops necessários para colocar um elemento abaixoX(n) >= Abv(X(n))
De
(ASRT4)
,Abv(n)
é ilimitado em n.Portanto, o número de pops necessários para colocar um elemento abaixo
X(n)
é ilimitado.Isso contradiz
ASRT1
, portanto, não é possível simular umaO(1)
fila com 3 pilhas.Ou seja,
Pelo menos 1 pilha deve ser ilimitada.
Para um elemento que permanece nessa pilha, o número de elementos acima dele deve ser limitado ou a operação de desenfileiramento para remover esse elemento será ilimitada.
No entanto, se o número de elementos acima dele for limitado, ele atingirá um limite. Em algum momento, um novo elemento deve entrar abaixo dele.
Como sempre podemos escolher o elemento antigo dentre um dos poucos elementos mais baixos dessa pilha, pode haver um número ilimitado de elementos acima dele (com base no tamanho ilimitado da pilha ilimitada).
Para inserir um novo elemento abaixo dele, como há um número ilimitado de elementos acima dele, é necessário um número ilimitado de pops para colocar o novo elemento abaixo dele.
E assim a contradição.
Existem 5 declarações WLOG (sem perda de generalidade). Em certo sentido, eles podem ser intuitivamente entendidos como verdadeiros (mas, como são 5, pode levar algum tempo). A prova formal de que nenhuma generalidade é perdida pode ser obtida, mas é extremamente longa. Eles são omitidos.
Admito que essa omissão possa deixar as declarações do WLOG em questão. Com a paranóia de um programador quanto a erros, verifique as instruções do WLOG, se desejar.
A terceira pilha também é irrelevante. O que importa é que há um conjunto de pilhas limitadas e um conjunto de pilhas ilimitadas. O mínimo necessário para um exemplo é 2 pilhas. O número de pilhas deve ser, obviamente, finito.
Por fim, se eu estiver certo de que não há provas, deve haver uma prova indutiva mais fácil disponível. Provavelmente, com base no que acontece após cada fila (acompanhe como isso afeta o pior caso de desenfileiramento, considerando o conjunto de todos os elementos na fila).
fonte
Nota: Isso pretende ser um comentário para a implementação funcional de filas em tempo real (pior caso de tempo constante) com listas vinculadas individualmente. Não posso adicionar comentários devido à reputação, mas será bom se alguém puder mudar isso para um comentário anexado à resposta por antti.huima. Então, novamente, é um pouco longo para um comentário.
@ antti.huima: Listas vinculadas não são iguais a uma pilha.
s1 = (1 2 3 4) --- uma lista vinculada com 4 nós, cada um apontando para o da direita e mantendo os valores 1, 2, 3 e 4
s2 = popped (s1) --- s2 agora (2 3 4)
Nesse ponto, s2 é equivalente a popped (s1), que se comporta como uma pilha. No entanto, s1 ainda está disponível para referência!
Ainda podemos espiar s1 para obter 1, enquanto em uma implementação de pilha adequada, o elemento 1 desapareceu de s1!
O que isto significa?
As listas vinculadas adicionais criadas agora servem cada uma como referência / ponteiro! Um número finito de pilhas não pode fazer isso.
Pelo que vejo nos documentos / código, todos os algoritmos fazem uso dessa propriedade de listas vinculadas para reter referências.
Edit: Estou me referindo apenas aos algoritmos de lista vinculada 2 e 3 que fazem uso dessa propriedade de listas vinculadas, como as li primeiro (elas pareciam mais simples). Isso não pretende mostrar que eles são ou não são aplicáveis, apenas para explicar que as listas vinculadas não são necessariamente idênticas. Vou ler aquele com 6 quando estiver livre. @ Welbog: Obrigado pela correção.
A preguiça também pode simular a funcionalidade do ponteiro de maneiras semelhantes.
O uso da lista vinculada resolve um problema diferente. Essa estratégia pode ser usada para implementar filas em tempo real no Lisp (ou pelo menos Lisps que insistem em criar tudo, a partir de listas vinculadas): Consulte "Operações da fila em tempo real no Pure Lisp" (vinculado aos links do antti.huima). Também é uma boa maneira de projetar listas imutáveis com tempo de operação O (1) e estruturas compartilhadas (imutáveis).
fonte
java.util.Stack
objetos. O único local em que esse recurso é usado é uma otimização que permite que pilhas imutáveis sejam "duplicadas" em tempo constante, que pilhas Java básicas não podem fazer (mas que podem ser implementadas em Java), pois são estruturas mutáveis.Você pode fazer isso em tempo constante amortizado com duas pilhas:
Adicionar
O(1)
e remover éO(1)
se o lado que você deseja retirar não estiver vazio eO(n)
caso contrário (divida a outra pilha em duas).O truque é ver que a
O(n)
operação só será feita todaO(n)
vez (se você dividir, por exemplo, ao meio). Portanto, o tempo médio para uma operação éO(1)+O(n)/O(n) = O(1)
.Embora isso possa parecer um problema, se você estiver usando uma linguagem imperativa com uma pilha baseada em array (mais rápida), você só terá amortizado o tempo constante de qualquer maneira.
fonte