Todo tipo de dados se resume a nós com ponteiros?

21

Uma matriz ou vetor é apenas uma sequência de valores. Eles certamente podem ser implementados com uma lista vinculada. Este é apenas um monte de nós com ponteiros para o próximo nó.

Pilhas e filas são dois tipos de dados abstratos comumente ensinados nos cursos Intro CS. Em algum lugar da turma, os alunos geralmente precisam implementar pilhas e filas usando uma lista vinculada como estrutura de dados subjacente, o que significa que estamos de volta à mesma idéia de "coleção de nós".

As filas prioritárias podem ser criadas usando um Heap. Um heap pode ser pensado como uma árvore com o valor mínimo na raiz. Árvores de todos os tipos, incluindo BSTs, AVL e pilhas, podem ser consideradas como uma coleção de nós conectados por arestas. Esses nós são vinculados juntos onde um nó aponta para outro.

Parece que todo conceito de dados sempre pode se resumir a apenas nós com ponteiros para outro nó apropriado. Isso esta certo? Se é simples assim, por que os livros didáticos não explicam que os dados são apenas um monte de nós com ponteiros? Como vamos dos nós para o código binário?

derekchen14
fonte
5
A estrutura de dados fundamental a que você está se referindo é chamada de "célula de contras"; você pode criar qualquer estrutura de dados que desejar a partir deles. Se você quiser saber por que um determinado autor de livro não escolheu explicar as células contras, pergunte a esse autor por que ele fez essa escolha. Ir da descrição de um arranjo de nós para o código binário é chamado "compilação" e é a tarefa de um "compilador".
Eric Lippert
18
Você também pode argumentar que todas as estruturas de dados se resumem a uma matriz. Afinal, todos eles acabam na memória, que é apenas uma matriz muito grande.
precisa saber é o seguinte
10
Você não pode implementar uma matriz usando uma lista vinculada se quiser continuar indexando . O(1)
precisa saber é o seguinte
5
Desculpe, mas falar de "nós e ponteiros" significa que você já sucumbiu a comer quiche. " Como todos os programadores reais sabem, a única estrutura de dados útil é a matriz. Strings, listas, estruturas, conjuntos - todos esses são casos especiais de matrizes e podem ser tratados dessa maneira com a mesma facilidade, sem alterar a linguagem de programação de todos os tipos. . de complicações "Ref: "Os programadores reais não usar Pascal", de web.mit.edu/humor/Computers/real.programmers
alephzero
3
... mas, mais sério, o importante sobre estruturas de dados é o que você pode fazer com elas , não como elas são implementadas. No século 21, implementá-los você mesmo é apenas um exercício de programação - e para os educadores preguiçosos, o fato de que esses exercícios são fáceis de avaliar supera o fato de que eles são, na melhor das hipóteses, inúteis e, na pior das hipóteses, positivamente prejudiciais se incentivar os alunos a pensar que " reinventar as rodas "é uma atividade útil na programação do mundo real.
alephzero

Respostas:

14

Bem, é basicamente para isso que todas as estruturas de dados se resumem. Dados com conexões. Os nós são todos artificiais - eles realmente não existem fisicamente. É aqui que entra a parte binária. Você deve criar algumas estruturas de dados em C ++ e verificar onde seus objetos ficam na memória. Pode ser muito interessante aprender sobre como os dados são dispostos na memória.

A principal razão para tantas estruturas diferentes é que todas elas se especializam em uma coisa ou outra. Por exemplo, geralmente é mais rápido percorrer um vetor em vez de uma lista vinculada, devido à maneira como as páginas são extraídas da memória. Uma lista vinculada é melhor para armazenar tipos de tamanhos maiores, pois os vetores devem alocar espaço extra para slots não utilizados (isso é necessário no design de um vetor).

Como uma observação lateral, uma estrutura de dados interessante que você pode querer examinar é uma Tabela Hash. Ele não segue completamente o sistema de nós e ponteiros que você está descrevendo.

TL; DR: Contêineres basicamente todos os nós e ponteiros, mas têm usos muito específicos e são melhores para algo e pior para outros.

