“Ciência da Computação: é a área que estuda os fundamentos teóricos e práticos da computação, focando no desenvolvimento de softwares e sistemas, algoritmos, segurança da informação, inteligência artificial e redes de computadores para resolver problemas complexos.”

Essa é a definição encontrada no Google ao pesquisar “Ciência da Computação”. Contudo, a parte mais importante dessa definição, e que realmente carrega o foco da área, está na resolução de problemas. Afinal, todo o restante é produzido a fim de resolver problemas ou otimizar soluções existentes em nossas vidas. Nessa perspectiva, podemos interpretar que os problemas a serem resolvidos são o que chamamos de inputs e, por meio de linguagens e tecnologias desenvolvidas pelos cientistas da computação, obtemos soluções, os nossos outputs.

Neste post, vamos focar em entender como esses computadores, que há anos ajudam a nossa sociedade a evoluir cada vez mais, funcionam.

O conteúdo aqui apresentado tem forte influência do curso CC50 de Harvard, especialmente sua distribuição com legendas e anotações em português, da Fundação Estudar.

Sumário


Representação de Dados

Nós, humanos, utilizamos diferentes formas de contabilizar dados, dependendo do contexto. Um exemplo são os nossos próprios dedos, que compõem um sistema unário, onde cada dígito (neste caso, dedo) representa o valor 1. Sendo assim, com as duas mãos só podemos contar até 10, já que possuímos apenas 10 dedos.

Contudo, os computadores trabalham internamente com um sistema de contagem denominado binário, ou seja, sequências de 0s e 1s (conhecidos como bits). Assim, todo e qualquer dado precisa ser convertido para esse sistema para que o computador consiga armazená-lo em sua memória.

Dessa forma, a depender do arquivo que estamos manipulando, ele terá um tamanho específico, que é definido como uma cadeia desses bits. Assim, chegamos à unidade de bytes, onde 1 byte corresponde a uma sequência de 8 bits, e progressivamente essa cadeia vai aumentando para arquivos cada vez maiores, com os kbytes, megabytes, gigabytes…

Em termos computacionais, a eletricidade é a vida de todo o processamento dos computadores. Essa representação dos bits em 0s e 1s pode ser entendida como desligado e ligado, e a ordem na qual esses “interruptores” estão ligados e desligados ilustra os diferentes tipos de valores.

Para que o computador consiga trabalhar com as informações de forma padronizada, precisamos converter os diferentes tipos de dados que ele precisa armazenar. Vamos detalhar cada uma dessas conversões, começando pelo tipo de dado numérico.

Números

O sistema decimal, utilizado em nossos cálculos matemáticos, nos permite contar de 0 a 9 (10 dígitos por algarismo). A cada posição do algarismo na representação de um número, há um avanço em potências de base 10. Ou seja, o número 100 representa \((1 . 10^2) + (0 . 10^1) + (0 . 10^0) = 100\). Embora essa forma de pensar se torne automática para nós, é assim que a representação de todos os demais números desse sistema é feita.

Quando se trata dos computadores, como dito anteriormente, eles utilizam um sistema binário, ou seja, contam apenas de 0 a 1, e a cada posição desses algarismos na representação de um número, o avanço ocorre em potências de base 2. Ou seja, o número decimal 12 seria representado em binário como 1100, de forma que \((1 . 2^3) + (1 . 2^2) + (0 . 2^1) + (0 . 2^0) = 12\).

Essa conversão, em termos matemáticos, pode ser realizada dividindo o número decimal sucessivamente por 2 e obtendo o resto da divisão do último para o primeiro. Em seguida, unindo esses restos nessa ordem, teremos a representação em binário de um número inteiro.

Para números decimais, o processo é diferente, mas a ideia aqui é apenas entender como funciona o sistema binário.

Textos e Caracteres

Para a representação de textos, existem tabelas de codificação que possuem um mapeamento de cada caractere para um número em binário. Assim, após a identificação do computador de que esse dado é textual, ele utiliza um dos sistemas de mapeamento para registrar cada caractere e interpretá-lo através dos seus bits correspondentes. Os sistemas mais utilizados atualmente são:

ASCII (American Standard Code for Information Interchange): Utiliza 1 byte (8 bits) por caractere, permitindo 256 símbolos diferentes. Por exemplo, a letra ‘A’ é representida pelo número 65, que em binário é 01000001.

UTF-8: Sistema mais moderno que pode usar de 1 a 4 bytes por caractere, sendo compatível com ASCII e capaz de representar qualquer caractere Unicode. É o padrão mais utilizado atualmente na web. (Seu grande diferencial está no tamanho que pode ser utilizado por caractere, permitindo o processamento de caracteres especiais como emojis)

Imagens

As imagens digitais são compostas por pixels, onde cada pixel contém informações de cor em formato numérico. O sistema examina cada pixel individualmente, analisando valores que representam intensidade de cor e outras características.

Em imagens coloridas, utiliza-se frequentemente o sistema RGB (Red, Green, Blue), onde cada cor primária é representada por um valor numérico. Por exemplo, em imagens de 24 bits (3 bytes), cada canal de cor utiliza 8 bits, permitindo 256 níveis de intensidade por cor (isto é, 2 elevado a 8, com valores de 0 a 255).


Algoritmos e Tempo de Execução

