Arrays
Ler o milionésimo valor de um array leva um passo, mas pôr um valor novo na frente do mesmo array move um milhão de valores.
O que é uma estrutura de dados
Um programa que guarda uma lista de nomes, uma fila de trabalhos de impressão ou uma tabela de preços precisa armazenar esses dados em algum lugar, e precisa responder a perguntas sobre eles: qual é o terceiro item, este nome está na lista, qual trabalho vem depois? Uma estrutura de dados é um jeito de guardar dados junto com os métodos que respondem a essas perguntas. O curso 6.006 do MIT separa duas ideias aqui. A interface diz quais operações você pode pedir, como "pegar o item na posição i" ou "acrescentar um item no fim". A estrutura de dados é como essas operações são de fato executadas na memória. Uma interface pode ter várias estruturas de dados por trás, e elas podem diferir enormemente em velocidade.
Este curso conta passos, como o curso de algoritmos, para que duas estruturas possam ser comparadas sem cronômetro. Nesta unidade, um passo é ler ou escrever uma célula de memória. O laboratório abaixo conta os passos para você.
Um bloco de células, lado a lado
Um array guarda os seus valores num bloco contínuo de memória, cada valor numa célula do mesmo tamanho, numerada a partir de 0. É essa disposição que torna os arrays rápidos de ler. Se o bloco começa no endereço s e cada célula ocupa c bytes, então a[i] fica no endereço s + i × c. O computador calcula isso com uma multiplicação e uma adição e pula direto para lá, seja i igual a 3 ou a 3 000 000. As notas do MIT listam "get at(i)" e "set at(i, x)" de um array como Θ(1), o que quer dizer que o custo não cresce com o comprimento.
A mesma disposição deixa lentas as mudanças no meio. Suponha que o array guarde 7, 3, 9, 4, 6 e você queira o 1 na frente. Não há espaço na frente do 7, então todo valor precisa primeiro se mover uma célula para a direita, começando pelo último para nada ser sobrescrito. São 5 movimentos e 1 escrita: 6 passos. Remover o valor da frente é a imagem espelhada: ler o valor e depois mover os outros 4 valores uma célula para a esquerda. Inserir na posição i de um array de n valores custa n − i movimentos mais uma escrita, então a frente é o pior lugar e o fim é o melhor.
Arraste para girar. Role ou faça pinça para dar zoom.
Quando o bloco está cheio
Um bloco de memória tem tamanho fixo, então o que acontece quando você acrescenta um valor e todas as células estão ocupadas? O array precisa pedir um bloco novo, maior, e copiar todos os valores para lá antes de poder escrever o novo. No laboratório, fazer push no bloco cheio de 5 custa 5 cópias e 1 escrita. Se o array crescesse só uma célula de cada vez, todo push copiaria tudo, e n pushes custariam cerca de n²/2 cópias.
A solução descrita nas notas do MIT é crescer bastante de uma vez, por exemplo dobrando. Começando da capacidade 1, as cópias acontecem quando o array tem 1, 2, 4, 8 e 16 valores, então 17 pushes custam 1 + 2 + 4 + 8 + 16 = 31 cópias no total, menos de 2 por push. Um push isolado ainda pode ser lento, mas qualquer sequência longa de pushes dá, em média, um custo constante por push. Isso se chama tempo Θ(1) amortizado. Um array de JavaScript se comporta assim visto de fora: o MDN descreve os arrays como redimensionáveis, e push e pop trabalham no fim, que é a ponta barata.
| Operação com n valores | Passos no laboratório | Cresce como |
|---|---|---|
| Ler ou escrever a[i] | 1 | Θ(1) |
| Inserir na frente | n movimentos + 1 escrita | Θ(n) |
| Remover a frente | 1 leitura + (n − 1) movimentos | Θ(n) |
| Push no fim (com espaço) | 1 | Θ(1) |
| Push no fim (bloco cheio) | n cópias + 1 escrita | Θ(n) uma vez, Θ(1) amortizado |
const a = [7, 3, 9, 4, 6];
console.log(a[3]); // 4, um passo
a.push(8); // barato: o fim
a.unshift(1); // todo valor anda uma célula para a direita
console.log(a.join(" ")); // 1 7 3 9 4 6 8| Interface | A lista de operações que você pode pedir, como get at(i) ou insert last(x). |
| Estrutura de dados | Como essas operações são executadas na memória. |
| Array | Valores num bloco de células iguais; a[i] fica em início + i × tamanho da célula. |
| Array dinâmico | Um array que cresce copiando para um bloco maior, em geral com o dobro do tamanho. |
| Custo amortizado | O custo médio por operação numa sequência longa, mesmo que algumas operações isoladas sejam lentas. |
- Na contagem de passos desta unidade, quantos passos leva ler a[700000] de um array de 1 000 000 de valores?
- 1
- 20
- 700,000
- 1,000,000
- Um array guarda 7, 3, 9, 4, 6 e ainda tem espaço. Quantos passos leva inserir um valor na frente (movimentos mais a escrita)?
- Mesmo array de 5 valores: quantos passos leva remover o valor da frente (1 leitura mais os movimentos)?
- Inserir na posição 2 de um array de 5 valores (posições 0 a 4) com espaço: quantos passos?
- Um array que dobra guarda 5 valores e a capacidade dele é 5. Você faz push de mais um valor. Quantos passos esse push leva?
- Depois desse push, qual é a capacidade?
- Começando da capacidade 1 e dobrando, quantas cópias 17 pushes fazem no total?
- Por que o push num array que dobra é chamado de Θ(1) amortizado?
- Cada push leva exatamente um passo
- As cópias são raras, então o total de n pushes fica abaixo de uma constante vezes n
- O computador copia em segundo plano de graça
- O JavaScript nunca copia arrays
- Nos termos do MIT 6.006, "insert last(x) e delete last()" é
- uma estrutura de dados
- uma interface
- um endereço de memória
- um método de ordenação
- Um array tem comprimento n e conta a partir de 0. Qual é o índice do último valor?
- n
- n − 1
- n + 1
- 1
- Uma interface lista operações; uma estrutura de dados é como elas são feitas.
- Um array guarda os valores num bloco, então a[i] é um passo.
- Inserir ou remover perto da frente move cerca de n valores.
- Um array que dobra torna o push Θ(1) amortizado: 17 pushes a partir da capacidade 1 copiam 31 valores.
Fontes deste curso (11)
- MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
- lecture notes 2 Data Structures (interface vs data structure, Sequence and Set interfaces, stack = insert_last/delete_last, queue = insert_last/delete_first, array, linked list, dynamic array, amortized Θ(1) append)
- 4 Hashing (chaining, pigeonhole collisions, chain length n/m)
- 6 Binary Trees Part 1 (height, traversal order, O(h) operations) and 7 Binary Trees Part 2: AVL (Adelson-Velsky and Landis, 1962) at ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes
- MDN Web Docs, Array.prototype.shift (shifts all values left by 1)
- Array.prototype.unshift, Map (insertion order, Map vs Object, spec requires sublinear average access) and Set (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects)
- ECMAScript 2025 Language Specification (tc39.es/ecma262)
- 24.1 Map Objects and 24.2 Set Objects ("must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements")
- Array.prototype.shift steps (Repeat, while k < len: move element k to k - 1)
- D. E. Knuth, The Art of Computer Programming vol. 3 (2nd ed. 1998) section 6.4 History (H. P. Luhn, IBM internal memorandum January 1953, hashing with chaining; digit pairs added mod 10, 31415926 -> 4548) via gwern.net copy, and IEEE Spectrum, "Hans Peter Luhn and the Birth of the Hashing Algorithm" (2018)
- "A brief history of the stack", SIGCIS (sigcis.org/files/A brief history.pdf: Bauer and Samelson applied for the patent in 1957, US 3,047,228 granted July 31, 1962; IPL list processing by Newell, Simon and Shaw at RAND 1956) and Wikipedia, Information Processing Language (Newell, Shaw, Simon, RAND and Carnegie Institute of Technology, about 1956).
Data de verificação: 2026-10-18