user3853544
fonte
1
Meu argumento é que a maioria dos dados pode realmente ser representada como um monte de nós com ponteiros. No entanto, não são porque (a) no nível físico, não é o que acontece e (b) no nível conceitual, pensar nos valores como uma lista vinculada não é tão útil para raciocinar sobre os dados subjacentes. De qualquer maneira, são apenas abstrações para simplificar nosso pensamento; assim, é melhor escolher a melhor abstração para uma situação, mesmo que outra possa funcionar.
precisa saber é o seguinte
13

Parece que todo conceito de dados sempre pode se resumir a apenas nós com ponteiros para outro nó apropriado.

Oh, querido não. Você está me machucando.

Como tentei explicar em outro lugar (" Qual é a diferença entre uma árvore de pesquisa binária e uma pilha binária? "), Mesmo para uma estrutura de dados fixa, existem vários níveis para entendê-la.

Como a fila de prioridade mencionada, se você quiser usá-la apenas, é um tipo de dado abstrato. Você o usa sabendo que tipo de objetos ele armazena e que perguntas você pode fazer. São mais estruturas de dados como um pacote de itens. No próximo nível, sua famosa implementação, a pilha binária, pode ser entendida como uma árvore binária, mas o último nível é por razões de eficiência implementadas como uma matriz. Não há nós e ponteiros lá.

E também para gráficos, por exemplo, que certamente se parecem com nós e ponteiros (arestas), você tem duas representações básicas, matriz de adjacência e listas de adjacência. Nem todos os ponteiros, imagino.

Ao realmente tentar entender as estruturas de dados, você deve estudar seus pontos positivos e negativos. Às vezes, uma representação usa uma matriz para eficiência (espaço ou tempo); às vezes, existem indicadores (para flexibilidade). Isso ocorre mesmo quando você tem boas implementações "enlatadas", como o C ++ STL, porque também é possível escolher algumas vezes as representações subjacentes. Há sempre uma troca por lá.

Hendrik Jan
fonte
10

Vamos fazer uma analogia com a matemática. Considere a seguinte frase: " é uma função contínua". Funções são realmente definidas em termos de relações, que são definidas em termos de conjuntos. O conjunto de números reais é o único campo completo e totalmente ordenado: todos esses conceitos têm definições em termos mais simples. Para falar sobre continuidade, você precisa do conceito de vizinhança, definido em relação a uma topologia ... e assim por diante, até os axiomas do ZFC.f:RR

Ninguém espera que você diga tudo isso apenas para definir uma função contínua, caso contrário, ninguém seria capaz de realizar nenhum trabalho. Nós apenas "confiamos" que alguém fez o trabalho chato para nós.

Toda estrutura de dados que você pode imaginar se resume aos objetos básicos com os quais o modelo computacional subjacente lida, números inteiros em algum registro, se você usa uma máquina de acesso aleatório, ou símbolos em alguma fita, se você usa uma máquina de Turing.

Usamos abstrações porque elas libertam nossa mente de assuntos triviais, permitindo-nos falar sobre problemas mais complexos. É perfeitamente razoável apenas "confiar" que essas estruturas funcionam: mergulhar em cada detalhe é - a menos que você tenha uma razão específica para fazê-lo - um exercício fútil que não acrescenta nada ao seu argumento.

ordenação rápida
fonte
10

Aqui está um contra-exemplo: no cálculo λ, todo tipo de dados se resume a funções. O cálculo λ não possui nós ou ponteiros, a única coisa que possui são funções; portanto, tudo deve ser implementado usando funções.

Este é um exemplo de codificação de booleanos como funções, escritas em ECMAScript:

const T   = (thn, _  ) => thn,
      F   = (_  , els) => els,
      or  = (a  , b  ) => a(a, b),
      and = (a  , b  ) => a(b, a),
      not = a          => a(F, T),
      xor = (a  , b  ) => a(not(b), b),
      iff = (cnd, thn, els) => cnd(thn, els)();

E esta é uma lista de contras:

