“Algoritmos Grocking” ou uma introdução indolor aos algoritmos
Introdução
Quase todas as vagas de nível júnior possuem requisitos como “conhecimento de estruturas de dados e algoritmos”. Para quem tem formação especializada, os algoritmos estão incluídos no curso geral e não devem haver problemas. Mas e se o desenvolvimento vier de outras estepes? Resta apenas aprender por conta própria. Há uma resposta para a pergunta “quem é o culpado”, mas para a pergunta “o que fazer” a resposta deve ser buscada. Vamos procurar nos livros. E eu quero falar sobre um.Algoritmos Grok
Entre todos os trabalhos, me deparei com um livro como “Grocking Algorithms”. Você pode saber mais aqui: " O livro "Crescendo Algoritmos. Um guia ilustrado para programadores e curiosos ." Notei o livro há muito tempo, mas no ozônio custava 680 rublos. Caro ou barato - cada um decide por si. Já estou comprando o segundo livro no Avito pela metade do preço. Então eu o encontrei em São Petersburgo, comprei e fui grocar. Foi isso que decidi compartilhar com vocês. Sim, não há código Java no livro, mas há... outro código, mas falaremos mais sobre isso mais tarde.Introdução aos algoritmos (classificação por seleção)
Assim, numa forma fácil de narração, chegamos à primeira ordenação em nossa performance. Esta é a classificação por seleção. A sua essência é percorrer os elementos da esquerda para a direita (do elemento 0 ao último) e procurar o maior entre os restantes elementos. Se o encontrarmos, trocamos o elemento em que estamos agora e o maior elemento. A maneira mais simples de pensar primeiro em um array é: [5, 3, 6, 2, 10]. Pegue um pedaço de papel, uma caneta (a forma mais simples e econômica) e imagine como temos uma borda esquerda, um índice atual (ou borda direita) e um índice do elemento mínimo. E como trabalhamos com isso. Por exemplo:
def selectionSort(array):
for left in range(0, len(array)):
minIndex = left
for right in range (left+1, len(array)):
if array[right] < array[minIndex]:
minIndex = right
if minIndex != left:
temp = array[left]
array[left] = array[minIndex]
array[minIndex] = temp
return array
print(selectionSort([5, 3, 6, 2, 10]))
Agora vamos apresentá-lo na forma de código Java:
public static void selectionSort(int[] array) {
for (int left = 0; left < array.length; left++) {
int minIndex = left;
for (int right = left+1; right < array.length; right++) {
if (array[right] < array[minIndex]) {
minIndex = right;
}
}
if (minIndex != left) {
int temp = array[left];
array[left] = array[minIndex];
array[minIndex] = temp;
}
}
}
Como você pode ver, o código é quase o mesmo. O primeiro código é um exemplo do livro. A segunda é minha execução gratuita em código Java.
Recursão
A seguir, somos informados de que existe algo chamado recursão. Em primeiro lugar, existe o problema de um agricultor que possui um campo de tamanho AxB. É necessário dividir este campo em “quadrados” iguais. E depois disso o Algoritmo de Euclides é mencionado. O que não gosto é que eles não tentaram escrever seu código. Mas o algoritmo de Euclides é simples e eficaz:
def euclidean(a, b):
if a == 0 : return b
if b == 0 : return a
return euclidean (b, a % b)
Vamos escrever o mesmo código em Java. Se desejar, podemos usar o compilador online :
public static int euclid(int a, int b) {
if (a == 0) return b;
if (b == 0) return a;
return euclid(b, a%b);
}
Factorial também foi mencionado no início do livro. O fatorial de um número n (n!) é o produto dos números de 1 a n. Por que fazer isso? Há uma aplicação prática aqui. Se tivermos n objetos (por exemplo, n cidades), então poderemos fazer n deles! Combinações. Você pode ler mais sobre recursão aqui: “ Recursão. Tarefas de treinamento ”. Comparação de abordagens iterativas e recursivas: “ Recursão ”.
Ordenação rápida
A classificação rápida é um algoritmo bastante interessante. O livro não dá muita atenção a ele. Além disso, o código é fornecido apenas para o pior caso, quando o primeiro elemento é selecionado. No entanto, talvez para um primeiro contato este exemplo seja mais fácil de lembrar. E é melhor escrever um quicksort ruim do que não escrever nenhum. Aqui está um exemplo do livro:
def quicksort(array):
if len(array) < 2:
return array
else:
pivot = array[0]
less = [i for i in array[1:] if i <= pivot]
greater = [i for i in array[1:] if i > pivot]
return quicksort(less) + [pivot] + quicksort(greater)
Tudo aqui é super simples. Se tivermos um array de 0 ou 1 elemento, não há necessidade de classificá-lo. Se for maior, pegamos o primeiro elemento do array e o consideramos o “elemento pivô”. Criamos 2 novos arrays - um contém elementos maiores que o pivô e o segundo contém elementos menores. E repetimos recursivamente. Não é a melhor opção, mas, novamente, é melhor lembrada. Vamos implementar esse algoritmo em Java, mas de forma mais correta. O material da revisão “ Ciência da Computação em JavaScript: Quicksort ” nos ajudará nisso . E antes de escrever o código, vamos desenhar novamente para “sentir” o algoritmo: Primeiro, vamos desenhar novamente em um pedaço de papel para entender o algoritmo:
-
Precisamos ser capazes de trocar elementos em um array:
private static void swap(int[] array, int firstIndex, int secondIndex) { int temp = array[firstIndex]; array[firstIndex] = array[secondIndex]; array[secondIndex] = temp; }
-
Precisamos de um método que divida o array no intervalo especificado em 3 partes
private static int partition(int[] array, int left, int right) { int pivot = array[(right + left) / 2]; while (left <= right) { while (array[left] < pivot) { left++; } while (array[right] > pivot) { right--; } if (left <= right) { swap(array, left, right); left++; right--; } } return left; }
Detalhes no link acima. Resumindo, movemos o cursor esquerdo até que o elemento seja menor que o pivô. Da mesma forma, mova o cursor direito da outra extremidade. E fazemos uma troca se os cursores não corresponderem. Continuamos até que os cursores convirjam. Retornamos um índice que divide o processamento adicional em 2 partes.
-
Há uma separação, precisamos da própria classificação:
public static void quickSort(int[] array, int left, int right) { int index = 0; if (array.length > 1) { index = partition(array, left, right); if (left < index - 1) { quickSort(array, left, index - 1); } if (index < right) { quickSort(array, index, right); } } }
Ou seja, se o array consistir em pelo menos dois elementos, eles já poderão ser classificados. Primeiro, dividimos todo o array em duas partes, elementos menores que o pivô e elementos maiores. Em seguida, realizamos ações semelhantes para cada uma das partes resultantes.
E para o teste:
public static void main(String []args){ int[] array = {8,9,3,7,6,7,1}; quickSort(array, 0, array.length-1); System.out.println(Arrays.toString(array)); }
public static void mergeSort(int[] source, int left, int right) {
if ((right - left) > 1) {
int middle = (right + left) / 2;
mergeSort(source, left, middle);
mergeSort(source, middle + 1, right);
}
merge(source, left, right);
}
Determinamos o meio e dividimos o array ao meio. Para cada metade fazemos o mesmo e assim por diante. Condição de parada ou caso base - devemos ter mais de um elemento, pois não podemos dividir um elemento em dois. Agora precisamos implementar a fusão, ou seja, mesclar:
public static void merge(int[] array, int from, int to) {
int middle = ((from + to) / 2) + 1;
int left = from;
int right = middle;
int cursor = 0;
int[] tmp = new int[to - from + 1];
while (left < middle || right <= to) {
if (left >= middle) {
tmp[cursor] = array[right];
System.out.println("Остаток справа: " + array[right]);
right++;
} else if (right > to) {
tmp[cursor] = array[left];
System.out.println("Остаток слева: " + array[left]);
left++;
} else if (array[left] <= array[right]) {
tmp[cursor] = array[left];
System.out.println("Слева меньше: " + array[left]);
left++;
} else if (array[right] < array[left]) {
tmp[cursor] = array[right];
System.out.println("Справа меньше: " + array[right]);
right++;
}
cursor++;
}
System.arraycopy(tmp, 0, array, from, tmp.length);
}
Não há muito o que comentar aqui. Pelos nomes das variáveis println
tudo fica claro. Bem, para verificar:
int array[] = {1, 7, 3, 6, 7, 9, 8, 4};
mergeSort(array, 0, array.length - 1);
System.out.println(Arrays.toString(array));
Tabelas hash
O livro também aborda tabelas hash. Você não precisa implementá-lo sozinho, e a essência das tabelas hash é bastante simples. Afinal, Java também possui uma implementação de tabelas hash, java.util.HashTable. Se olharmos para o dispositivo HashTable, veremos que o array Entry reside dentro dele. Entrada é um registro que é uma combinação de Chave – Valor. HashTable possui inicialCapacity - ou seja, o tamanho inicial. E loadFactor – fator de carga. O padrão é 0,75. Este número informa em qual carga da matriz (número de elementos/número total) o tamanho precisa ser aumentado. Em Java aumenta 2 vezes. O livro explica que as tabelas hash são chamadas de tabelas hash porque, com base na função hash, a célula da matriz (cesta) na qual o arquivoEntry
. Você também pode ler mais aqui: Estruturas de dados em imagens. HashMap e LinkedHashMap . Você também pode ler isso em livros. Por exemplo aqui: " Noções básicas de HashTable "
Gráficos, pesquisa em amplitude (pesquisa de caminho mais curto)
Provavelmente um dos tópicos mais interessantes são os gráficos. E aqui, para ser justo, o livro dá muita atenção a eles. Talvez por isso valha a pena ler este livro. Embora, talvez, pudesse ter sido dito um pouco mais claramente)) Mas, temos a Internet e além do livro, você pode conferir esta playlist sobre teoria para “aqueles que estão ouvindo falar de gráficos pela primeira vez . ” Bem, naturalmente, logo no início do livro,breadth-first-search
é fornecido o algoritmo de busca em largura, também conhecido como BFS. O livro contém o seguinte gráfico:
private Map<String, String[]> getGraph() {
Map<String, String[]> map = new HashMap<>();
map.put("you", new String[]{"alice", "bob", "claire"});
map.put("bob", new String[]{"anuj", "peggy"});
map.put("alice", new String[]{"peggy"});
map.put("claire", new String[]{"thom", "jonny"});
map.put("annuj", null);
map.put("peggy", null);
map.put("thom", null);
map.put("johny", null);
return map;
}
Agora a pesquisa em si, baseada nestes dados:
private String search() {
Map<String, String[]> graph = getGraph();
Set<String> searched = new HashSet<>();
Deque<String> searchQue = new ArrayDeque<>();
searchQue.add("you");
while (!searchQue.isEmpty()) {
String person = searchQue.pollFirst();
System.out.println(person);
if (personIsSeller(person)) {
return person;
} else {
String[] friends = graph.get(person);
if (friends == null) continue;
for (String friend : friends) {
if (friend != null && !searched.contains(friend)) {
searchQue.addLast(friend);
}
}
}
}
return null;
}
Como você pode ver, nada complicado. Se você comparar com o código do livro, é quase a mesma coisa.
Gráficos, Algoritmo de Dijkstra
Tendo compreendido mais ou menos o BFS, o autor do livro nos convida a compreender o algoritmo Daysktra e os gráficos ponderados. O seguinte gráfico é proposto para a solução:public Integer[][] getGraphMatrix(int size) {
Integer[][] matrix = new Integer[size][size];
matrix[0][1] = 6;
matrix[0][2] = 2;
matrix[2][1] = 3;
matrix[1][3] = 1;
matrix[2][3] = 5;
return matrix;
}
E agora a própria lógica:
@Test
public void dijkstra() {
Integer[][] graph = getGraphMatrix(); // Данные графа
Integer[] costs = new Integer[graph.length]; // Стоимость перехода
Integer[] parents = new Integer[graph.length]; // Родительский узел
BitSet visited = new BitSet(graph.length); // "Ферма" маркеров посещённости
Integer w = 0;
do {
System.out.println("-> Рассматриваем вершину: " + w);
Integer min = null;
for (int i = 0; i < graph.length; i++) { // Обрабатываем каждую дугу
if (graph[w][i] == null) continue; // Дуги нет - идём дальше
if (min == null || (!visited.get(i) && graph[w][min] > graph[w][i])) {
min = i;
}
if (costs[i] == null || costs[i] > costs[w] + graph[w][i]) {
System.out.print("Меням вес с " + costs[i]);
costs[i] = (costs[w] != null ? costs[w] : 0) + graph[w][i];
System.out.println(" на " + costs[i] + " для вершины " + i);
parents[i] = w;
}
}
System.out.println("Вершина с минимальным весом: " + min);
visited.set(w);
w = min;
} while (w != null);
System.out.println(Arrays.toString(costs));
printPath(parents, 3);
}
public void printPath(Integer[] parents, int target) {
Integer parent = target;
do {
System.out.print(parent + " <- ");
parent = parents[parent];
} while (parent != null);
}
O livro explica tudo passo a passo. Se você adicionar um artigo sobre Habré na Internet + olhar o código, você poderá se lembrar dele. Achei a análise passo a passo um pouco confusa. Mas para a própria natureza passo a passo é uma vantagem. No geral, ok, embora pudesse ter sido melhor)
Algoritmos gananciosos
A próxima seção é dedicada a “algoritmos gananciosos”. Esta seção é interessante porque usa conjuntos (java.util.Set). Finalmente podemos ver por que isso pode ser necessário. Usamos uma lista de estados como entrada:Set<String> statesNeeded = new HashSet();
statesNeeded.addAll(Arrays.asList("mt", "wa", "or", "id", "nv", "ut", "ca", "az" ));
E também uma lista de estações de rádio que cobrem alguns desses estados:
Map<String, Set<String>> stations = new HashMap<>();
stations.put("kone", new HashSet(Arrays.asList("id", "nv", "ut")));
stations.put("ktwo", new HashSet(Arrays.asList("wa", "id", "mt")));
stations.put("kthree", new HashSet(Arrays.asList("or", "nv", "ca")));
stations.put("kfour", new HashSet(Arrays.asList("nv", "ut")));
stations.put("kfive", new HashSet(Arrays.asList("ca", "az")));
O livro continua apontando e explicando o algoritmo em si:
Set<String> finalStations = new HashSet();
while (!statesNeeded.isEmpty()) {
String bestStation = null;
Set<String> statesCovered = new HashSet();
for (String station: stations.keySet()) {
Set covered = new HashSet(statesNeeded);
covered.retainAll(stations.get(station));
if (covered.size() > statesCovered.size()) {
bestStation = station;
statesCovered = covered;
}
}
statesNeeded.removeAll(statesCovered);
finalStations.add(bestStation);
}
System.out.println(finalStations);
Programaçao dinamica
O livro também descreve problemas aos quais é aplicada uma abordagem chamada “programação dinâmica”. A tarefa é dada:List<Thing> things = new ArrayList<>();
things.add(new Thing("guitar", 1, 1500));
things.add(new Thing("tape recorder", 4, 3000));
things.add(new Thing("notebook", 3, 2000));
Agora o algoritmo em si:
int bagSize = 4;
int cell[][] = new int[things.size()][bagSize];
// Заполняем первую строку без условий
for (int i = 0; i < bagSize; i++) {
cell[0][i] = things.get(0).cost;
}
// Заполняем оставшиеся
for (int i = 1; i < cell.length; i++) {
for (int j = 0; j < cell[i].length; j++) {
// Если вещь не влезает - берём прошлый максимум
if (things.get(i).weight > j+1) {
cell[i][j] = cell[i - 1][j];
} else {
// Иначе текущая стоимость + предыдущий максимум оставшегося размера
cell[i][j] = things.get(i).cost;
if (j + 1 - things.get(i).weight > 0) {
cell[i][j] += cell[i-1][j + 1 - things.get(i).weight];
}
}
}
}
System.out.println(Arrays.deepToString(cell));
Há também uma tarefa interessante para encontrar as palavras mais semelhantes. Interessante, não é? Mais detalhes aqui: LongestCommonSubsequence.java
Procure os vizinhos mais próximos
O livro também fala claramente sobre o algoritmo dos k-vizinhos mais próximos:Resultado final
O livro termina com uma seção interessante “O que vem a seguir?”, que fornece uma rápida visão geral de algoritmos interessantes. Aqui está uma breve descrição do significado das árvores e outros algoritmos. No geral, gostei do livro. Não deve ser levado a sério como algum tipo de informação abrangente. Você terá que pesquisar e entender por si mesmo. Mas como informação introdutória para interessar e dar uma ideia inicial, é muito boa. Sim, o código do livro está escrito em Python. Portanto, todos os exemplos acima são compiláveis. Espero que esta resenha ajude você a ter uma ideia do que o livro contém e se vale a pena comprá-lo.Adicionalmente
Você também pode verificar os seguintes recursos sobre este tópico:- EdX - Introdução à Programação Java: Estruturas de Dados e Algoritmos Fundamentais
- LinkedIn - Introdução a estruturas de dados e algoritmos em Java (pago)
- 27 sites com quebra-cabeças para aprimorar suas habilidades de programação
- Codificação Java Bat
- Tarefas para programadores, respostas para tarefas de complexidade variada
GO TO FULL VERSION