Primeiramente, embora esteja muito relacionado, o termo algoritmo não é exclusivo da computação. Sua definição é uma sequência finita de instruções bem definidas para resolver um problema específico. Dessa forma, receitas culinárias, manuais de montagem e outras variadas ferramentas de instrução que têm como objetivo solucionar um problema são algoritmos. E entenda que a palavra “problema” não tem um valor negativo; significa apenas algo que precisa chegar a um determinado resultado.

Com isso em mente, dentro da computação os algoritmos são o que utilizamos para transformar os nossos inputs (problemas a serem resolvidos) em outputs (soluções). O diferencial é que, na linguagem do computador, precisamos ser extremamente precisos em nossas instruções, dada a interpretação direta e sem ambiguidade das máquinas.

Dentro da análise dessas instruções, é natural pensar que existem inúmeras maneiras de solucionar o mesmo problema, contudo algumas são mais eficientes que outras. E, a respeito dessa eficiência em termos computacionais, nos referimos principalmente à quantidade de memória e de tempo utilizados pela máquina ao executar esse algoritmo.

Complexidade Temporal

Falando sobre o tempo de execução de um algoritmo, temos a Complexidade Temporal, que é expressa através da notação Big O, relacionando como o tempo de execução cresce conforme aumenta o tamanho da entrada (n). Ou seja, pensando no problema de encontrar um número dentro de uma lista telefônica, a depender de quantos números temos nessa lista, um algoritmo de “olhar página por página” se tornaria cada vez mais lento à medida que as páginas aumentassem. Nesse caso, a nossa entrada (n) são as páginas dessa lista.

Porém, sabendo que essa lista está em ordem alfabética e possuindo o nome do número, podemos pensar em um algoritmo que faça essa busca de forma mais eficiente do que olhar cada página do livro. Por exemplo, olhando de 5 em 5 páginas e retrocedendo caso, por acidente, tenhamos passado a posição alfabética do nome procurado.

Outro algoritmo seria abrir a lista telefônica ao meio, decidir se nosso nome estará na metade esquerda ou na metade direita do livro (porque o livro está em ordem alfabética) e reduzir o tamanho do nosso problema pela metade. Podemos repetir isso até encontrar nosso nome, dividindo o problema pela metade a cada vez.

É importante compreender que a análise de eficiência relacionada a esses algoritmos considera a pior situação possível para alcançar o resultado. Ou seja, mesmo que o método de divisão pareça ineficiente se o nosso nome estiver na primeira página da lista, temos que pensar que se o nome estiver na última página da lista (a pior situação para o algoritmo de “olhar página por página”), com certeza o algoritmo de divisão será bem mais eficiente (também considerando a sua pior situação).

É nisso que se baseia a notação Big O e podemos classificá-la em:

  • O(1) - Tempo Constante: O algoritmo executa no mesmo tempo, independente do tamanho da entrada

  • O(log n) - Tempo Logarítmico: Muito eficiente

  • O(n) - Tempo Linear: O tempo cresce proporcionalmente à entrada

  • O(n²) - Tempo Quadrático: O tempo cresce quadraticamente

  • O(n!) - Tempo Fatorial: Muito ineficiente

De forma mais técnica, temos que os algoritmos de “página por página” e de “divisão pela metade” correspondem a:

  • Busca Linear [O(n)]: Precisa verificar cada elemento até encontrar o desejado, podendo examinar toda a lista no pior caso.

  • Busca Binária [O(log n)]: Em uma lista ordenada, elimina metade das possibilidades a cada comparação, sendo muito mais eficiente para grandes volumes de dados.

Importante mencionar que a notação Big O leva em consideração o termo de maior expoente, visto que ele domina o crescimento da função (entenda como o algoritmo) à medida que n cresce. Ou seja, o método de ler 1 página por vez e o de ler 5 páginas no final representam o mesmo algoritmo de “Busca Linear” e possuem a mesma complexidade temporal.


Conclusão

Por fim, relembrando os assuntos que discutimos nesta introdução à área de computação, temos:

  • A base: O bit é a menor unidade de informação, é a base sobre a qual toda a computação é construída. Tudo que vemos, ouvimos e interagimos no mundo digital — seja uma imagem de alta definição ou uma música — é, em sua essência, uma complexa combinação de 0s e 1s.

  • A “receita”: Mude o foco para os algoritmos. Se os bits são os ingredientes, os algoritmos são a receita. Eles são a sequência de passos lógicos que instruem o computador sobre como processar esses bits para realizar uma tarefa e precisam ser extremamente determinísticos em seus passos.

  • O “ingrediente secreto”: Introduza a ideia da complexidade temporal como a forma de avaliar a qualidade dessa receita. Não basta ter um algoritmo que funcione; ele precisa ser eficiente, especialmente quando lidamos com grandes volumes de dados. A complexidade de tempo nos dá uma métrica para entender se a nossa “receita” é rápida o suficiente para o que precisamos.

Não discutimos sobre Complexidade Espacial, mas é importante ter a noção de que nem sempre o que é mais eficiente em termos de complexidade temporal é realmente a melhor opção, visto que em alguns casos alcançar essa eficiência significa utilizar uma quantidade massiva de espaço. Ou seja, no final do dia, tudo é sobre encontrar o equilíbrio.

A ciência da computação, portanto, não é apenas sobre máquinas ou linguagens de programação. É uma área científica que nos ensina a pensar de forma lógica, a resolver problemas de forma eficaz e a criar soluções inteligentes para os desafios do mundo.