const cons = (hd, tl) => which => which(hd, tl),
      first  = list => list(T),
      rest   = list => list(F);

Números naturais podem ser implementados como funções do iterador.

Um conjunto é a mesma coisa que sua função característica (isto é, o containsmétodo).

Observe que a codificação de booleanos da igreja acima é realmente como os condicionais são implementados em linguagens OO como Smalltalk, que não possuem booleanos, condicionais ou loops como a linguagem constrói e os implementam apenas como um recurso de biblioteca. Um exemplo em Scala:

sealed abstract trait Boolean {
  def apply[T, U <: T, V <: T](thn: => U)(els: => V): T
  def(other: => Boolean): Boolean
  def(other: => Boolean): Boolean
  val ¬ : Boolean

  final val unary_! = ¬
  final def &(other: => Boolean) =(other)
  final def |(other: => Boolean) =(other)
}

case object True extends Boolean {
  override def apply[T, U <: T, V <: T](thn: => U)(els: => V): U = thn
  override def(other: => Boolean) = other
  override def(other: => Boolean): this.type = this
  override final val ¬ = False
}

case object False extends Boolean {
  override def apply[T, U <: T, V <: T](thn: => U)(els: => V): V = els
  override def(other: => Boolean): this.type = this
  override def(other: => Boolean) = other
  override final val ¬ = True
}

object BooleanExtension {
  import scala.language.implicitConversions
  implicit def boolean2Boolean(b: => scala.Boolean) = if (b) True else False
}

import BooleanExtension._

(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3
Jörg W Mittag
fonte
2
@ Hamsteriffic: Tente o seguinte: é assim que os condicionais são implementados em linguagens OO como Smalltalk. O Smalltalk não possui booleanos, condicionais ou loops como uma construção de linguagem. Tudo isso é puramente implementado como bibliotecas. Mente ainda não explodiu? William Cook aponta algo que deveria ter sido óbvio há muito tempo, mas não foi realmente percebido: como OO é uma abstração comportamental, e abstração comportamental é o único tipo de abstração que existe no cálculo-λ, segue-se que todos os programas escritos em O cálculo λ é, necessariamente, OO. Portanto, o λ-cálculo é o mais antigo e…
Jörg W Mittag
... a linguagem OO mais pura!
Jörg W Mittag
1
Um dia mau com Smalltalk bate um bom dia com C ++ :-)
Bob Jarvis - Reintegrar Monica
@ JörgWMittag Eu não acho que sua conclusão se segue da sua suposição, não acho que sua suposição seja verdadeira, e definitivamente não acho que sua conclusão seja verdadeira.
Route de milhas
4

Muitas estruturas de dados (a maioria?) São construídas com nós e ponteiros. Matrizes são outro elemento crítico de algumas estruturas de dados.

Por fim, toda estrutura de dados é apenas um monte de palavras na memória ou apenas um monte de bits. É como eles são estruturados e como nós os interpretamos e usamos.

DW
fonte
2
Por fim, os bits são um monte de sinais elétricos em um fio, ou sinais de luz em um cabo de fibra óptica, ou partículas magnetizadas especificamente em um prato, ou ondas de rádio de comprimento de onda específico, ou, ou, ou. Portanto, a questão é: até que ponto você quer ir? :)
Caractere curinga
2

A implementação de estruturas de dados sempre se resume a nós e ponteiros, sim.

Mas por que parar aí? A implementação de nós e ponteiros se resume a bits.

A implementação de bits se resume a sinais elétricos, armazenamento magnético, talvez cabos de fibra ótica etc. (em uma palavra, física).

Esta é a reductio ad absurdum da afirmação: "Todas as estruturas de dados se resumem a nós e ponteiros". É verdade - mas está relacionado apenas à implementação.


Chris Date é muito capaz de diferenciar entre implementação e modelo , embora seu ensaio seja destinado a bancos de dados em particular.

Podemos ir um pouco mais longe se percebermos que não existe uma única linha divisória entre modelo e implementação. Isso é semelhante (se não idêntico) ao conceito de "camadas de abstração".

Em uma determinada camada de abstração, as camadas "abaixo" de você (as camadas nas quais você está construindo) são meros "detalhes de implementação" para a abstração ou modelo que você está abordando.

No entanto, as próprias camadas inferiores da abstração têm detalhes de implementação.

Se você lê um manual de um software, está aprendendo sobre a camada de abstração "apresentada" por esse software, na qual é possível criar suas próprias abstrações (ou apenas executar ações como enviar mensagens).

Se você aprender os detalhes de implementação do software, aprenderá como os criadores sustentaram as abstrações que eles criaram. Os "detalhes da implementação" podem incluir estruturas e algoritmos de dados, entre outras coisas.

No entanto, você não consideraria a medição de tensão parte dos "detalhes de implementação" de qualquer software específico, mesmo que isso implique como "bits" e "bytes" e "armazenamento" realmente funcionam no computador físico.

Em resumo, as estruturas de dados são uma camada de abstração para raciocinar e implementar algoritmos e software. O fato de que essa camada de abstração é criada com detalhes de implementação de nível inferior, como nós e ponteiros, é verdadeiro, mas irrelevante dentro da camada de abstração.


Uma grande parte de realmente entender um sistema é compreender como as camadas de abstração se encaixam. Portanto, é importante entender como as estruturas de dados são implementadas. Mas o fato de que eles são implementadas, não significa que a abstração de estruturas de dados não existe.

Curinga
fonte
2

Uma matriz ou vetor é apenas uma sequência de valores. Eles certamente podem ser implementados com uma lista vinculada. Este é apenas um monte de nós com ponteiros para o próximo nó.

Uma matriz ou um vetor pode ser implementado com uma lista vinculada, mas quase nunca deveria ser.

nnΘ(n)Θ(registron)Θ(1) uma matriz real(isto é, um bloco seqüencial de memória de acesso aleatório). Além disso, na CPU, acessar a matriz real é muito mais simples de implementar e mais rápido de executar, além de armazenar menos memória, devido à falta de espaço nos ponteiros entre nós separados.

Θ(n)Θ(1)Θ(1)em média, ao custo de, no máximo, um fator constante de memória extra, apenas arredondando o tamanho real alocado da matriz até, por exemplo, a potência mais próxima de 2. Mas se você precisar fazer muitas inserções e / ou remoções de No meio da sua lista, uma matriz física pode não ser a melhor implementação para sua estrutura de dados. Com bastante frequência, porém, você pode substituir inserções e remoções por swaps, que são baratos.

Se você ampliar um pouco o seu escopo, para incluir matrizes físicas contíguas em sua caixa de ferramentas, quase todas as estruturas práticas de dados poderão ser implementadas com aquelas junto com nós e ponteiros.

Θ(1)operação de reversão). Na prática, porém, esses recursos raramente são úteis o suficiente para superar suas desvantagens, que incluem complexidade extra de implementação e incompatibilidade com os padrão deesquemas de coleta de lixo .

Ilmari Karonen
fonte
1

Se é simples assim, por que os livros didáticos não explicam que os dados são apenas um monte de nós com ponteiros?

Porque não é isso que "dados" significa. Você está confundindo idéias abstratas com implementações. "Dados" é uma idéia altamente abstrata: é apenas outro nome para "informação". Um monte de nós vinculados com ponteiros (também conhecido como "estrutura de dados vinculada") é uma idéia muito mais concreta: é uma maneira específica de representar e organizar informações.

Algumas abstrações de dados se prestam muito bem a implementações "vinculadas". Não existem muitas maneiras boas de implementar a natureza ramificada de uma árvore totalmente geral sem o uso de nós e ponteiros explícitos (ou algum isomorfismo de nós e ponteiros.) Mas existem outras abstrações que você nunca implementaria usando nós e ponteiros. Números de ponto flutuante vêm à mente.

Pilhas e filas caem em algum lugar no meio. Há momentos em que faz muito sentido executar uma implementação vinculada de uma pilha. Outras vezes, faz muito mais sentido usar uma matriz e um único "ponteiro de pilha".

Salomão Lento
fonte