Merge branch 'trekhleb:master' into my-test

This commit is contained in:
nikikalwar 2021-06-18 14:50:34 +05:30 committed by GitHub
commit f7bb28a0fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 3446 additions and 1242 deletions

View File

@ -11,5 +11,10 @@
"class-methods-use-this": "off",
"arrow-body-style": "off",
"no-loop-func": "off"
},
"settings": {
"react": {
"version": "latest"
}
}
}

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

5
.husky/pre-commit Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint
# npm run test

View File

@ -1,5 +0,0 @@
{
"hooks": {
"pre-commit": "npm run lint && npm run test"
}
}

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=false

View File

@ -21,7 +21,8 @@ _اقرأ هذا في لغات أخرى:_
[_Português_](README.pt-BR.md),
[_Русский_](README.ru-RU.md),
[_Türk_](README.tr-TR.md),
[_Italiana_](README.it-IT.md)
[_Italiana_](README.it-IT.md),
[_Deutsch_](README.de-DE.md)
☝ ملاحضة هذا المشروع مخصص للاستخدام لأغراض التعلم والبحث
فقط ، و ** ليست ** معدة للاستخدام في **الإنتاج**
@ -320,3 +321,5 @@ npm test -- 'playground'
> يمكنك دعم هذا المشروع عبر ❤️️ [GitHub](https://github.com/sponsors/trekhleb) أو ❤️️ [Patreon](https://www.patreon.com/trekhleb).
[الناس الذين يدعمون هذا المشروع](https://github.com/trekhleb/javascript-algorithms/blob/master/BACKERS.md) `∑ = 0`
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

336
README.de-DE.md Normal file
View File

@ -0,0 +1,336 @@
# JavaScript-Algorithmen und Datenstrukturen
[![CI](https://github.com/trekhleb/javascript-algorithms/workflows/CI/badge.svg)](https://github.com/trekhleb/javascript-algorithms/actions?query=workflow%3ACI+branch%3Amaster)
[![codecov](https://codecov.io/gh/trekhleb/javascript-algorithms/branch/master/graph/badge.svg)](https://codecov.io/gh/trekhleb/javascript-algorithms)
Dieses Repository enthält JavaScript Beispiele für viele
gängige Algorithmen und Datenstrukturen.
Jeder Algorithmus und jede Datenstruktur hat eine eigene README
mit zugehörigen Erklärungen und weiterführenden Links (einschließlich zu YouTube-Videos).
_Lies dies in anderen Sprachen:_
[_English_](https://github.com/trekhleb/javascript-algorithms/)
[_简体中文_](README.zh-CN.md),
[_繁體中文_](README.zh-TW.md),
[_한국어_](README.ko-KR.md),
[_日本語_](README.ja-JP.md),
[_Polski_](README.pl-PL.md),
[_Français_](README.fr-FR.md),
[_Español_](README.es-ES.md),
[_Português_](README.pt-BR.md),
[_Русский_](README.ru-RU.md),
[_Türk_](README.tr-TR.md),
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
_☝ Beachte, dass dieses Projekt nur für Lern- und Forschungszwecke gedacht ist und **nicht** für den produktiven Einsatz verwendet werden soll_
## Datenstrukturen
Eine Datenstruktur ist eine bestimmte Art und Weise, Daten in einem Computer so zu organisieren und zu speichern, dass sie
effizient erreicht und verändert werden können. Genauer gesagt, ist eine Datenstruktur eine Sammlung von Werten,
den Beziehungen zwischen ihnen und den Funktionen oder Operationen, die auf die Daten angewendet werden können.
`B` - Anfänger:innen, `A` - Fortgeschrittene
* `B` [Verkettete Liste (Linked List)](src/data-structures/linked-list)
* `B` [Doppelt verkettete Liste (Doubly Linked List)](src/data-structures/doubly-linked-list)
* `B` [Warteschlange (Queue)](src/data-structures/queue)
* `B` [Stapelspeicher (Stack)](src/data-structures/stack)
* `B` [Hashtabelle (Hash Table)](src/data-structures/hash-table)
* `B` [Heap-Algorithmus (Heap)](src/data-structures/heap) - max und min Heap-Versionen
* `B` [Vorrangwarteschlange (Priority Queue)](src/data-structures/priority-queue)
* `A` [Trie (Trie)](src/data-structures/trie)
* `A` [Baum (Tree)](src/data-structures/tree)
* `A` [Binärer Suchbaum (Binary Search Tree)](src/data-structures/tree/binary-search-tree)
* `A` [AVL-Baum (AVL Tree)](src/data-structures/tree/avl-tree)
* `A` [Rot-Schwarz-Baum (Red-Black Tree)](src/data-structures/tree/red-black-tree)
* `A` [Segment-Baum (Segment Tree)](src/data-structures/tree/segment-tree) - mit Min/Max/Summenbereich-Abfrage Beispiel
* `A` [Fenwick Baum (Fenwick Tree)](src/data-structures/tree/fenwick-tree) (Binär indizierter Baum / Binary Indexed Tree)
* `A` [Graph (Graph)](src/data-structures/graph) (sowohl gerichtet als auch ungerichtet)
* `A` [Union-Find-Struktur (Disjoint Set)](src/data-structures/disjoint-set)
* `A` [Bloomfilter (Bloom Filter)](src/data-structures/bloom-filter)
## Algorithmen
Ein Algorithmus ist eine eindeutige Spezifikation, wie eine Klasse von Problemen zu lösen ist. Er besteht
aus einem Satz von Regeln, die eine Abfolge von Operationen genau definieren.
`B` - Anfänger:innen, `A` - Fortgeschrittene
### Algorithmen nach Thema
* **Mathe**
* `B` [Bitmanipulation (Bit Manipulation)](src/algorithms/math/bits) - Bits setzen/lesen/aktualisieren/löschen, Multiplikation/Division durch zwei negieren usw..
* `B` [Faktoriell (Factorial)](src/algorithms/math/factorial)
* `B` [Fibonacci-Zahl (Fibonacci Number)](src/algorithms/math/fibonacci) - Klassische und geschlossene Version
* `B` [Primfaktoren (Prime Factors)](src/algorithms/math/prime-factors) - Auffinden von Primfaktoren und deren Zählung mit Hilfe des Satz von Hardy-Ramanujan (Hardy-Ramanujan's theorem)
* `B` [Primzahl-Test (Primality Test)](src/algorithms/math/primality-test) (Probedivision / trial division method)
* `B` [Euklidischer Algorithmus (Euclidean Algorithm)](src/algorithms/math/euclidean-algorithm) - Berechnen des größten gemeinsamen Teilers (ggT)
* `B` [Kleinstes gemeinsames Vielfaches (Least Common Multiple)](src/algorithms/math/least-common-multiple) (kgV)
* `B` [Sieb des Eratosthenes (Sieve of Eratosthenes)](src/algorithms/math/sieve-of-eratosthenes) - Finden aller Primzahlen bis zu einer bestimmten Grenze
* `B` [Power of two (Is Power of Two)](src/algorithms/math/is-power-of-two) - Prüft, ob die Zahl eine Zweierpotenz ist (naive und bitweise Algorithmen)
* `B` [Pascalsches Dreieck (Pascal's Triangle)](src/algorithms/math/pascal-triangle)
* `B` [Komplexe Zahlen (Complex Number)](src/algorithms/math/complex-number) - Komplexe Zahlen und Grundoperationen mit ihnen
* `B` [Bogenmaß & Grad (Radian & Degree)](src/algorithms/math/radian) - Umrechnung von Bogenmaß in Grad und zurück
* `B` [Fast Powering Algorithmus (Fast Powering)](src/algorithms/math/fast-powering)
* `B` [Horner-Schema (Horner's method)](src/algorithms/math/horner-method) - Polynomauswertung
* `B` [Matrizen (Matrices)](src/algorithms/math/matrix) - Matrizen und grundlegende Matrixoperationen (Multiplikation, Transposition usw.)
* `B` [Euklidischer Abstand (Euclidean Distance)](src/algorithms/math/euclidean-distance) - Abstand zwischen zwei Punkten/Vektoren/Matrizen
* `A` [Ganzzahlige Partitionierung (Integer Partition)](src/algorithms/math/integer-partition)
* `A` [Quadratwurzel (Square Root)](src/algorithms/math/square-root) - Newtonverfahren (Newton's method)
* `A` [Liu Hui π Algorithmus (Liu Hui π Algorithm)](src/algorithms/math/liu-hui) - Näherungsweise π-Berechnungen auf Basis von N-gons
* `A` [Diskrete Fourier-Transformation (Discrete Fourier Transform)](src/algorithms/math/fourier-transform) - Eine Funktion der Zeit (ein Signal) in die Frequenzen zerlegen, aus denen sie sich zusammensetzt
* **Sets**
* `B` [Kartesisches Produkt (Cartesian Product)](src/algorithms/sets/cartesian-product) - Produkt aus mehreren Mengen
* `B` [Fisher-Yates-Verfahren (FisherYates Shuffle)](src/algorithms/sets/fisher-yates) - Zufällige Permutation einer endlichen Folge
* `A` [Potenzmenge (Power Set)](src/algorithms/sets/power-set) - Alle Teilmengen einer Menge (Bitweise und Rücksetzverfahren Lösungen(backtracking solutions))
* `A` [Permutation (Permutations)](src/algorithms/sets/permutations) (mit und ohne Wiederholungen)
* `A` [Kombination (Combinations)](src/algorithms/sets/combinations) (mit und ohne Wiederholungen)
* `A` [Problem der längsten gemeinsamen Teilsequenz (Longest Common Subsequence)](src/algorithms/sets/longest-common-subsequence) (LCS)
* `A` [Längste gemeinsame Teilsequenz (Longest Increasing Subsequence)](src/algorithms/sets/longest-increasing-subsequence)
* `A` [Der kürzeste gemeinsame String (Shortest Common Supersequence)](src/algorithms/sets/shortest-common-supersequence) (SCS)
* `A` [Rucksackproblem (Knapsack Problem)](src/algorithms/sets/knapsack-problem) - "0/1" und "Ungebunden"
* `A` [Das Maximum-Subarray Problem (Maximum Subarray)](src/algorithms/sets/maximum-subarray) - "Brute-Force-Methode" und "Dynamische Programmierung" (Kadane' Algorithmus)
* `A` [Kombinationssumme (Combination Sum)](src/algorithms/sets/combination-sum) - Alle Kombinationen finden, die eine bestimmte Summe bilden
* **Zeichenketten (Strings)**
* `B` [Hamming-Abstand (Hamming Distance)](src/algorithms/string/hamming-distance) - Anzahl der Positionen, an denen die Symbole unterschiedlich sind
* `A` [Levenshtein-Distanz (Levenshtein Distance)](src/algorithms/string/levenshtein-distance) - Minimaler Editierabstand zwischen zwei Sequenzen
* `A` [Knuth-Morris-Pratt-Algorithmus (KnuthMorrisPratt Algorithm)](src/algorithms/string/knuth-morris-pratt) (KMP Algorithmus) - Teilstringsuche (Mustervergleich / Pattern Matching)
* `A` [Z-Algorithmus (Z Algorithm)](src/algorithms/string/z-algorithm) - Teilstringsuche (Mustervergleich / Pattern Matching)
* `A` [Rabin-Karp-Algorithmus (Rabin Karp Algorithm)](src/algorithms/string/rabin-karp) - Teilstringsuche
* `A` [Längstes häufiges Teilzeichenfolgenproblem (Longest Common Substring)](src/algorithms/string/longest-common-substring)
* `A` [Regulärer Ausdruck (Regular Expression Matching)](src/algorithms/string/regular-expression-matching)
* **Suchen**
* `B` [Lineare Suche (Linear Search)](src/algorithms/search/linear-search)
* `B` [Sprungsuche (Jump Search)](src/algorithms/search/jump-search) (oder Blocksuche) - Suche im sortierten Array
* `B` [Binäre Suche (Binary Search)](src/algorithms/search/binary-search) - Suche in einem sortierten Array
* `B` [Interpolationssuche (Interpolation Search)](src/algorithms/search/interpolation-search) - Suche in gleichmäßig verteilt sortiertem Array
* **Sortieren**
* `B` [Bubblesort (Bubble Sort)](src/algorithms/sorting/bubble-sort)
* `B` [Selectionsort (Selection Sort)](src/algorithms/sorting/selection-sort)
* `B` [Einfügesortierenmethode (Insertion Sort)](src/algorithms/sorting/insertion-sort)
* `B` [Haldensortierung (Heap Sort)](src/algorithms/sorting/heap-sort)
* `B` [Mergesort (Merge Sort)](src/algorithms/sorting/merge-sort)
* `B` [Quicksort (Quicksort)](src/algorithms/sorting/quick-sort) - in-place und non-in-place Implementierungen
* `B` [Shellsort (Shellsort)](src/algorithms/sorting/shell-sort)
* `B` [Countingsort (Counting Sort)](src/algorithms/sorting/counting-sort)
* `B` [Fachverteilen (Radix Sort)](src/algorithms/sorting/radix-sort)
* **Verkettete Liste (Linked List)**
* `B` [Gerade Traversierung (Straight Traversal)](src/algorithms/linked-list/traversal)
* `B` [Umgekehrte Traversierung (Reverse Traversal)](src/algorithms/linked-list/reverse-traversal)
* **Bäume**
* `B` [Tiefensuche (Depth-First Search)](src/algorithms/tree/depth-first-search) (DFS)
* `B` [Breitensuche (Breadth-First Search)](src/algorithms/tree/breadth-first-search) (BFS)
* **Graphen**
* `B` [Tiefensuche (Depth-First Search)](src/algorithms/graph/depth-first-search) (DFS)
* `B` [Breitensuche (Breadth-First Search)](src/algorithms/graph/breadth-first-search) (BFS)
* `B` [Algorithmus von Kruskal (Kruskals Algorithm)](src/algorithms/graph/kruskal) - Finden des Spannbaum (Minimum Spanning Tree / MST) für einen gewichteten ungerichteten Graphen
* `A` [Dijkstra-Algorithmus (Dijkstra Algorithm)](src/algorithms/graph/dijkstra) - Finden der kürzesten Wege zu allen Knoten des Graphen von einem einzelnen Knotenpunkt aus
* `A` [Bellman-Ford-Algorithmus (Bellman-Ford Algorithm)](src/algorithms/graph/bellman-ford) - Finden der kürzesten Wege zu allen Knoten des Graphen von einem einzelnen Knotenpunkt aus
* `A` [Algorithmus von Floyd und Warshall (Floyd-Warshall Algorithm)](src/algorithms/graph/floyd-warshall) - Die kürzesten Wege zwischen allen Knotenpaaren finden
* `A` [Zykluserkennung (Detect Cycle)](src/algorithms/graph/detect-cycle) - Sowohl für gerichtete als auch für ungerichtete Graphen (DFS- und Disjoint-Set-basierte Versionen)
* `A` [Algorithmus von Prim (Prims Algorithm)](src/algorithms/graph/prim) - Finden des Spannbaums (Minimum Spanning Tree / MST) für einen gewichteten ungerichteten Graphen
* `A` [Topologische Sortierung (Topological Sorting)](src/algorithms/graph/topological-sorting) - DFS-Verfahren
* `A` [Artikulationspunkte (Articulation Points)](src/algorithms/graph/articulation-points) - Algorithmus von Tarjan (Tarjan's algorithm) (DFS basiert)
* `A` [Brücke (Bridges)](src/algorithms/graph/bridges) - DFS-basierter Algorithmus
* `A` [Eulerkreisproblem (Eulerian Path and Eulerian Circuit)](src/algorithms/graph/eulerian-path) - Algorithmus von Fleury (Fleury's algorithm) - Jede Kante genau einmal durchlaufen.
* `A` [Hamiltonkreisproblem (Hamiltonian Cycle)](src/algorithms/graph/hamiltonian-cycle) - Jeden Eckpunkt genau einmal durchlaufen.
* `A` [Starke Zusammenhangskomponente (Strongly Connected Components)](src/algorithms/graph/strongly-connected-components) - Kosarajus Algorithmus
* `A` [Problem des Handlungsreisenden (Travelling Salesman Problem)](src/algorithms/graph/travelling-salesman) - Kürzestmögliche Route, die jede Stadt besucht und zur Ausgangsstadt zurückkehrt
* **Kryptographie**
* `B` [Polynomiale Streuwertfunktion(Polynomial Hash)](src/algorithms/cryptography/polynomial-hash) - Rollierende Streuwert-Funktion basierend auf Polynom
* `B` [Schienenzaun Chiffre (Rail Fence Cipher)](src/algorithms/cryptography/rail-fence-cipher) - Ein Transpositionsalgorithmus zur Verschlüsselung von Nachrichten
* `B` [Caesar-Verschlüsselung (Caesar Cipher)](src/algorithms/cryptography/caesar-cipher) - Einfache Substitutions-Chiffre
* `B` [Hill-Chiffre (Hill Cipher)](src/algorithms/cryptography/hill-cipher) - Substitutionschiffre basierend auf linearer Algebra
* **Maschinelles Lernen**
* `B` [Künstliches Neuron (NanoNeuron)](https://github.com/trekhleb/nano-neuron) - 7 einfache JS-Funktionen, die veranschaulichen, wie Maschinen tatsächlich lernen können (Vorwärts-/Rückwärtspropagation)
* `B` [Nächste-Nachbarn-Klassifikation (k-NN)](src/algorithms/ml/knn) - k-nächste-Nachbarn-Algorithmus
* `B` [k-Means (k-Means)](src/algorithms/ml/k-means) - k-Means-Algorithmus
* **Image Processing**
* `B` [Inhaltsabhängige Bildverzerrung (Seam Carving)](src/algorithms/image-processing/seam-carving) - Algorithmus zur inhaltsabhängigen Bildgrößenänderung
* **Unkategorisiert**
* `B` [Türme von Hanoi (Tower of Hanoi)](src/algorithms/uncategorized/hanoi-tower)
* `B` [Rotationsmatrix (Square Matrix Rotation)](src/algorithms/uncategorized/square-matrix-rotation) - In-Place-Algorithmus
* `B` [Jump Game (Jump Game)](src/algorithms/uncategorized/jump-game) - Backtracking, dynamische Programmierung (Top-down + Bottom-up) und gierige Beispiele
* `B` [Eindeutige Pfade (Unique Paths)](src/algorithms/uncategorized/unique-paths) - Backtracking, dynamische Programmierung und Pascalsches Dreieck basierte Beispiele
* `B` [Regenterrassen (Rain Terraces)](src/algorithms/uncategorized/rain-terraces) - Auffangproblem für Regenwasser (trapping rain water problem) (dynamische Programmierung und Brute-Force-Versionen)
* `B` [Rekursive Treppe (Recursive Staircase)](src/algorithms/uncategorized/recursive-staircase) - Zählen der Anzahl der Wege, die nach oben führen (4 Lösungen)
* `B` [Beste Zeit zum Kaufen/Verkaufen von Aktien (Best Time To Buy Sell Stocks)](src/algorithms/uncategorized/best-time-to-buy-sell-stocks) - Beispiele für "Teile und Herrsche" und Beispiele für den One-Pass-Algorithmus
* `A` [Damenproblem (N-Queens Problem)](src/algorithms/uncategorized/n-queens)
* `A` [Springerproblem (Knight's Tour)](src/algorithms/uncategorized/knight-tour)
### Algorithmen nach Paradigma
Ein algorithmisches Paradigma ist eine generische Methode oder ein Ansatz, der dem Entwurf einer Klasse von Algorithmen zugrunde liegt. Es ist eine Abstraktion, die höher ist als der Begriff des Algorithmus. Genauso wie ein Algorithmus eine Abstraktion ist, die höher ist als ein Computerprogramm.
* **Brachiale Gewalt (Brute Force)** - schaut sich alle Möglichkeiten an und wählt die beste Lösung aus
* `B` [Lineare Suche (Linear Search)](src/algorithms/search/linear-search)
* `B` [Regenterrassen (Rain Terraces)](src/algorithms/uncategorized/rain-terraces) - Auffangproblem für Regenwasser (trapping rain water problem) (dynamische Programmierung und Brute-Force-Versionen)
* `B` [Rekursive Treppe (Recursive Staircase)](src/algorithms/uncategorized/recursive-staircase) - Zählen der Anzahl der Wege, die nach oben führen (4 Lösungen)
* `A` [Das Maximum-Subarray Problem (Maximum Subarray)](src/algorithms/sets/maximum-subarray)
* `A` [Problem des Handlungsreisenden (Travelling Salesman Problem)](src/algorithms/graph/travelling-salesman) - Kürzestmögliche Route, die jede Stadt besucht und zur Ausgangsstadt zurückkehrt
* `A` [Diskrete Fourier-Transformation (Discrete Fourier Transform)](src/algorithms/math/fourier-transform) - Eine Funktion der Zeit (ein Signal) in die Frequenzen zerlegen, aus denen sie sich zusammensetzt
* **Gierig (Greedy)** - Wählt die beste Option zum aktuellen Zeitpunkt, ohne Rücksicht auf die Zukunft
* `B` [Jump Game (Jump Game)](src/algorithms/uncategorized/jump-game)
* `A` [Rucksackproblem (Unbound Knapsack Problem)](src/algorithms/sets/knapsack-problem)
* `A` [Dijkstra-Algorithmus (Dijkstra Algorithm)](src/algorithms/graph/dijkstra) - Finden der kürzesten Wege zu allen Knoten des Graphen von einem einzelnen Knotenpunkt aus
* `A` [Algorithmus von Prim (Prims Algorithm)](src/algorithms/graph/prim) - Finden des Spannbaums (Minimum Spanning Tree / MST) für einen gewichteten ungerichteten Graphen
* `B` [Algorithmus von Kruskal (Kruskals Algorithm)](src/algorithms/graph/kruskal) - Finden des Spannbaum (Minimum Spanning Tree / MST) für einen gewichteten ungerichteten Graphen
* **Teile und herrsche** - Das Problem in kleinere Teile aufteilen und diese Teile dann lösen
* `B` [Binäre Suche (Binary Search)](src/algorithms/search/binary-search)
* `B` [Türme von Hanoi (Tower of Hanoi)](src/algorithms/uncategorized/hanoi-tower)
* `B` [Pascalsches Dreieck (Pascal's Triangle)](src/algorithms/math/pascal-triangle)
* `B` [Euklidischer Algorithmus (Euclidean Algorithm)](src/algorithms/math/euclidean-algorithm) - calculate the Greatest Common Divisor (GCD)
* `B` [Mergesort (Merge Sort)](src/algorithms/sorting/merge-sort)
* `B` [Quicksort (Quicksort)](src/algorithms/sorting/quick-sort)
* `B` [Tiefensuche (Depth-First Search)](src/algorithms/tree/depth-first-search) (DFS)
* `B` [Breitensuche (Breadth-First Search)](src/algorithms/graph/depth-first-search) (DFS)
* `B` [Matrizen (Matrices)](src/algorithms/math/matrix) - Matrizen und grundlegende Matrixoperationen (Multiplikation, Transposition usw.)
* `B` [Jump Game (Jump Game)](src/algorithms/uncategorized/jump-game)
* `B` [Fast Powering Algorithmus (Fast Powering)](src/algorithms/math/fast-powering)
* `B` [Beste Zeit zum Kaufen/Verkaufen von Aktien (Best Time To Buy Sell Stocks)](src/algorithms/uncategorized/best-time-to-buy-sell-stocks) - Beispiele für "Teile und Herrsche" und Beispiele für den One-Pass-Algorithmus
* `A` [Permutation (Permutations)](src/algorithms/sets/permutations) (mit und ohne Wiederholungen)
* `A` [Kombination (Combinations)](src/algorithms/sets/combinations) (mit und ohne Wiederholungen)
* **Dynamische Programmierung** - Eine Lösung aus zuvor gefundenen Teillösungen aufbauen
* `B` [Fibonacci-Zahl (Fibonacci Number)](src/algorithms/math/fibonacci)
* `B` [Jump Game (Jump Game)](src/algorithms/uncategorized/jump-game)
* `B` [Eindeutige Pfade (Unique Paths)](src/algorithms/uncategorized/unique-paths)
* `B` [Regenterrassen (Rain Terraces)](src/algorithms/uncategorized/rain-terraces) - Auffangproblem für Regenwasser (trapping rain water problem) (dynamische Programmierung und Brute-Force-Versionen)
* `B` [Rekursive Treppe (Recursive Staircase)](src/algorithms/uncategorized/recursive-staircase) - Zählen der Anzahl der Wege, die nach oben führen (4 Lösungen)
* `B` [Inhaltsabhängige Bildverzerrung (Seam Carving)](src/algorithms/image-processing/seam-carving) - Algorithmus zur inhaltsabhängigen Bildgrößenänderung
* `A` [Levenshtein-Distanz (Levenshtein Distance)](src/algorithms/string/levenshtein-distance) - Minimaler Editierabstand zwischen zwei Sequenzen
* `A` [Problem der längsten gemeinsamen Teilsequenz (Longest Common Subsequence)](src/algorithms/sets/longest-common-subsequence) (LCS)
* `A` [Längstes häufiges Teilzeichenfolgenproblem (Longest Common Substring)](src/algorithms/string/longest-common-substring)
* `A` [Längste gemeinsame Teilsequenz (Longest Increasing Subsequence)](src/algorithms/sets/longest-increasing-subsequence)
* `A` [Der kürzeste gemeinsame String (Shortest Common Supersequence)](src/algorithms/sets/shortest-common-supersequence)
* `A` [Rucksackproblem (0/1 Knapsack Problem)](src/algorithms/sets/knapsack-problem)
* `A` [Ganzzahlige Partitionierung (Integer Partition)](src/algorithms/math/integer-partition)
* `A` [Das Maximum-Subarray Problem (Maximum Subarray)](src/algorithms/sets/maximum-subarray)
* `A` [Bellman-Ford-Algorithmus (Bellman-Ford Algorithm)](src/algorithms/graph/bellman-ford) - Finden der kürzesten Wege zu allen Knoten des Graphen von einem einzelnen Knotenpunkt aus
* `A` [Algorithmus von Floyd und Warshall (Floyd-Warshall Algorithm)](src/algorithms/graph/floyd-warshall) - Die kürzesten Wege zwischen allen Knotenpaaren finden
* `A` [Regulärer Ausdruck (Regular Expression Matching)](src/algorithms/string/regular-expression-matching)
* **Zurückverfolgung** - Ähnlich wie bei Brute-Force versuchen Sie, alle möglichen Lösungen zu generieren, aber jedes Mal, wenn Sie die nächste Lösung generieren, testen Sie, ob sie alle Bedingungen erfüllt, und fahren erst dann mit der Generierung weiterer Lösungen fort. Andernfalls gehen Sie zurück und nehmen einen anderen Weg, um eine Lösung zu finden. Normalerweise wird das DFS-Traversal des Zustandsraums verwendet.
* `B` [Jump Game (Jump Game)](src/algorithms/uncategorized/jump-game)
* `B` [Eindeutige Pfade (Unique Paths)](src/algorithms/uncategorized/unique-paths)
* `A` [Potenzmenge (Power Set)](src/algorithms/sets/power-set) - Alle Teilmengen einer Menge
* `A` [Hamiltonkreisproblem (Hamiltonian Cycle)](src/algorithms/graph/hamiltonian-cycle) - Jeden Eckpunkt genau einmal durchlaufen.
* `A` [Damenproblem (N-Queens Problem)](src/algorithms/uncategorized/n-queens)
* `A` [Springerproblem (Knight's Tour)](src/algorithms/uncategorized/knight-tour)
* `A` [Kombinationssumme (Combination Sum)](src/algorithms/sets/combination-sum) - Alle Kombinationen finden, die eine bestimmte Summe bilden
* **Verzweigung & Bindung** - Merkt sich die Lösung mit den niedrigsten Kosten, die in jeder Phase der Backtracking-Suche gefunden wurde, und verwendet die Kosten der bisher gefundenen Lösung mit den niedrigsten Kosten als untere Schranke für die Kosten einer Lösung des Problems mit den geringsten Kosten, um Teillösungen zu verwerfen, deren Kosten größer sind als die der bisher gefundenen Lösung mit den niedrigsten Kosten. Normalerweise wird das BFS-Traversal in Kombination mit dem DFS-Traversal des Zustandsraumbaums verwendet.
## So verwendest du dieses Repository
**Alle Abhängigkeiten installieren**
```
npm install
```
**ESLint ausführen**
You may want to run it to check code quality.
```
npm run lint
```
**Alle Tests ausführen**
```
npm test
```
**Tests nach Namen ausführen**
```
npm test -- 'LinkedList'
```
**Fehlerbehebung**
Falls das Linting oder Testen fehlschlägt, versuche, den Ordner "node_modules" zu löschen und die npm-Pakete neu zu installieren:
```
rm -rf ./node_modules
npm i
```
**Spielwiese**
Du kannst mit Datenstrukturen und Algorithmen in der Datei `./src/playground/playground.js` herumspielen und
dir in dieser Datei Tests schreiben `./src/playground/__test__/playground.test.js`.
Dann führe einfach folgenden Befehl aus, um zu testen, ob dein Spielwiesencode wie erwartet funktioniert:
```
npm test -- 'playground'
```
## Nützliche Informationen
### Referenzen
[▶ Datenstrukturen und Algorithmen auf YouTube(Englisch)](https://www.youtube.com/playlist?list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)
### O-Notation (_Big O Notation_)
Die O-Notation wird verwendet, um Algorithmen danach zu klassifizieren, wie ihre Laufzeit oder ihr Platzbedarf mit zunehmender Eingabegröße wächst. In der folgenden Tabelle finden Sie die häufigsten Wachstumsordnungen von Algorithmen, die in Big-O-Notation angegeben sind.
![O-Notation Graphen](./assets/big-o-graph.png)
Quelle: [Big O Cheat Sheet](http://bigocheatsheet.com/).
Nachfolgend finden Sie eine Liste einiger der am häufigsten verwendeten Big O-Notationen und deren Leistungsvergleiche für unterschiedliche Größen der Eingabedaten.
| Big O Notation | Berechnungen für 10 Elemente | Berechnungen für 100 Elemente | Berechnungen für 1000 Elemente |
| -------------- | ---------------------------- | ----------------------------- | ------------------------------ |
| **O(1)** | 1 | 1 | 1 |
| **O(log N)** | 3 | 6 | 9 |
| **O(N)** | 10 | 100 | 1000 |
| **O(N log N)** | 30 | 600 | 9000 |
| **O(N^2)** | 100 | 10000 | 1000000 |
| **O(2^N)** | 1024 | 1.26e+29 | 1.07e+301 |
| **O(N!)** | 3628800 | 9.3e+157 | 4.02e+2567 |
### Komplexität von Datenstrukturoperationen
| Datenstruktur | Zugriff auf | Suche | Einfügen | Löschung | Kommentare |
| ---------------------- | :---------: | :----: | :------: | :------: | :-------------------------------------------------------------- |
| **Array** | 1 | n | n | n | |
| **Stack** | n | n | 1 | 1 | |
| **Queue** | n | n | 1 | 1 | |
| **Linked List** | n | n | 1 | n | |
| **Hash Table** | - | n | n | n | Im Falle einer perfekten Hash-Funktion wären die Kosten O(1) |
| **Binary Search Tree** | n | n | n | n | Im Falle eines ausgeglichenen Baumes wären die Kosten O(log(n)) |
| **B-Tree** | log(n) | log(n) | log(n) | log(n) | |
| **Red-Black Tree** | log(n) | log(n) | log(n) | log(n) | |
| **AVL Tree** | log(n) | log(n) | log(n) | log(n) | |
| **Bloom Filter** | - | 1 | 1 | - | Falschpostive sind bei der Suche möglichen |
### Komplexität von Array-Sortieralgorithmen
| Name | Bester | Durchschnitt | Schlechtester | Speicher | Stabil | Kommentar |
| ------------------ | :-----------: | :---------------------: | :-------------------------: | :------: | :----: | :------------------------------------------------------------------------- |
| **Bubble sort** | n | n<sup>2</sup> | n<sup>2</sup> | 1 | JA | |
| **Insertion sort** | n | n<sup>2</sup> | n<sup>2</sup> | 1 | Ja | |
| **Selection sort** | n<sup>2</sup> | n<sup>2</sup> | n<sup>2</sup> | 1 | Nein | |
| **Heap sort** | n&nbsp;log(n) | n&nbsp;log(n) | n&nbsp;log(n) | 1 | Nein | |
| **Merge sort** | n&nbsp;log(n) | n&nbsp;log(n) | n&nbsp;log(n) | n | Ja | |
| **Quick sort** | n&nbsp;log(n) | n&nbsp;log(n) | n<sup>2</sup> | log(n) | Nein | Quicksort wird normalerweise in-place mit O(log(n)) Stapelplatz ausgeführt |
| **Shell sort** | n&nbsp;log(n) | abhängig von Spaltfolge | n&nbsp;(log(n))<sup>2</sup> | 1 | Nein | |
| **Counting sort** | n + r | n + r | n + r | n + r | Ja | r - größte Zahl im Array |
| **Radix sort** | n \* k | n \* k | n \* k | n + k | Ja | k - Länge des längsten Schlüssels |
## Projekt-Unterstützer
> Du kannst dieses Projekt unterstützen über ❤️️ [GitHub](https://github.com/sponsors/trekhleb) or ❤️️ [Patreon](https://www.patreon.com/trekhleb).
[Leute, die dieses Projekt unterstützen](https://github.com/trekhleb/javascript-algorithms/blob/master/BACKERS.md) `∑ = 0`
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -23,7 +23,8 @@ _Léelo en otros idiomas:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
*☝ Nótese que este proyecto está pensado con fines de aprendizaje e investigación,
y **no** para ser usado en producción.*
@ -298,3 +299,5 @@ frente a diferentes tamaños de los datos de entrada.
| **Shellsort** | n&nbsp;log(n) | depende de la secuencia de huecos | n&nbsp;(log(n))<sup>2</sup> | 1 | No | |
| **Ordenamiento por cuentas** | n + r | n + r | n + r | n + r | Si | r - mayor número en el arreglo |
| **Ordenamiento Radix** | n \* k | n \* k | n \* k | n + k | Si | k - largo de la llave más larga |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -24,7 +24,8 @@ _Lisez ceci dans d'autres langues:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
## Data Structures
@ -292,3 +293,5 @@ comparaisons de performance suivant différentes tailles pour les données d'ent
| **Tri Shell** | n&nbsp;log(n) | dépend du gap séquence | n&nbsp;(log(n))<sup>2</sup> | 1 | Non | |
| **Tri Comptage** | n + r | n + r | n + r | n + r | Oui | r - le plus grand nombre dans la liste |
| **Tri Radix** | n \* k | n \* k | n \* k | n + k | Non | k - longueur du plus long index |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -21,7 +21,8 @@ _Baca ini dalam bahasa yang lain:_
[_Türk_](README.tr-TR.md),
[_Italiana_](README.it-IT.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
_☝ Perhatikan bahwa proyek ini hanya dimaksudkan untuk tujuan pembelajaran dan riset, dan **tidak** dimaksudkan untuk digunakan sebagai produksi._
@ -303,3 +304,5 @@ Di bawah ini adalah daftar dari beberapa notasi _Bog O_ yang sering digunakan da
> Anda dapat mendukung proyek ini via ❤️️ [GitHub](https://github.com/sponsors/trekhleb) atau ❤️️ [Patreon](https://www.patreon.com/trekhleb).
[Orang-orang yang mendukung proyek ini](https://github.com/trekhleb/javascript-algorithms/blob/master/BACKERS.md) `∑ = 1`
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -20,7 +20,8 @@ _Leggilo in altre lingue:_
[_Türk_](README.tr-TR.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
*☝ Si noti che questo progetto è destinato ad essere utilizzato solo per l'apprendimento e la ricerca e non è destinato ad essere utilizzato per il commercio.*
@ -296,3 +297,5 @@ Nella tabella qua sotto ci sono riportate la lista delle notazioni Big O più us
| **Shell sort** | n&nbsp;log(n) | dipende dagli spazi vuoti nella sequenza | n&nbsp;(log(n))<sup>2</sup> | 1 | No | |
| **Counting sort** | n + r | n + r | n + r | n + r | Yes | r - numero più grande nell'array |
| **Radix sort** | n * k | n * k | n * k | n + k | Yes | k - lunghezza della chiave più grande |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -23,7 +23,8 @@ _Read this in other languages:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
## データ構造
@ -295,3 +296,5 @@ npm test -- 'playground'
| **Shell sort** | n&nbsp;log(n) | depends on gap sequence | n&nbsp;(log(n))<sup>2</sup> | 1 | No | |
| **Counting sort** | n + r | n + r | n + r | n + r | Yes | r - biggest number in array |
| **Radix sort** | n * k | n * k | n * k | n + k | Yes | k - length of longest key |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -22,7 +22,8 @@ _Read this in other languages:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
## 자료 구조
@ -276,3 +277,5 @@ Source: [Big O Cheat Sheet](http://bigocheatsheet.com/).
| **셸 정렬** | n&nbsp;log(n) | 간격 순서에 영향을 받습니다. | n&nbsp;(log(n))<sup>2</sup> | 1 | No | |
| **계수 정렬** | n + r | n + r | n + r | n + r | Yes | r - 배열내 가장 큰 수 |
| **기수 정렬** | n * k | n * k | n * k | n + k | Yes | k - 키값의 최대 길이 |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -24,7 +24,8 @@ _Read this in other languages:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
*☝ Note that this project is meant to be used for learning and researching purposes
only, and it is **not** meant to be used for production.*
@ -152,6 +153,8 @@ a set of rules that precisely define a sequence of operations.
* `B` [NanoNeuron](https://github.com/trekhleb/nano-neuron) - 7 simple JS functions that illustrate how machines can actually learn (forward/backward propagation)
* `B` [k-NN](src/algorithms/ml/knn) - k-nearest neighbors classification algorithm
* `B` [k-Means](src/algorithms/ml/k-means) - k-Means clustering algorithm
* **Image Processing**
* `B` [Seam Carving](src/algorithms/image-processing/seam-carving) - content-aware image resizing algorithm
* **Uncategorized**
* `B` [Tower of Hanoi](src/algorithms/uncategorized/hanoi-tower)
* `B` [Square Matrix Rotation](src/algorithms/uncategorized/square-matrix-rotation) - in-place algorithm
@ -203,6 +206,7 @@ algorithm is an abstraction higher than a computer program.
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths)
* `B` [Rain Terraces](src/algorithms/uncategorized/rain-terraces) - trapping rain water problem
* `B` [Recursive Staircase](src/algorithms/uncategorized/recursive-staircase) - count the number of ways to reach to the top
* `B` [Seam Carving](src/algorithms/image-processing/seam-carving) - content-aware image resizing algorithm
* `A` [Levenshtein Distance](src/algorithms/string/levenshtein-distance) - minimum edit distance between two sequences
* `A` [Longest Common Subsequence](src/algorithms/sets/longest-common-subsequence) (LCS)
* `A` [Longest Common Substring](src/algorithms/string/longest-common-substring)
@ -233,6 +237,7 @@ tree is being used.
## How to use this repository
**Install all dependencies**
```
npm install
```
@ -246,15 +251,26 @@ npm run lint
```
**Run all tests**
```
npm test
```
**Run tests by name**
```
npm test -- 'LinkedList'
```
**Troubleshooting**
In case if linting or testing is failing try to delete the `node_modules` folder and re-install npm packages:
```
rm -rf ./node_modules
npm i
```
**Playground**
You may play with data-structures and algorithms in `./src/playground/playground.js` file and write
@ -327,3 +343,5 @@ Below is the list of some of the most used Big O notations and their performance
> You may support this project via ❤️️ [GitHub](https://github.com/sponsors/trekhleb) or ❤️️ [Patreon](https://www.patreon.com/trekhleb).
[Folks who are backing this project](https://github.com/trekhleb/javascript-algorithms/blob/master/BACKERS.md) `∑ = 0`
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -24,7 +24,8 @@ _Read this in other languages:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
## Struktury Danych
@ -288,3 +289,5 @@ Poniżej umieszczamy listę najbardziej używanych Big O notacji i ich porównan
| **Sortowanie Shella** | n&nbsp;log(n) | zależy od luki w układzie | n&nbsp;(log(n))<sup>2</sup> | 1 | No | |
| **Sortowanie przez zliczanie** | n + r | n + r | n + r | n + r | Yes | r - największy numer w tablicy|
| **Sortowanie Radix** | n * k | n * k | n * k | n + k | Yes | k -długość najdłuższego klucza |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -24,7 +24,8 @@ _Leia isto em outros idiomas:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
## Estrutura de Dados
@ -289,3 +290,5 @@ Abaixo está a lista de algumas das notações Big O mais usadas e suas compara
| **Shell sort** | n&nbsp;log(n) | depende da sequência de lacunas | n&nbsp;(log(n))<sup>2</sup> | 1 | Não | |
| **Counting sort** | n + r | n + r | n + r | n + r | Sim | r - maior número na matriz |
| **Radix sort** | n * k | n * k | n * k | n + k | Sim | k - comprimento da chave mais longa |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -21,7 +21,8 @@ _Читать на других языках:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
*☝ Замечание: этот репозиторий предназначен для учебно-исследовательских целей (**не** для использования в продакшн-системах).*
@ -294,3 +295,5 @@ npm test -- 'playground'
| **Сортировка Шелла** | n&nbsp;log(n) | зависит от выбранных шагов | n&nbsp;(log(n))<sup>2</sup> | 1 | Нет | |
| **Сортировка подсчётом** | n + r | n + r | n + r | n + r | Да | r — наибольшее число в массиве |
| **Поразрядная сортировка** | n * k | n * k | n * k | n + k | Да | k — длина самого длинного ключа |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -22,7 +22,8 @@ _Read this in other languages:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
*☝ Not, bu proje araştırma ve öğrenme amacı ile yapılmış
olup üretim için **yaplılmamıştır**.*
@ -314,3 +315,5 @@ Altta Big O notations ve farklı input boyutlarına karşın yapılmış perform
## Projeyi Destekleme
Bu projeyi buradan destekleyebilirsiniz ❤️️ [GitHub](https://github.com/sponsors/trekhleb) veya ❤️️ [Patreon](https://www.patreon.com/trekhleb).
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -21,7 +21,8 @@ _Вивчення матеріалу на інших мовах:_
[_Türk_](README.tr-TR.md),
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
*☝ Зверніть увагу! Даний проект призначений лише для навчальних та дослідницьких цілей, і він **не** призначений для виробництва (продакшн).*
@ -304,3 +305,5 @@ npm test -- 'playground'
> Ви можете підтримати цей проект через ❤️️ [GitHub](https://github.com/sponsors/trekhleb) або ❤️️ [Patreon](https://www.patreon.com/trekhleb).
[Люди, які підтримують цей проект](https://github.com/trekhleb/javascript-algorithms/blob/master/BACKERS.md) `∑ = 1`
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -21,7 +21,8 @@ _Read this in other languages:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
*注意:这个项目仅用于学习和研究,**不是**用于生产环境。*
@ -291,3 +292,5 @@ npm test -- 'playground'
| **希尔排序** | n log(n) | 取决于差距序列 | n (log(n))^2 | 1 | No | |
| **计数排序** | n + r | n + r | n + r | n + r | Yes | r - 数组里最大的数 |
| **基数排序** | n * k | n * k | n * k | n + k | Yes | k - 最长 key 的升序 |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

View File

@ -20,7 +20,8 @@ _Read this in other languages:_
[_Italiana_](README.it-IT.md),
[_Bahasa Indonesia_](README.id-ID.md),
[_Українська_](README.uk-UA.md),
[_Arabic_](README.ar-AR.md)
[_Arabic_](README.ar-AR.md),
[_Deutsch_](README.de-DE.md)
## 資料結構
@ -214,10 +215,12 @@ npm test -- 'playground'
| 名稱 | 最佳 | 平均 | 最差 | 記憶體 | 穩定 |
| ---------------------- | :-------: | :-------: | :-----------: | :-------: | :-------: |
| **氣排序** | n | n^2 | n^2 | 1 | Yes |
| **氣排序** | n | n^2 | n^2 | 1 | Yes |
| **插入排序** | n | n^2 | n^2 | 1 | Yes |
| **選擇排序** | n^2 | n^2 | n^2 | 1 | No |
| **Heap 排序** | n log(n) | n log(n) | n log(n) | 1 | No |
| **合併排序** | n log(n) | n log(n) | n log(n) | n | Yes |
| **快速排序** | n log(n) | n log(n) | n^2 | log(n) | No |
| **希爾排序** | n log(n) | 由gap sequence決定 | n (log(n))^2 | 1 | No |
> A few more [projects](https://trekhleb.dev/projects/) and [articles](https://trekhleb.dev/blog/) about JavaScript and algorithms on [trekhleb.dev](https://trekhleb.dev)

2628
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,8 @@
"lint": "eslint ./src/**",
"test": "jest",
"coverage": "npm run test -- --coverage",
"ci": "npm run lint && npm run coverage"
"ci": "npm run lint && npm run coverage",
"prepare": "husky install"
},
"repository": {
"type": "git",
@ -37,14 +38,18 @@
"@babel/cli": "7.12.10",
"@babel/preset-env": "7.12.11",
"@types/jest": "26.0.19",
"canvas": "^2.7.0",
"eslint": "7.16.0",
"eslint-config-airbnb": "18.2.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jest": "24.1.3",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.21.5",
"husky": "4.3.6",
"husky": "6.0.0",
"jest": "26.6.3"
},
"dependencies": {}
"engines": {
"node": ">=12.0.0",
"npm": ">=6.9.0"
}
}

View File

@ -0,0 +1,509 @@
# Content-aware image resizing in JavaScript
![Content-aware image resizing in JavaScript](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/01-cover-02.png)
> There is an [interactive version of this post](https://trekhleb.dev/blog/2021/content-aware-image-resizing-in-javascript/) available where you can upload and resize your custom images.
## TL;DR
There are many great articles written about the *Seam Carving algorithm* already, but I couldn't resist the temptation to explore this elegant, powerful, and *yet simple* algorithm on my own, and to write about my personal experience with it. Another point that drew my attention (as a creator of [javascript-algorithms](https://github.com/trekhleb/javascript-algorithms) repo) was the fact that *Dynamic Programming (DP)* approach might be smoothly applied to solve it. And, if you're like me and still on your "learning algorithms" journey, this algorithmic solution may enrich your personal DP arsenal.
So, with this article I want to do three things:
1. Provide you with an interactive **content-aware resizer** so that you could play around with resizing your own images
2. Explain the idea behind the **Seam Carving algorithm**
3. Explain the **dynamic programming approach** to implement the algorithm (we'll be using TypeScript for it)
### Content-aware image resizing
*Content-aware image resizing* might be applied when it comes to changing the image proportions (i.e. reducing the width while keeping the height) and when losing some parts of the image is not desirable. Doing the straightforward image scaling in this case would distort the objects in it. To preserve the proportions of the objects while changing the image proportions we may use the [Seam Carving algorithm](https://perso.crans.org/frenoy/matlab2012/seamcarving.pdf) that was introduced by *Shai Avidan* and *Ariel Shamir*.
The example below shows how the original image width was reduced by 50% using *content-aware resizing* (left image) and *straightforward scaling* (right image). In this particular case, the left image looks more natural since the proportions of the balloons were preserved.
![Content-aware image resizing](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/01-resizing-options.png)
The Seam Carving algorithm's idea is to find the *seam* (continuous sequence of pixels) with the lowest contribution to the image content and then *carve* (remove) it. This process repeats over and over again until we get the required image width or height. In the example below you may see that the hot air balloon pixels contribute more to the content of the image than the sky pixels. Thus, the sky pixels are being removed first.
![JS IMAGE CARVER DEMO](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/10-demo-01.gif)
Finding the seam with the lowest energy is a computationally expensive task (especially for large images). To make the seam search faster the *dynamic programming* approach might be applied (we will go through the implementation details below).
### Objects removal
The importance of each pixel (so-called pixel's energy) is being calculated based on its color (`R`, `G`, `B`, `A`) difference between two neighbor pixels. Now, if we set the pixel energy to some really low level artificially (i.e. by drawing a mask on top of them), the Seam Carving algorithm would perform an **object removal** for us for free.
![JS IMAGE CARVER OBJECT REMOVAL DEMO](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/10-demo-02.gif)
### JS IMAGE CARVER demo
I've created the [JS IMAGE CARVER](https://trekhleb.dev/js-image-carver/) web-app (and also [open-sourced it on GitHub](https://github.com/trekhleb/js-image-carver)) that you may use to play around with resizing of your custom images.
### More examples
Here are some more examples of how the algorithm copes with more complex backgrounds.
Mountains on the background are being shrunk smoothly without visible seams.
![Resizing demo with more complex backgrounds](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/11-demo-01.png)
The same goes for the ocean waves. The algorithm preserved the wave structure without distorting the surfers.
![Resizing demo with more complex backgrounds](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/11-demo-02.png)
We need to keep in mind that the Seam Carving algorithm is not a silver bullet, and it may fail to resize the images where *most of the pixels are edges* (look important to the algorithm). In this case, it starts distorting even the important parts of the image. In the example below the content-aware image resizing looks pretty similar to a straightforward scaling since for the algorithm all the pixels look important, and it is hard for it to distinguish Van Gogh's face from the background.
![Example when the algorithm does not work as expected](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/12-demo-01.png)
## How Seam Carving algorithms works
Imagine we have a `1000 x 500 px` picture, and we want to change its size to `500 x 500 px` to make it square (let's say the square ratio would better fit the Instagram feed). We might want to set up several **requirements to the resizing process** in this case:
- *Preserve the important parts of the image* (i.e. if there were 5 trees before the resizing we want to have 5 trees after resizing as well).
- *Preserve the proportions* of the important parts of the image (i.e. circle car wheels should not be squeezed to the ellipse wheels)
To avoid changing the important parts of the image we may find the **continuous sequence of pixels (the seam)**, that goes from top to bottom and has *the lowest contribution to the content* of the image (avoids important parts) and then remove it. The seam removal will shrink the image by 1 pixel. We will then repeat this step until the image will get the desired width.
The question is how to define *the importance of the pixel* and its contribution to the content (in the original paper the authors are using the term **energy of the pixel**). One of the ways to do it is to treat all the pixels that form the edges as important ones. In case if a pixel is a part of the edge its color would have a greater difference between the neighbors (left and right pixels) than the pixel that isn't a part of the edge.
![Pixels color difference](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/30-pixel-energy-comparison.png)
Assuming that the color of a pixel is represented by *4* numbers (`R` - red, `G` - green, `B` - blue, `A` - alpha) we may use the following formula to calculate the color difference (the pixel energy):
![Pixel energy formula](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/20-energy-formula.png)
Where:
- `mEnergy` - *Energy* (importance) of the *middle* pixel (`[0..626]` if rounded)
- `lR` - *Red* channel value for the *left* pixel (`[0..255]`)
- `mR` - *Red* channel value for the *middle* pixel (`[0..255]`)
- `rR` - *Red* channel value for the *right* pixel (`[0..255]`)
- `lG` - *Green* channel value for the *left* pixel (`[0..255]`)
- and so on...
In the formula above we're omitting the alpha (transparency) channel, for now, assuming that there are no transparent pixels in the image. Later we will use the alpha channel for masking and for object removal.
![Example of pixel energy calculation](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/30-pixel-energy-calculation-example.png)
Now, since we know how to find the energy of one pixel, we can calculate, so-called, **energy map** which will contain the energies of each pixel of the image. On each resizing step the energy map should be re-calculated (at least partially, more about it below) and would have the same size as the image.
For example, on the 1st resizing step we will have a `1000 x 500` image and a `1000 x 500` energy map. On the 2nd resizing step we will remove the seam from the image and re-calculate the energy map based on the new shrunk image. Thus, we will get a `999 x 500` image and a `999 x 500` energy map.
The higher the energy of the pixel the more likely it is a part of an edge, and it is important for the image content and the less likely that we need to remove it.
To visualize the energy map we may assign a brighter color to the pixels with the higher energy and darker colors to the pixels with the lower energy. Here is an artificial example of how the random part of the energy map might look like. You may see the bright line which represents the edge and which we want to preserve during the resizing.
![Energy map sketch](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/30-energy-map-padding.png)
Here is a real example of the energy map for the demo image you saw above (with hot air balloons).
![Energy map example](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/40-energy-map.png)
You may play around with your custom images and see how the energy map would look like in the [interactive version of the post](https://trekhleb.dev/blog/2021/content-aware-image-resizing-in-javascript/).
We may use the energy map to find the seams (one after another) with the lowest energy and by doing this to decide which pixels should be ultimately deleted.
![Searching the seam](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/41-seam-search.png)
Finding the seam with the lowest energy is not a trivial task and requires exploring many possible pixel combinations before making the decision. We will apply the dynamic programming approach to speed it up.
In the example below, you may see the energy map with the first lowest energy seam that was found for it.
![Energy map example with seam](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/40-energy-map-with-seam.png)
In the examples above we were reducing the width of the image. A similar approach may be taken to reduce the image height. We need to "rotate" the approach though:
- start using *top* and *bottom* pixel neighbors (instead of *left* and *right* ones) to calculate the pixel energy
- when searching for a seam we need to move from *left* to *right* (instead of from *up* to *bottom*)
## Implementation in TypeScript
> You may find the source code, and the functions mentioned below in the [js-image-carver](https://github.com/trekhleb/js-image-carver) repository.
To implement the algorithm we will be using TypeScript. If you want a JavaScript version, you may ignore (remove) type definitions and their usages.
For simplicity reasons let's implement the seam carving algorithm only for the image *width* reduction.
### Content-aware width resizing (the entry function)
First, let's define some common types that we're going to use while implementing the algorithm.
```typescript
// Type that describes the image size (width and height).
type ImageSize = { w: number, h: number };
// The coordinate of the pixel.
type Coordinate = { x: number, y: number };
// The seam is a sequence of pixels (coordinates).
type Seam = Coordinate[];
// Energy map is a 2D array that has the same width and height
// as the image the map is being calculated for.
type EnergyMap = number[][];
// Type that describes the image pixel's RGBA color.
type Color = [
r: number, // Red
g: number, // Green
b: number, // Blue
a: number, // Alpha (transparency)
] | Uint8ClampedArray;
```
On the high level the algorithm consists of the following steps:
1. Calculate the **energy map** for the current version of the image.
2. Find the **seam** with the lowest energy based on the energy map (this is where we will apply Dynamic Programming).
3. **Delete the seam** with the lowest energy seam from the image.
4. **Repeat** until the image width is reduced to the desired value.
```typescript
type ResizeImageWidthArgs = {
img: ImageData, // Image data we want to resize.
toWidth: number, // Final image width we want the image to shrink to.
};
type ResizeImageWidthResult = {
img: ImageData, // Resized image data.
size: ImageSize, // Resized image size (w x h).
};
// Performs the content-aware image width resizing using the seam carving method.
export const resizeImageWidth = (
{ img, toWidth }: ResizeImageWidthArgs,
): ResizeImageWidthResult => {
// For performance reasons we want to avoid changing the img data array size.
// Instead we'll just keep the record of the resized image width and height separately.
const size: ImageSize = { w: img.width, h: img.height };
// Calculating the number of pixels to remove.
const pxToRemove = img.width - toWidth;
if (pxToRemove < 0) {
throw new Error('Upsizing is not supported for now');
}
let energyMap: EnergyMap | null = null;
let seam: Seam | null = null;
// Removing the lowest energy seams one by one.
for (let i = 0; i < pxToRemove; i += 1) {
// 1. Calculate the energy map for the current version of the image.
energyMap = calculateEnergyMap(img, size);
// 2. Find the seam with the lowest energy based on the energy map.
seam = findLowEnergySeam(energyMap, size);
// 3. Delete the seam with the lowest energy seam from the image.
deleteSeam(img, seam, size);
// Reduce the image width, and continue iterations.
size.w -= 1;
}
// Returning the resized image and its final size.
// The img is actually a reference to the ImageData, so technically
// the caller of the function already has this pointer. But let's
// still return it for better code readability.
return { img, size };
};
```
The image that needs to be resized is being passed to the function in [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) format. You may draw the image on the canvas and then extract the ImageData from the canvas like this:
```javascript
const ctx = canvas.getContext('2d');
const imgData = ctx.getImageData(0, 0, imgWidth, imgHeight);
```
> The way of uploading and drawing images in JavaScript is out of scope for this article, but you may find the complete source code of how it may be done using React in [js-image-carver](https://github.com/trekhleb/js-image-carver) repo.
Let's break down each step ony be one and implement the `calculateEnergyMap()`, `findLowEnergySeam()` and `deleteSeam()` functions.
### Calculating the pixel's energy
Here we apply the color difference formula described above. For the left and right borders (when there are no left or right neighbors), we ignore the neighbors and don't take them into account during the energy calculation.
```typescript
// Calculates the energy of a pixel.
const getPixelEnergy = (left: Color | null, middle: Color, right: Color | null): number => {
// Middle pixel is the pixel we're calculating the energy for.
const [mR, mG, mB] = middle;
// Energy from the left pixel (if it exists).
let lEnergy = 0;
if (left) {
const [lR, lG, lB] = left;
lEnergy = (lR - mR) ** 2 + (lG - mG) ** 2 + (lB - mB) ** 2;
}
// Energy from the right pixel (if it exists).
let rEnergy = 0;
if (right) {
const [rR, rG, rB] = right;
rEnergy = (rR - mR) ** 2 + (rG - mG) ** 2 + (rB - mB) ** 2;
}
// Resulting pixel energy.
return Math.sqrt(lEnergy + rEnergy);
};
```
### Calculating the energy map
The image we're working with has the [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) format. It means that all the pixels (and their colors) are stored in a flat (*1D*) [Uint8ClampedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray) array. For readability purposes let's introduce the couple of helper functions that will allow us to work with the Uint8ClampedArray array as with a *2D* matrix instead.
```typescript
// Helper function that returns the color of the pixel.
const getPixel = (img: ImageData, { x, y }: Coordinate): Color => {
// The ImageData data array is a flat 1D array.
// Thus we need to convert x and y coordinates to the linear index.
const i = y * img.width + x;
const cellsPerColor = 4; // RGBA
// For better efficiency, instead of creating a new sub-array we return
// a pointer to the part of the ImageData array.
return img.data.subarray(i * cellsPerColor, i * cellsPerColor + cellsPerColor);
};
// Helper function that sets the color of the pixel.
const setPixel = (img: ImageData, { x, y }: Coordinate, color: Color): void => {
// The ImageData data array is a flat 1D array.
// Thus we need to convert x and y coordinates to the linear index.
const i = y * img.width + x;
const cellsPerColor = 4; // RGBA
img.data.set(color, i * cellsPerColor);
};
```
To calculate the energy map we go through each image pixel and call the previously described `getPixelEnergy()` function against it.
```typescript
// Helper function that creates a matrix (2D array) of specific
// size (w x h) and fills it with specified value.
const matrix = <T>(w: number, h: number, filler: T): T[][] => {
return new Array(h)
.fill(null)
.map(() => {
return new Array(w).fill(filler);
});
};
// Calculates the energy of each pixel of the image.
const calculateEnergyMap = (img: ImageData, { w, h }: ImageSize): EnergyMap => {
// Create an empty energy map where each pixel has infinitely high energy.
// We will update the energy of each pixel.
const energyMap: number[][] = matrix<number>(w, h, Infinity);
for (let y = 0; y < h; y += 1) {
for (let x = 0; x < w; x += 1) {
// Left pixel might not exist if we're on the very left edge of the image.
const left = (x - 1) >= 0 ? getPixel(img, { x: x - 1, y }) : null;
// The color of the middle pixel that we're calculating the energy for.
const middle = getPixel(img, { x, y });
// Right pixel might not exist if we're on the very right edge of the image.
const right = (x + 1) < w ? getPixel(img, { x: x + 1, y }) : null;
energyMap[y][x] = getPixelEnergy(left, middle, right);
}
}
return energyMap;
};
```
> The energy map is going to be recalculated on every resize iteration. It means that it will be recalculated, let's say, 500 times if we need to shrink the image by 500 pixels which is not optimal. To speed up the energy map calculation on the 2nd, 3rd, and further steps, we may re-calculate the energy only for those pixels that are placed around the seam that is going to be removed. For simplicity reasons this optimization is omitted here, but you may find the example source-code in [js-image-carver](https://github.com/trekhleb/js-image-carver) repo.
### Finding the seam with the lowest energy (Dynamic Programming approach)
> I've described some Dynamic Programming basics in [Dynamic Programming vs Divide-and-Conquer](https://trekhleb.dev/blog/2018/dynamic-programming-vs-divide-and-conquer/) article before. There is a DP example based on the minimum edit distance problem. You might want to check it out to get some more context.
The issue we need to solve now is to find the path (the seam) on the energy map that goes from top to bottom and has the minimum sum of pixel energies.
#### The naive approach
The naive approach would be to check all possible paths one after another.
![The naive approach](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/50-naive-approach.png)
Going from top to bottom, for each pixel, we have 3 options (↙︎ go down-left, ↓ go down, ↘︎ go down-right). This gives us the time complexity of `O(w * 3^h)` or simply `O(3^h)`, where `w` and `h` are the width and the height of the image. This approach looks slow.
#### The greedy approach
We may also try to choose the next pixel as a pixel with the lowest energy, hoping that the resulting seam energy will be the smallest one.
![The greedy approach](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/51-greedy-approach.png)
This approach gives not the worst solution, but it cannot guarantee that we will find the best available solution. On the image above you may see how the greedy approach chose `5` instead of `10` at first and missed the chain of optimal pixels.
The good part about this approach is that it is fast, and it has a time complexity of `O(w + h)`, where `w` and `h` are the width and the height of the image. In this case, the cost of the speed is the low quality of resizing. We need to find a minimum value in the first row (traversing `w` cells) and then we explore only 3 neighbor pixels for each row (traversing `h` rows).
#### The dynamic programming approach
You might have noticed that in the naive approach we summed up the same pixel energies over and over again while calculating the resulting seams' energy.
![Repeated problems](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/52-dp-repeated-problems.png)
In the example above you see that for the first two seams we are re-using the energy of the shorter seam (which has the energy of `235`). Instead of doing just one operation `235 + 70` to calculate the energy of the 2nd seam we're doing four operations `(5 + 0 + 80 + 150) + 70`.
> This fact that we're re-using the energy of the previous seam to calculate the current seam's energy might be applied recursively to all the shorter seams up to the very top 1st row seam. When we have such overlapping sub-problems, [it is a sign](https://trekhleb.dev/blog/2018/dynamic-programming-vs-divide-and-conquer/) that the general problem *might* be optimized by dynamic programming approach.
So, we may **save the energy of the current seam** at the particular pixel in an additional `seamsEnergies` table to make it re-usable for calculating the next seams faster (the `seamsEnergies` table will have the same size as the energy map and the image itself).
Let's also keep in mind that for one particular pixel on the image (i.e. the bottom left one) we may have *several* values of the previous seams energies.
![What seam to choose](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/53-dp-what-to-choose.png)
Since we're looking for a seam with the lowest resulting energy it would make sense to pick the previous seam with the lowest resulting energy as well.
![Seams energies example](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/56-dp-seams-energies-example.png)
In general, we have three possible previous seems to choose from:
![Three options to choose from](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/55-dp-three-options.png)
You may think about it this way:
- The cell `[1][x]`: contains the lowest possible energy of the seam that starts somewhere on the row `[0][?]` and ends up at cell `[1][x]`
- **The current cell** `[2][3]`: contains the lowest possible energy of the seam that starts somewhere on the row `[0][?]` and ends up at cell `[2][3]`. To calculate it we need to sum up the energy of the current pixel `[2][3]` (from the energy map) with the `min(seam_energy_1_2, seam_energy_1_3, seam_energy_1_4)`
If we fill the `seamsEnergies` table completely, then the minimum number in the lowest row would be the lowest possible seam energy.
Let's try to fill several cells of this table to see how it works.
![Seams energies map traversal](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/57-dp-seams-energies-traversal.png)
After filling out the `seamsEnergies` table we may see that the lowest energy pixel has an energy of `50`. For convenience, during the `seamsEnergies` generation for each pixel, we may save not only the energy of the seam but also the coordinates of the previous lowest energy seam. This will give us the possibility to reconstruct the seam path from the bottom to the top easily.
The time complexity of DP approach would be `O(w * h)`, where `w` and `h` are the width and the height of the image. We need to calculate energies for *every* pixel of the image.
Here is an example of how this logic might be implemented:
```typescript
// The metadata for the pixels in the seam.
type SeamPixelMeta = {
energy: number, // The energy of the pixel.
coordinate: Coordinate, // The coordinate of the pixel.
previous: Coordinate | null, // The previous pixel in a seam.
};
// Finds the seam (the sequence of pixels from top to bottom) that has the
// lowest resulting energy using the Dynamic Programming approach.
const findLowEnergySeam = (energyMap: EnergyMap, { w, h }: ImageSize): Seam => {
// The 2D array of the size of w and h, where each pixel contains the
// seam metadata (pixel energy, pixel coordinate and previous pixel from
// the lowest energy seam at this point).
const seamsEnergies: (SeamPixelMeta | null)[][] = matrix<SeamPixelMeta | null>(w, h, null);
// Populate the first row of the map by just copying the energies
// from the energy map.
for (let x = 0; x < w; x += 1) {
const y = 0;
seamsEnergies[y][x] = {
energy: energyMap[y][x],
coordinate: { x, y },
previous: null,
};
}
// Populate the rest of the rows.
for (let y = 1; y < h; y += 1) {
for (let x = 0; x < w; x += 1) {
// Find the top adjacent cell with minimum energy.
// This cell would be the tail of a seam with lowest energy at this point.
// It doesn't mean that this seam (path) has lowest energy globally.
// Instead, it means that we found a path with the lowest energy that may lead
// us to the current pixel with the coordinates x and y.
let minPrevEnergy = Infinity;
let minPrevX: number = x;
for (let i = (x - 1); i <= (x + 1); i += 1) {
if (i >= 0 && i < w && seamsEnergies[y - 1][i].energy < minPrevEnergy) {
minPrevEnergy = seamsEnergies[y - 1][i].energy;
minPrevX = i;
}
}
// Update the current cell.
seamsEnergies[y][x] = {
energy: minPrevEnergy + energyMap[y][x],
coordinate: { x, y },
previous: { x: minPrevX, y: y - 1 },
};
}
}
// Find where the minimum energy seam ends.
// We need to find the tail of the lowest energy seam to start
// traversing it from its tail to its head (from the bottom to the top).
let lastMinCoordinate: Coordinate | null = null;
let minSeamEnergy = Infinity;
for (let x = 0; x < w; x += 1) {
const y = h - 1;
if (seamsEnergies[y][x].energy < minSeamEnergy) {
minSeamEnergy = seamsEnergies[y][x].energy;
lastMinCoordinate = { x, y };
}
}
// Find the lowest energy energy seam.
// Once we know where the tail is we may traverse and assemble the lowest
// energy seam based on the "previous" value of the seam pixel metadata.
const seam: Seam = [];
if (!lastMinCoordinate) {
return seam;
}
const { x: lastMinX, y: lastMinY } = lastMinCoordinate;
// Adding new pixel to the seam path one by one until we reach the top.
let currentSeam = seamsEnergies[lastMinY][lastMinX];
while (currentSeam) {
seam.push(currentSeam.coordinate);
const prevMinCoordinates = currentSeam.previous;
if (!prevMinCoordinates) {
currentSeam = null;
} else {
const { x: prevMinX, y: prevMinY } = prevMinCoordinates;
currentSeam = seamsEnergies[prevMinY][prevMinX];
}
}
return seam;
};
```
### Removing the seam with the lowest energy
Once we found the lowest energy seam, we need to remove (to carve) the pixels that form it from the image. The removal is happening by shifting the pixels to the right of the seam by `1px` to the left. For performance reasons, we don't actually delete the last columns. Instead, the rendering component will just ignore the part of the image that lays beyond the resized image width.
![Deleting the seam](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/60-deleting-example.png)
```typescript
// Deletes the seam from the image data.
// We delete the pixel in each row and then shift the rest of the row pixels to the left.
const deleteSeam = (img: ImageData, seam: Seam, { w }: ImageSize): void => {
seam.forEach(({ x: seamX, y: seamY }: Coordinate) => {
for (let x = seamX; x < (w - 1); x += 1) {
const nextPixel = getPixel(img, { x: x + 1, y: seamY });
setPixel(img, { x, y: seamY }, nextPixel);
}
});
};
```
## Objects removal
The Seam Carving algorithm tries to remove the seams which consist of low energy pixels first. We could leverage this fact and by assigning low energy to some pixels manually (i.e. by drawing on the image and masking out some areas of it) we could make the Seam Carving algorithm to do *objects removal* for us for free.
Currently, in `getPixelEnergy()` function we were using only the `R`, `G`, `B` color channels to calculate the pixel's energy. But there is also the `A` (alpha, transparency) parameter of the color that we didn't use yet. We may use the transparency channel to tell the algorithm that transparent pixels are the pixels we want to remove. You may check the [source-code of the energy function](https://github.com/trekhleb/js-image-carver/blob/main/src/utils/contentAwareResizer.ts#L54) that takes transparency into account.
Here is how the algorithm works for object removal.
![JS IMAGE CARVER OBJECT REMOVAL DEMO](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/10-demo-02.gif)
## Issues and what's next
The [JS IMAGE CARVER](https://github.com/trekhleb/js-image-carver) web app is far from being a production ready resizer of course. Its main purpose was to experiment with the Seam Carving algorithm interactively. So the plan for the future is to continue experimentation.
The [original paper](https://perso.crans.org/frenoy/matlab2012/seamcarving.pdf) describes how the Seam Carving algorithm might be used not only for the downscaling but also for the **upscaling of the images**. The upscaling, in turn, might be used to **upscale the image back to its original width after the objects' removal**.
Another interesting area of experimentation might be to make the algorithm work in a **real-time**.
> Those are the plans for the future, but for now, I hope that the example with image downsizing was interesting and useful for you. I also hope that you've got the idea of using dynamic programming to implement it.
>
> So, good luck with your own experiments!

View File

@ -0,0 +1,509 @@
# Изменение размеров изображения с учетом его содержимого в JavaScript
![Content-aware image resizing in JavaScript](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/01-cover-02.png)
> Доступна [английская интерактивная версия этой статьи](https://trekhleb.dev/blog/2021/content-aware-image-resizing-in-javascript/) в которой вы можете загрузить свои собственные изображения и посмотреть, как алгоритм "справляется" с ними.
## TL;DR
Написано много замечательных статей об алгоритме *Seam Carving* ("Вырезание швов"), но я не смог устоять перед соблазном самостоятельно исследовать этот элегантный, мощный и в то же время простой алгоритм и написать о своем личном опыте работы с ним. Мое внимание также привлек тот факт, что для его имплементации мы можем применить *динамическое программирование (DP)*. И, если вы, как и я, все еще находитесь на пути изучения алгоритмов, то это решение может обогатить ваш личный арсенал DP.
Итак, в этой статье я хочу сделать три вещи:
1. Предоставить вам возможность "поиграться" с алгоритмом самостоятельно при помощи **интерактивного ресайзера**.
2. Объяснить **идею алгоритма Seam Carving**.
3. Объяснить как можно **применить динамическое программирование** для имплементации алгоритма (мы будем писать на TypeScript).
### Изменение размеров изображений с учетом их содержимого
*Изменение размера изображения с учетом содержимого* (content-aware image resizing) может быть применено, когда дело доходит до изменения пропорций изображения (например, уменьшения ширины при сохранении высоты), а также когда потеря некоторых частей изображения нежелательна. Простое масштабирование изображения в этом случае исказит находящиеся в нем объекты. Для сохранения пропорций объектов при изменении пропорций изображения можно использовать [алгоритм Seam Carving](https://perso.crans.org/frenoy/matlab2012/seamcarving.pdf), который был описан *Shai Avidan* и *Ariel Shamir*.
В приведенном ниже примере показано, как ширина исходного изображения была уменьшена на 50% *с учетом содержимого изображения* (слева) и *без учета содержимого изображения* (справа, простой скейлинг). В данном случае левое изображение выглядит более естественным, так как пропорции воздушных шаров в нем были сохранены.
![Content-aware image resizing](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/01-resizing-options.png)
Идея алгоритма Seam Carving заключается в том, чтобы найти *шов* (seam, непрерывную последовательность пикселей) с наименьшим влиянием на содержание изображения, а затем его *вырезать* (carve). Этот процесс повторяется снова и снова, пока мы не получим требуемую ширину или высоту изображения. В примере ниже интуитивно видно, что пиксели воздушных шаров вносят больший "вклад" в содержание и смысл изображения, чем пиксели неба. Таким образом, сначала удаляются пиксели неба.
![JS IMAGE CARVER DEMO](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/10-demo-01.gif)
Поиск шва с наименьшей энергией (с наименьшим вкладом в содержимое изображения) является вычислительно дорогостоящей операцией (особенно для больших изображений). Для ускорения поиска шва может быть применено *динамическое программирование* (мы рассмотрим детали реализации ниже).
### Удаление объектов
Важность каждого пикселя (так называемая энергия пикселя) вычисляется исходя из его цветовой разницы (`R`, `G`, `B`, `A`) между двумя соседними пикселями. Если же мы вручную зададим некоторым пикселям низкий уровень энергии (например нарисовав маску поверх них), то алгоритм Seam Carving выполнит для нас **удаление помеченного объекта**, как говорится, "забесплатно".
![JS IMAGE CARVER OBJECT REMOVAL DEMO](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/10-demo-02.gif)
### Интерактивный ресзайзер
Для этой статьи я создал приложение [JS IMAGE CARVER](https://trekhleb.dev/js-image-carver/) (доступен также и [исходный код на GitHub](https://github.com/trekhleb/js-image-carver)), которым вы можете воспользоваться для ресайза своих изображений и увидеть в реальном времени, как работает алгоритм.
### Другие примеры ресайза
Вот еще несколько примеров того, как алгоритм справляется с более сложным фоном.
Горы на заднем плане плавно сжимаются, без видимых швов.
![Resizing demo with more complex backgrounds](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/11-demo-01.png)
То же самое и с океанскими волнами. Алгоритм сохранил волновую структуру, не искажая серферов.
![Resizing demo with more complex backgrounds](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/11-demo-02.png)
Нужно помнить, что алгоритм Seam Carving не является "волшебной таблеткой", и может не сохранить пропорции важных частей изображения в том случае, когда *большая часть пикселей выглядят как края, ребра или границы* (почти все пиксели выглядят одинаково важными с точки зрения алгоритма). В приведенном ниже примере изменение размера изображения с учетом содержимого похоже на простое масштабирование, т.к. для алгоритма все пиксели выглядят важными, и ему трудно отличить лицо Ван Гога от фона.
![Example when the algorithm does not work as expected](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/12-demo-01.png)
## Как работает алгоритм Seam Carving
Представим, что у нас есть картинка размером `1000 x 500 px`, и мы хотим уменьшить ее до `500 x 500 px` (допустим, квадратное изображение больше подходит для Instagram). В этом случае мы, возможно, захотим задать несколько **требований к процессу изменения размера**:
- *Важные части изображения должны быть сохранены* (если до ресайза на фото было 5 деревьев, то и после ресайза мы хотим увидеть все те же 5 деревьев).
- *Пропорции важных частей изображения должны быть сохранены* (круглые колеса автомобиля не должны стать овальными после ресайза).
Чтобы избежать изменения важных частей изображения можно найти **непрерывную последовательность пикселей (шов)**, которая будет идти от верхней границы к нижней и иметь *наименьший вклад в содержимое* изображения (шов, который не проходит через важные части изображения), а затем удалить его. Удаление шва сожмет изображение на один пиксель. Далее надо повторять этот шаг до тех пор, пока изображение не станет нужной ширины.
Вопрос в том, как определить *важность пикселя* и его вклад в содержание изображения (в оригинальной статье авторы используют термин **энергия пикселя**). Один из способов это сделать — рассматривать все пиксели, образующие края (границы, ребра), как важные. В случае, если пиксель является частью ребра, его цвет будет отличаться от соседей (левого и правого пикселей).
![Pixels color difference](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/30-pixel-energy-comparison.png)
Предполагая, что цвет пикселя представлен *4-мя* числами (`R` - красный, `G` - зеленый, `B` - синий, `A` - альфа, прозрачность), мы можем использовать следующую формулу для вычисления разницы в цвете (энергии пикселя):
![Pixel energy formula](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/20-energy-formula.png)
Где:
- `mEnergy` - *Энергия* (важность) *среднего* пикселя (`[0..626]` если округлить)
- `lR` - *Красный* цветовой канал *левого* пикселя (`[0..255]`)
- `mR` - *Красный* цветовой канал *среднего* пикселя (`[0..255]`)
- `rR` - *Красный* цветовой канал *правого* пикселя (`[0..255]`)
- `lG` - *Зеленый* цветовой канал *левого* пикселя (`[0..255]`)
- и так далее...
В приведенной выше формуле мы пока не используем альфа-канал (прозрачность), предполагая, что изображение не содержит прозрачные пиксели. Позже мы будем использовать альфа-канал для маскировки и удаления объектов.
![Example of pixel energy calculation](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/30-pixel-energy-calculation-example.png)
Поскольку мы знаем, как найти энергию одного пикселя, мы можем вычислить так называемую **энергетическую карту**, которая будет содержать энергии каждого пикселя изображения. На каждом шаге изменения размера изображения карту энергий необходимо пересчитывать (по крайней мере частично, подробнее об этом ниже), и она будет иметь тот же размер, что и изображение.
Например, на 1-м шаге у нас будет изображение размером `1000 x 500` и энергетическая карта размером `1000 x 500`. На 2-м шаге изменения размера мы удалим шов с изображения и пересчитаем карту энергий на основе нового уменьшенного изображения. Таким образом, мы получим изображение размером `999 x 500` и карту энергий размером `999 x 500`.
Чем выше энергия пикселя, тем больше вероятность того, что он является частью ребра, важен для содержимого изображения и тем меньше вероятность того, что нам потребуется его удалить.
Для визуализации карты энергий мы можем присвоить более яркий цвет пикселям с большей энергией и более темные цвета пикселям с меньшей энергией. Вот как может выглядеть часть карты энергий. Вы можете увидеть светлую линию, которая представляет край и которую мы хотим сохранить при изменении размера.
![Energy map sketch](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/30-energy-map-padding.png)
Вот реальный пример энергетической карты для изображения, которое вы видели выше (с воздушными шарами).
![Energy map example](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/40-energy-map.png)
Вы можете загрузить свое изображение и посмотреть, как будет выглядеть энергетическая карта в [интерактивной версии статьи](https://trekhleb.dev/blog/2021/content-aware-image-resizing-in-javascript/).
Мы можем использовать энергетическую карту, чтобы найти швы (один за другим) с наименьшей энергией и тем самым решить, какие пиксели в конечном итоге должны быть удалены.
![Searching the seam](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/41-seam-search.png)
Поиск шва с наименьшими затратами энергии не является тривиальной задачей и требует перебора множества возможных комбинаций пикселей. Мы применим динамическое программирование для оптимизации поиска шва.
В примере ниже вы можете увидеть карту энергий с первым найденным для нее швом с наименьшей энергией.
![Energy map example with seam](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/40-energy-map-with-seam.png)
В приведенных выше примерах мы уменьшали ширину изображения. Аналогичный подход может быть использован для уменьшения высоты изображения. Для этого нам нужно:
- начать использовать соседей *сверху* и *снизу*, а не *слева* и *справа*, для вычисления энергии пикселя
- при поиске шва нам нужно двигаться *слева* *направо*, а не *сверху* *вниз*.
## Реализация алгоритма на TypeScript
> Исходный код и функции, упомянутые ниже, можно найти в репозитории [js-image-carver](https://github.com/trekhleb/js-image-carver).
Для реализации алгоритма мы будем использовать TypeScript. Если вам нужна версия на JavaScript, вы можете игнорировать (удалить) определения типов и их использование.
Для простоты примеров мы напишем код только для уменьшения *ширины* изображения.
### Уменьшение ширины с учетом содержимого изображения (исходная функция)
Для начала определим некоторые общие типы, которые мы будем использовать при реализации алгоритма.
```typescript
// Type that describes the image size (width and height).
type ImageSize = { w: number, h: number };
// The coordinate of the pixel.
type Coordinate = { x: number, y: number };
// The seam is a sequence of pixels (coordinates).
type Seam = Coordinate[];
// Energy map is a 2D array that has the same width and height
// as the image the map is being calculated for.
type EnergyMap = number[][];
// Type that describes the image pixel's RGBA color.
type Color = [
r: number, // Red
g: number, // Green
b: number, // Blue
a: number, // Alpha (transparency)
] | Uint8ClampedArray;
```
Для имплементации алгоритма нам необходимо выполнить следующие шаги:
1. Рассчитать **карту энергии** для текущей версии изображения.
2. Найти **шов** с наименьшей энергией на основе карты энергий (здесь мы применим динамическое программирование).
3. **Удалить шов** с наименьшей энергией из изображения.
4. **Повторять** до тех пор, пока ширина изображения не будет уменьшена до нужного значения.
```typescript
type ResizeImageWidthArgs = {
img: ImageData, // Image data we want to resize.
toWidth: number, // Final image width we want the image to shrink to.
};
type ResizeImageWidthResult = {
img: ImageData, // Resized image data.
size: ImageSize, // Resized image size (w x h).
};
// Performs the content-aware image width resizing using the seam carving method.
export const resizeImageWidth = (
{ img, toWidth }: ResizeImageWidthArgs,
): ResizeImageWidthResult => {
// For performance reasons we want to avoid changing the img data array size.
// Instead we'll just keep the record of the resized image width and height separately.
const size: ImageSize = { w: img.width, h: img.height };
// Calculating the number of pixels to remove.
const pxToRemove = img.width - toWidth;
if (pxToRemove < 0) {
throw new Error('Upsizing is not supported for now');
}
let energyMap: EnergyMap | null = null;
let seam: Seam | null = null;
// Removing the lowest energy seams one by one.
for (let i = 0; i < pxToRemove; i += 1) {
// 1. Calculate the energy map for the current version of the image.
energyMap = calculateEnergyMap(img, size);
// 2. Find the seam with the lowest energy based on the energy map.
seam = findLowEnergySeam(energyMap, size);
// 3. Delete the seam with the lowest energy seam from the image.
deleteSeam(img, seam, size);
// Reduce the image width, and continue iterations.
size.w -= 1;
}
// Returning the resized image and its final size.
// The img is actually a reference to the ImageData, so technically
// the caller of the function already has this pointer. But let's
// still return it for better code readability.
return { img, size };
};
```
Изображение, которому необходимо изменить размер, передается в функцию в формате [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData). Вы можете отобразить изображение на canvas-е, а затем извлечь ImageData из того же canvas-а следующим образом:
```javascript
const ctx = canvas.getContext('2d');
const imgData = ctx.getImageData(0, 0, imgWidth, imgHeight);
```
> Загрузка и отрисовка изображений в JavaScript выходит за рамки данной статьи, но вы можете найти полный исходный код того, как это можно сделать с помощью React в репозитории [js-image-carver](https://github.com/trekhleb/js-image-carver).
Теперь, пошагово реализуем функции `calculateEnergyMap()`, `findLowEnergySeam()` и `deleteSeam()`.
### Расчет энергии пикселя
Для расчета воспользуемся формулой разницы цветов, описанной выше. Для левой и правой краев изображения (когда нет левого или правого соседей) мы игнорируем соседей и не учитываем их при расчете энергии.
```typescript
// Calculates the energy of a pixel.
const getPixelEnergy = (left: Color | null, middle: Color, right: Color | null): number => {
// Middle pixel is the pixel we're calculating the energy for.
const [mR, mG, mB] = middle;
// Energy from the left pixel (if it exists).
let lEnergy = 0;
if (left) {
const [lR, lG, lB] = left;
lEnergy = (lR - mR) ** 2 + (lG - mG) ** 2 + (lB - mB) ** 2;
}
// Energy from the right pixel (if it exists).
let rEnergy = 0;
if (right) {
const [rR, rG, rB] = right;
rEnergy = (rR - mR) ** 2 + (rG - mG) ** 2 + (rB - mB) ** 2;
}
// Resulting pixel energy.
return Math.sqrt(lEnergy + rEnergy);
};
```
### Расчет энергетической карты
Изображение, с которым мы работаем, имеет формат [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData). Это означает, что все пиксели (и их цвета) хранятся в одномерном массиве [Uint8ClampedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray). Для удобства чтения введем пару вспомогательных функций, которые позволят работать с массивом Uint8ClampedArray как с *2D* матрицей.
```typescript
// Helper function that returns the color of the pixel.
const getPixel = (img: ImageData, { x, y }: Coordinate): Color => {
// The ImageData data array is a flat 1D array.
// Thus we need to convert x and y coordinates to the linear index.
const i = y * img.width + x;
const cellsPerColor = 4; // RGBA
// For better efficiency, instead of creating a new sub-array we return
// a pointer to the part of the ImageData array.
return img.data.subarray(i * cellsPerColor, i * cellsPerColor + cellsPerColor);
};
// Helper function that sets the color of the pixel.
const setPixel = (img: ImageData, { x, y }: Coordinate, color: Color): void => {
// The ImageData data array is a flat 1D array.
// Thus we need to convert x and y coordinates to the linear index.
const i = y * img.width + x;
const cellsPerColor = 4; // RGBA
img.data.set(color, i * cellsPerColor);
};
```
Для вычисления карты энергии мы проходим через каждый пиксель изображения и вызываем для него описанную ранее функцию `getPixelEnergy()`.
```typescript
// Helper function that creates a matrix (2D array) of specific
// size (w x h) and fills it with specified value.
const matrix = <T>(w: number, h: number, filler: T): T[][] => {
return new Array(h)
.fill(null)
.map(() => {
return new Array(w).fill(filler);
});
};
// Calculates the energy of each pixel of the image.
const calculateEnergyMap = (img: ImageData, { w, h }: ImageSize): EnergyMap => {
// Create an empty energy map where each pixel has infinitely high energy.
// We will update the energy of each pixel.
const energyMap: number[][] = matrix<number>(w, h, Infinity);
for (let y = 0; y < h; y += 1) {
for (let x = 0; x < w; x += 1) {
// Left pixel might not exist if we're on the very left edge of the image.
const left = (x - 1) >= 0 ? getPixel(img, { x: x - 1, y }) : null;
// The color of the middle pixel that we're calculating the energy for.
const middle = getPixel(img, { x, y });
// Right pixel might not exist if we're on the very right edge of the image.
const right = (x + 1) < w ? getPixel(img, { x: x + 1, y }) : null;
energyMap[y][x] = getPixelEnergy(left, middle, right);
}
}
return energyMap;
};
```
> Карта энергии будет пересчитываться при каждой итерации изменения размера. Это значит, что она будет пересчитываться, скажем, 500 раз, если нам нужно будет уменьшить изображение на 500 пикселей, что выглядит неоптимально. Чтобы ускорить вычисление карты энергии на 2-м, 3-м и последующих этапах, мы можем пересчитать энергию только для тех пикселей, которые расположены вокруг шва, который будет удален. Для простоты эта оптимизация здесь пропущена, но пример с исходным кодом можно найти в репозитории [js-image-carver](https://github.com/trekhleb/js-image-carver).
### Нахождение шва с минимальной энергией (применяем динамическое программирование)
> В статье [Dynamic Programming vs Divide-and-Conquer](https://trekhleb.dev/blog/2018/dynamic-programming-vs-divide-and-conquer/) я описывал некоторые аспекты динамического программирования на примере нахождения "расстояния Левенштейна" (преобразование одной строки в другую). Возможно она будет полезна для ознакомления.
Проблема, которую нам необходимо решить заключается в нахождении пути (шва) на энергетической карте, который идет от верхней границы изображения к нижней и имеет минимальную энергию (сумма энергий пикселей, составляющих шов должна быть минимальной).
#### "Наивный" подход (naive)
Прямолинейный ("наивный") подход — перебрать все возможные пути один за другим.
![The naive approach](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/50-naive-approach.png)
Двигаясь сверху вниз, для каждого пикселя у нас есть 3 варианта (↙︎ идти вниз-влево, ↓ вниз, ↘︎ идти вниз-вправо). Это дает нам временную сложность `O (w * 3 ^ h)` или просто `O (3 ^ h)`, где `w` и` h` - ширина и высота изображения. Такой подход выглядит неоптимальным.
#### "Жадный" подход (greedy)
Жадный подход — выбирать следующий пиксель как пиксель с наименьшей энергией, надеясь, что результирующая энергия шва будет наименьшей.
![The greedy approach](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/51-greedy-approach.png)
Жадный подход приведет нас к не самому худшему решению, но он не сможет гарантировать, что мы найдем наилучшее доступное решение. На картинке выше видно, как мы выбрали `5` вместо `10` и пропустили цепочку оптимальных пикселей.
Плюс этого подхода в том, что он быстрый и имеет временную сложность `O(w + h)`, где `w` и `h` - это ширина и высота изображения. В этом случае плата за скорость — низкое качество ресайза (много искажений). Временная сложность обусловлена тем, что нужно найти минимальное значение в первом ряду (обход `w` ячеек), а затем исследовать только 3 соседних пикселя для каждого ряда (обход `h` рядов).
#### Используем динамическое программирование
Вы, наверное, заметили, что в наивном подходе мы снова и снова суммировали одни и те же энергии пикселей, вычисляя энергию образовавшихся швов.
![Repeated problems](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/52-dp-repeated-problems.png)
В примере выше видно, что для первых двух швов мы повторно используем энергию более короткого шва (который имеет энергию `235`). Вместо одной операции `235 + 70` для вычисления энергии 2-го шва мы делаем четыре операции `(5 + 0 + 80 + 150) + 70`.
> Тот факт, что мы повторно используем энергию предыдущего шва для вычисления энергии текущего шва, может быть применен рекурсивно ко всем более коротким швам до самого верхнего 1-го ряда. Когда у нас есть такие перекрывающиеся под-проблемы, [это признак](https://trekhleb.dev/blog/2018/dynamic-programming-vs-divide-and-conquer/), что общая задача *может* быть оптимизирована с использованием динамического программирования.
Таким образом, мы можем **сохранить энергию текущего шва** для конкретного пикселя в дополнительной таблице `samsEnergies`, чтобы повторно использовать ее при расчете энергии следующих швов (таблица `samsEnergies` будет иметь тот же размер, что и энергетическая карта и само изображение).
Обратите также внимание, что для большинства пикселей в изображении (например, для левого нижнего) мы можем иметь *несколько* значений энергий предыдущих швов.
![What seam to choose](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/53-dp-what-to-choose.png)
Так как мы ищем шов с наименьшей результирующей энергией, имеет смысл выбирать и предыдущий шов с наименьшей результирующей энергией.
![Seams energies example](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/56-dp-seams-energies-example.png)
Как правило, у нас есть три возможных предыдущих шва, которые текущий пиксель продолжает:
![Three options to choose from](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/55-dp-three-options.png)
Можем посмотреть на это с такой стороны:
- Ячейка `[1][x]`: содержит наименьшую возможную энергию шва, который начинается где-то в ряду `[0][?] ` и заканчивается в ячейке `[1][x]`.
- **Текущая ячейка** `[2][3]`: содержит наименьшую возможную энергию шва, который начинается где-то в ряду `[0][?] ` и заканчивается в ячейке `[2][3]`. Для вычисления нужно суммировать энергию текущего пикселя `[2][3]` (из энергетической карты) с `min(seam_energy_1_2, seam_energy_1_3, seam_energy_1_4)`.
Если мы заполним таблицу `ShesamsEnergies` полностью, то минимальное число в нижнем ряду будет наименьшей возможной энергией шва.
Попробуем заполнить несколько ячеек этой таблицы, чтобы посмотреть, как это работает.
![Seams energies map traversal](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/57-dp-seams-energies-traversal.png)
После заполнения таблицы `ShesamsEnergies` видно, что в нижнем ряду пиксель с самой низкой энергией имеет значение `50`. Для удобства во время генерации `samsEnergies` для каждого пикселя мы можем сохранить не только энергию шва, но и координаты предыдущего шва с наименьшей энергией. Это даст нам возможность легко восстанавливать траекторию шва снизу вверх.
Временная сложность DP подхода составит `O(w * h)`, где `w` и `h` - это ширина и высота изображения. Обусловлена она тем, что нужно вычислить энергии для *всех* пикселей изображения.
Вот пример того, как эта логика может быть реализована:
```typescript
// The metadata for the pixels in the seam.
type SeamPixelMeta = {
energy: number, // The energy of the pixel.
coordinate: Coordinate, // The coordinate of the pixel.
previous: Coordinate | null, // The previous pixel in a seam.
};
// Finds the seam (the sequence of pixels from top to bottom) that has the
// lowest resulting energy using the Dynamic Programming approach.
const findLowEnergySeam = (energyMap: EnergyMap, { w, h }: ImageSize): Seam => {
// The 2D array of the size of w and h, where each pixel contains the
// seam metadata (pixel energy, pixel coordinate and previous pixel from
// the lowest energy seam at this point).
const seamsEnergies: (SeamPixelMeta | null)[][] = matrix<SeamPixelMeta | null>(w, h, null);
// Populate the first row of the map by just copying the energies
// from the energy map.
for (let x = 0; x < w; x += 1) {
const y = 0;
seamsEnergies[y][x] = {
energy: energyMap[y][x],
coordinate: { x, y },
previous: null,
};
}
// Populate the rest of the rows.
for (let y = 1; y < h; y += 1) {
for (let x = 0; x < w; x += 1) {
// Find the top adjacent cell with minimum energy.
// This cell would be the tail of a seam with lowest energy at this point.
// It doesn't mean that this seam (path) has lowest energy globally.
// Instead, it means that we found a path with the lowest energy that may lead
// us to the current pixel with the coordinates x and y.
let minPrevEnergy = Infinity;
let minPrevX: number = x;
for (let i = (x - 1); i <= (x + 1); i += 1) {
if (i >= 0 && i < w && seamsEnergies[y - 1][i].energy < minPrevEnergy) {
minPrevEnergy = seamsEnergies[y - 1][i].energy;
minPrevX = i;
}
}
// Update the current cell.
seamsEnergies[y][x] = {
energy: minPrevEnergy + energyMap[y][x],
coordinate: { x, y },
previous: { x: minPrevX, y: y - 1 },
};
}
}
// Find where the minimum energy seam ends.
// We need to find the tail of the lowest energy seam to start
// traversing it from its tail to its head (from the bottom to the top).
let lastMinCoordinate: Coordinate | null = null;
let minSeamEnergy = Infinity;
for (let x = 0; x < w; x += 1) {
const y = h - 1;
if (seamsEnergies[y][x].energy < minSeamEnergy) {
minSeamEnergy = seamsEnergies[y][x].energy;
lastMinCoordinate = { x, y };
}
}
// Find the lowest energy energy seam.
// Once we know where the tail is we may traverse and assemble the lowest
// energy seam based on the "previous" value of the seam pixel metadata.
const seam: Seam = [];
if (!lastMinCoordinate) {
return seam;
}
const { x: lastMinX, y: lastMinY } = lastMinCoordinate;
// Adding new pixel to the seam path one by one until we reach the top.
let currentSeam = seamsEnergies[lastMinY][lastMinX];
while (currentSeam) {
seam.push(currentSeam.coordinate);
const prevMinCoordinates = currentSeam.previous;
if (!prevMinCoordinates) {
currentSeam = null;
} else {
const { x: prevMinX, y: prevMinY } = prevMinCoordinates;
currentSeam = seamsEnergies[prevMinY][prevMinX];
}
}
return seam;
};
```
### Удаление шва с минимальной энергией
Как только мы нашли шов с наименьшей суммарной энергией, нам нужно удалить (вырезать) пиксели, которые образуют его из изображения. Удаление происходит путем смещения пикселей справа от шва на `1px` влево. Из соображений производительности мы не будем удалять крайний столбик пикселей. Вместо этого, компонент, отвечающий за отрисовку уменьшенного изображения просто проигнорирует ту часть изображения, которая лежит за пределами обрезанной ширины.
![Deleting the seam](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/60-deleting-example.png)
```typescript
// Deletes the seam from the image data.
// We delete the pixel in each row and then shift the rest of the row pixels to the left.
const deleteSeam = (img: ImageData, seam: Seam, { w }: ImageSize): void => {
seam.forEach(({ x: seamX, y: seamY }: Coordinate) => {
for (let x = seamX; x < (w - 1); x += 1) {
const nextPixel = getPixel(img, { x: x + 1, y: seamY });
setPixel(img, { x, y: seamY }, nextPixel);
}
});
};
```
## Удаление объектов с изображения
Seam Carving алгоритм пытается сначала удалить швы, состоящие из низкоэнергетических пикселей. Мы могли бы использовать этот факт и, присвоив низкую энергию некоторым пикселям вручную (например, нарисовав на изображении маску), мы могли бы заставить алгоритм удалить отмеченные пиксели (*объекты*).
В настоящее время в функции `getPixelEnergy()` мы используем только каналы цветов `R`, `G`, `B` для вычисления энергии пикселей. Но есть еще и параметр `A` (альфа, прозрачность), который мы не использовали. Мы можем использовать канал прозрачности, чтобы "сказать" алгоритму, что прозрачные пиксели — это те пиксели, которые мы хотим удалить. Вы можете ознакомиться с [исходным кодом функции getPixelEnergy()](https://github.com/trekhleb/js-image-carver/blob/main/src/utils/contentAwareResizer.ts#L54), которая учитывает прозрачность.
Вот как при этом будет выглядеть удаление объектов:
![JS IMAGE CARVER OBJECT REMOVAL DEMO](https://raw.githubusercontent.com/trekhleb/trekhleb.github.io/master/src/posts/2021/content-aware-image-resizing-in-javascript/assets/10-demo-02.gif)
## Проблемы алгоритма и дальнейшие планы
Приложение [JS IMAGE CARVER](https://github.com/trekhleb/js-image-carver) далеко от идеала и не является приложением production-ready качества. Основной его целью была возможность интерактивного экспериментирования с алгоритмом. Поэтому в дальнейших планах — использовать его именно для экспериментов.
В [оригинальной статье](https://perso.crans.org/frenoy/matlab2012/seamcarving.pdf) описывается, как алгоритм может быть использован не только для уменьшения, но и для **увеличения изображения**. Увеличение (расширение) изображения, в свою очередь, может быть использовано для **автоматического расширения изображения до его исходной ширины после удаления объектов**.
Еще одной интересной областью экспериментов может быть попытка ускорить алгоритм, чтобы он работал в режиме **реального времени**.
> Таковы планы на будущее, но пока, надеюсь, пример с уменьшением изображения был интересен и полезен для вас. Также надеюсь, что вам было интересно увидеть применение динамического программирования в задачах, приближенных к реальности.
>
> Удачи с вашими собственными экспериментами!

View File

@ -0,0 +1,91 @@
import { createCanvas, loadImage } from 'canvas';
import resizeImageWidth from '../resizeImageWidth';
const testImageBeforePath = './src/algorithms/image-processing/seam-carving/__tests__/test-image-before.jpg';
const testImageAfterPath = './src/algorithms/image-processing/seam-carving/__tests__/test-image-after.jpg';
/**
* Compares two images and finds the number of different pixels.
*
* @param {ImageData} imgA - ImageData for the first image.
* @param {ImageData} imgB - ImageData for the second image.
* @param {number} threshold - Color difference threshold [0..255]. Smaller - stricter.
* @returns {number} - Number of different pixels.
*/
function pixelsDiff(imgA, imgB, threshold = 0) {
if (imgA.width !== imgB.width || imgA.height !== imgB.height) {
throw new Error('Images must have the same size');
}
let differentPixels = 0;
const numColorParams = 4; // RGBA
for (let pixelIndex = 0; pixelIndex < imgA.data.length; pixelIndex += numColorParams) {
// Get pixel's color for each image.
const [aR, aG, aB] = imgA.data.subarray(pixelIndex, pixelIndex + numColorParams);
const [bR, bG, bB] = imgB.data.subarray(pixelIndex, pixelIndex + numColorParams);
// Get average pixel's color for each image (make them greyscale).
const aAvgColor = Math.floor((aR + aG + aB) / 3);
const bAvgColor = Math.floor((bR + bG + bB) / 3);
// Compare pixel colors.
if (Math.abs(aAvgColor - bAvgColor) > threshold) {
differentPixels += 1;
}
}
return differentPixels;
}
describe('resizeImageWidth', () => {
it('should perform content-aware image width reduction', () => {
// @see: https://jestjs.io/docs/asynchronous
return Promise.all([
loadImage(testImageBeforePath),
loadImage(testImageAfterPath),
]).then(([imgBefore, imgAfter]) => {
// Original image.
const canvasBefore = createCanvas(imgBefore.width, imgBefore.height);
const ctxBefore = canvasBefore.getContext('2d');
ctxBefore.drawImage(imgBefore, 0, 0, imgBefore.width, imgBefore.height);
const imgDataBefore = ctxBefore.getImageData(0, 0, imgBefore.width, imgBefore.height);
// Resized image saved.
const canvasAfter = createCanvas(imgAfter.width, imgAfter.height);
const ctxAfter = canvasAfter.getContext('2d');
ctxAfter.drawImage(imgAfter, 0, 0, imgAfter.width, imgAfter.height);
const imgDataAfter = ctxAfter.getImageData(0, 0, imgAfter.width, imgAfter.height);
const toWidth = Math.floor(imgBefore.width / 2);
const {
img: resizedImg,
size: resizedSize,
} = resizeImageWidth({ img: imgDataBefore, toWidth });
expect(resizedImg).toBeDefined();
expect(resizedSize).toBeDefined();
// Resized image generated.
const canvasTest = createCanvas(resizedSize.w, resizedSize.h);
const ctxTest = canvasTest.getContext('2d');
ctxTest.putImageData(resizedImg, 0, 0, 0, 0, resizedSize.w, resizedSize.h);
const imgDataTest = ctxTest.getImageData(0, 0, resizedSize.w, resizedSize.h);
expect(resizedSize).toEqual({ w: toWidth, h: imgBefore.height });
expect(imgDataTest.width).toBe(toWidth);
expect(imgDataTest.height).toBe(imgBefore.height);
expect(imgDataTest.width).toBe(imgAfter.width);
expect(imgDataTest.height).toBe(imgAfter.height);
const colorThreshold = 50;
const differentPixels = pixelsDiff(imgDataTest, imgDataAfter, colorThreshold);
// Allow 10% of pixels to be different
const pixelsThreshold = Math.floor((imgAfter.width * imgAfter.height) / 10);
expect(differentPixels).toBeLessThanOrEqual(pixelsThreshold);
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,253 @@
import { getPixel, setPixel } from '../utils/imageData';
/**
* The seam is a sequence of pixels (coordinates).
* @typedef {PixelCoordinate[]} Seam
*/
/**
* Energy map is a 2D array that has the same width and height
* as the image the map is being calculated for.
* @typedef {number[][]} EnergyMap
*/
/**
* The metadata for the pixels in the seam.
* @typedef {Object} SeamPixelMeta
* @property {number} energy - the energy of the pixel.
* @property {PixelCoordinate} coordinate - the coordinate of the pixel.
* @property {?PixelCoordinate} previous - the previous pixel in a seam.
*/
/**
* Type that describes the image size (width and height)
* @typedef {Object} ImageSize
* @property {number} w - image width.
* @property {number} h - image height.
*/
/**
* @typedef {Object} ResizeImageWidthArgs
* @property {ImageData} img - image data we want to resize.
* @property {number} toWidth - final image width we want the image to shrink to.
*/
/**
* @typedef {Object} ResizeImageWidthResult
* @property {ImageData} img - resized image data.
* @property {ImageSize} size - resized image size.
*/
/**
* Helper function that creates a matrix (2D array) of specific
* size (w x h) and fills it with specified value.
* @param {number} w
* @param {number} h
* @param {?(number | SeamPixelMeta)} filler
* @returns {?(number | SeamPixelMeta)[][]}
*/
const matrix = (w, h, filler) => {
return new Array(h)
.fill(null)
.map(() => {
return new Array(w).fill(filler);
});
};
/**
* Calculates the energy of a pixel.
* @param {?PixelColor} left
* @param {PixelColor} middle
* @param {?PixelColor} right
* @returns {number}
*/
const getPixelEnergy = (left, middle, right) => {
// Middle pixel is the pixel we're calculating the energy for.
const [mR, mG, mB] = middle;
// Energy from the left pixel (if it exists).
let lEnergy = 0;
if (left) {
const [lR, lG, lB] = left;
lEnergy = (lR - mR) ** 2 + (lG - mG) ** 2 + (lB - mB) ** 2;
}
// Energy from the right pixel (if it exists).
let rEnergy = 0;
if (right) {
const [rR, rG, rB] = right;
rEnergy = (rR - mR) ** 2 + (rG - mG) ** 2 + (rB - mB) ** 2;
}
// Resulting pixel energy.
return Math.sqrt(lEnergy + rEnergy);
};
/**
* Calculates the energy of each pixel of the image.
* @param {ImageData} img
* @param {ImageSize} size
* @returns {EnergyMap}
*/
const calculateEnergyMap = (img, { w, h }) => {
// Create an empty energy map where each pixel has infinitely high energy.
// We will update the energy of each pixel.
const energyMap = matrix(w, h, Infinity);
for (let y = 0; y < h; y += 1) {
for (let x = 0; x < w; x += 1) {
// Left pixel might not exist if we're on the very left edge of the image.
const left = (x - 1) >= 0 ? getPixel(img, { x: x - 1, y }) : null;
// The color of the middle pixel that we're calculating the energy for.
const middle = getPixel(img, { x, y });
// Right pixel might not exist if we're on the very right edge of the image.
const right = (x + 1) < w ? getPixel(img, { x: x + 1, y }) : null;
energyMap[y][x] = getPixelEnergy(left, middle, right);
}
}
return energyMap;
};
/**
* Finds the seam (the sequence of pixels from top to bottom) that has the
* lowest resulting energy using the Dynamic Programming approach.
* @param {EnergyMap} energyMap
* @param {ImageSize} size
* @returns {Seam}
*/
const findLowEnergySeam = (energyMap, { w, h }) => {
// The 2D array of the size of w and h, where each pixel contains the
// seam metadata (pixel energy, pixel coordinate and previous pixel from
// the lowest energy seam at this point).
const seamPixelsMap = matrix(w, h, null);
// Populate the first row of the map by just copying the energies
// from the energy map.
for (let x = 0; x < w; x += 1) {
const y = 0;
seamPixelsMap[y][x] = {
energy: energyMap[y][x],
coordinate: { x, y },
previous: null,
};
}
// Populate the rest of the rows.
for (let y = 1; y < h; y += 1) {
for (let x = 0; x < w; x += 1) {
// Find the top adjacent cell with minimum energy.
// This cell would be the tail of a seam with lowest energy at this point.
// It doesn't mean that this seam (path) has lowest energy globally.
// Instead, it means that we found a path with the lowest energy that may lead
// us to the current pixel with the coordinates x and y.
let minPrevEnergy = Infinity;
let minPrevX = x;
for (let i = (x - 1); i <= (x + 1); i += 1) {
if (i >= 0 && i < w && seamPixelsMap[y - 1][i].energy < minPrevEnergy) {
minPrevEnergy = seamPixelsMap[y - 1][i].energy;
minPrevX = i;
}
}
// Update the current cell.
seamPixelsMap[y][x] = {
energy: minPrevEnergy + energyMap[y][x],
coordinate: { x, y },
previous: { x: minPrevX, y: y - 1 },
};
}
}
// Find where the minimum energy seam ends.
// We need to find the tail of the lowest energy seam to start
// traversing it from its tail to its head (from the bottom to the top).
let lastMinCoordinate = null;
let minSeamEnergy = Infinity;
for (let x = 0; x < w; x += 1) {
const y = h - 1;
if (seamPixelsMap[y][x].energy < minSeamEnergy) {
minSeamEnergy = seamPixelsMap[y][x].energy;
lastMinCoordinate = { x, y };
}
}
// Find the lowest energy energy seam.
// Once we know where the tail is we may traverse and assemble the lowest
// energy seam based on the "previous" value of the seam pixel metadata.
const seam = [];
const { x: lastMinX, y: lastMinY } = lastMinCoordinate;
// Adding new pixel to the seam path one by one until we reach the top.
let currentSeam = seamPixelsMap[lastMinY][lastMinX];
while (currentSeam) {
seam.push(currentSeam.coordinate);
const prevMinCoordinates = currentSeam.previous;
if (!prevMinCoordinates) {
currentSeam = null;
} else {
const { x: prevMinX, y: prevMinY } = prevMinCoordinates;
currentSeam = seamPixelsMap[prevMinY][prevMinX];
}
}
return seam;
};
/**
* Deletes the seam from the image data.
* We delete the pixel in each row and then shift the rest of the row pixels to the left.
* @param {ImageData} img
* @param {Seam} seam
* @param {ImageSize} size
*/
const deleteSeam = (img, seam, { w }) => {
seam.forEach(({ x: seamX, y: seamY }) => {
for (let x = seamX; x < (w - 1); x += 1) {
const nextPixel = getPixel(img, { x: x + 1, y: seamY });
setPixel(img, { x, y: seamY }, nextPixel);
}
});
};
/**
* Performs the content-aware image width resizing using the seam carving method.
* @param {ResizeImageWidthArgs} args
* @returns {ResizeImageWidthResult}
*/
const resizeImageWidth = ({ img, toWidth }) => {
/**
* For performance reasons we want to avoid changing the img data array size.
* Instead we'll just keep the record of the resized image width and height separately.
* @type {ImageSize}
*/
const size = { w: img.width, h: img.height };
// Calculating the number of pixels to remove.
const pxToRemove = img.width - toWidth;
let energyMap = null;
let seam = null;
// Removing the lowest energy seams one by one.
for (let i = 0; i < pxToRemove; i += 1) {
// 1. Calculate the energy map for the current version of the image.
energyMap = calculateEnergyMap(img, size);
// 2. Find the seam with the lowest energy based on the energy map.
seam = findLowEnergySeam(energyMap, size);
// 3. Delete the seam with the lowest energy seam from the image.
deleteSeam(img, seam, size);
// Reduce the image width, and continue iterations.
size.w -= 1;
}
// Returning the resized image and its final size.
// The img is actually a reference to the ImageData, so technically
// the caller of the function already has this pointer. But let's
// still return it for better code readability.
return { img, size };
};
export default resizeImageWidth;

View File

@ -0,0 +1,39 @@
/**
* @typedef {ArrayLike<number> | Uint8ClampedArray} PixelColor
*/
/**
* @typedef {Object} PixelCoordinate
* @property {number} x - horizontal coordinate.
* @property {number} y - vertical coordinate.
*/
/**
* Helper function that returns the color of the pixel.
* @param {ImageData} img
* @param {PixelCoordinate} coordinate
* @returns {PixelColor}
*/
export const getPixel = (img, { x, y }) => {
// The ImageData data array is a flat 1D array.
// Thus we need to convert x and y coordinates to the linear index.
const i = y * img.width + x;
const cellsPerColor = 4; // RGBA
// For better efficiency, instead of creating a new sub-array we return
// a pointer to the part of the ImageData array.
return img.data.subarray(i * cellsPerColor, i * cellsPerColor + cellsPerColor);
};
/**
* Helper function that sets the color of the pixel.
* @param {ImageData} img
* @param {PixelCoordinate} coordinate
* @param {PixelColor} color
*/
export const setPixel = (img, { x, y }, color) => {
// The ImageData data array is a flat 1D array.
// Thus we need to convert x and y coordinates to the linear index.
const i = y * img.width + x;
const cellsPerColor = 4; // RGBA
img.data.set(color, i * cellsPerColor);
};

View File

@ -0,0 +1,102 @@
# Lista doblemente enlazada
_Lea esto en otros idiomas:_
[_Русский_](README.ru-RU.md),
[_简体中文_](README.zh-CN.md),
[_日本語_](README.ja-JP.md),
[_Português_](README.pt-BR.md)
[_한국어_](README.ko-KR.md)
En informática, una **lista doblemente enlazada** es una estructura de datos enlazados que consta de un conjunto de registros enlazados secuencialmente llamados nodos. Cada nodo contiene dos campos, llamados enlaces, que son referencias al nodo anterior y al siguiente en la secuencia de nodos. Los enlaces anterior y siguiente de los nodos inicial y final, respectivamente, apuntan a algún tipo de terminador, normalmente un nodo centinela o nulo, para facilitar el recorrido de la lista. Si solo hay un ganglio centinela, la lista se enlaza circularmente a través del ganglio centinela. Puede conceptualizarse como dos listas enlazadas individualmente formadas a partir de los mismos elementos de datos, pero en órdenes secuenciales opuestos.
![Lista doblemente enlazada](https://upload.wikimedia.org/wikipedia/commons/5/5e/Doubly-linked-list.svg)
Los dos enlaces de nodo permiten recorrer la lista en cualquier dirección. Si bien agregar o eliminar un nodo en una lista doblemente enlazada requiere cambiar más enlaces que las mismas operaciones en una lista enlazada individualmente, las operaciones son más simples y potencialmente más eficientes (para nodos que no sean los primeros) porque no hay necesidad de realizar un seguimiento de el nodo anterior durante el recorrido o no es necesario recorrer la lista para encontrar el nodo anterior, de modo que se pueda modificar su enlace.
## Pseudocódigo para operaciones básicas
### Insertar
```text
Add(value)
Pre: value is the value to add to the list
Post: value has been placed at the tail of the list
n ← node(value)
if head = ø
head ← n
tail ← n
else
n.previous ← tail
tail.next ← n
tail ← n
end if
end Add
```
### Eliminar
```text
Remove(head, value)
Pre: head is the head node in the list
value is the value to remove from the list
Post: value is removed from the list, true; otherwise false
if head = ø
return false
end if
if value = head.value
if head = tail
head ← ø
tail ← ø
else
head ← head.next
head.previous ← ø
end if
return true
end if
n ← head.next
while n != ø and value !== n.value
n ← n.next
end while
if n = tail
tail ← tail.previous
tail.next ← ø
return true
else if n != ø
n.previous.next ← n.next
n.next.previous ← n.previous
return true
end if
return false
end Remove
```
### Recorrido Inverso
```text
ReverseTraversal(tail)
Pre: tail is the node of the list to traverse
Post: the list has been traversed in reverse order
n ← tail
while n != ø
yield n.value
n ← n.previous
end while
end Reverse Traversal
```
## Complejidades
## Complejidad del Tiempo
| Acceso | Busqueda | Inserción | Supresión |
| :-------: | :-------: | :-------: | :-------: |
| O(n) | O(n) | O(1) | O(n) |
### Complejidad del Espacio
O(n)
## Referencias
- [Wikipedia](https://en.wikipedia.org/wiki/Doubly_linked_list)
- [YouTube](https://www.youtube.com/watch?v=JdQeNxWCguQ&t=7s&index=72&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)

View File

@ -4,26 +4,27 @@ _Read this in other languages:_
[_Русский_](README.ru-RU.md),
[_简体中文_](README.zh-CN.md),
[_日本語_](README.ja-JP.md),
[_Português_](README.pt-BR.md)
[_한국어_](README.ko-KR.md)
[_Português_](README.pt-BR.md),
[_한국어_](README.ko-KR.md),
[_Español_](README.es-ES.md),
In computer science, a **doubly linked list** is a linked data structure that
consists of a set of sequentially linked records called nodes. Each node contains
two fields, called links, that are references to the previous and to the next
node in the sequence of nodes. The beginning and ending nodes' previous and next
links, respectively, point to some kind of terminator, typically a sentinel
node or null, to facilitate the traversal of the list. If there is only one
sentinel node, then the list is circularly linked via the sentinel node. It can
be conceptualized as two singly linked lists formed from the same data items,
In computer science, a **doubly linked list** is a linked data structure that
consists of a set of sequentially linked records called nodes. Each node contains
two fields, called links, that are references to the previous and to the next
node in the sequence of nodes. The beginning and ending nodes' previous and next
links, respectively, point to some kind of terminator, typically a sentinel
node or null, to facilitate the traversal of the list. If there is only one
sentinel node, then the list is circularly linked via the sentinel node. It can
be conceptualized as two singly linked lists formed from the same data items,
but in opposite sequential orders.
![Doubly Linked List](https://upload.wikimedia.org/wikipedia/commons/5/5e/Doubly-linked-list.svg)
The two node links allow traversal of the list in either direction. While adding
or removing a node in a doubly linked list requires changing more links than the
same operations on a singly linked list, the operations are simpler and
potentially more efficient (for nodes other than first nodes) because there
is no need to keep track of the previous node during traversal or no need
The two node links allow traversal of the list in either direction. While adding
or removing a node in a doubly linked list requires changing more links than the
same operations on a singly linked list, the operations are simpler and
potentially more efficient (for nodes other than first nodes) because there
is no need to keep track of the previous node during traversal or no need
to traverse the list to find the previous node, so that its link can be modified.
## Pseudocode for Basic Operations
@ -45,7 +46,7 @@ Add(value)
end if
end Add
```
### Delete
```text
@ -82,7 +83,7 @@ Remove(head, value)
return false
end Remove
```
### Reverse Traversal
```text
@ -96,7 +97,7 @@ ReverseTraversal(tail)
end while
end Reverse Traversal
```
## Complexities
## Time Complexity

View File

@ -65,7 +65,7 @@ export default class LinkedList {
let deletedNode = null;
// If the head must be deleted then make next node that is differ
// If the head must be deleted then make next node that is different
// from the head to be a new head.
while (this.head && this.compare.equal(this.head.value, value)) {
deletedNode = this.head;

View File

@ -7,27 +7,26 @@ _Lee esto en otros idiomas:_
[_Português_](README.pt-BR.md)
[_English_](README.md)
En ciencias de la computaciòn una **lista enlazada** es una coleccion linear
En ciencias de la computación una **lista enlazada** es una colección linear
de elemntos de datos, en los cuales el orden linear no es dado por
su posciòn fisica en memoria. En cambio, cada
elemento señala al siguiente. Es una estructura de datos
su posción física en memoria. En cambio, cada
elemento señala al siguiente. Es una estructura de datos
que consiste en un grupo de nodos los cuales juntos representan
una secuencia. Bajo la forma mas simple, cada nodo es
compuesto de datos y una referencia (en otras palabras,
una secuencia. Bajo la forma más simple, cada nodo es
compuesto de datos y una referencia (en otras palabras,
un lazo) al siguiente nodo en la secuencia. Esta estructura
permite la insercion o remocion de elementos
desde cualquier posicion en la secuencia durante la iteracion.
Variantes mas complejas agregan lazos adicionales, permitiendo
una eficiente insercion o remocion desde referencias arbitrarias
permite la inserción o remoción de elementos
desde cualquier posición en la secuencia durante la iteración.
Variantes más complejas agregan lazos adicionales, permitiendo
una eficiente inserción o remoción desde referencias arbitrarias
del elemento. Una desventaja de las listas lazadas es que el tiempo de
acceso es linear (y dificil de canalizar) Un acceso
mas rapido, como un acceso aleatorio, no es factible. Los arreglos
acceso es linear (y difícil de canalizar). Un acceso
más rápido, como un acceso aleatorio, no es factible. Los arreglos
tienen una mejor locazion comparados con las listas lazadas.
![Linked List](https://upload.wikimedia.org/wikipedia/commons/6/6d/Singly-linked-list.svg)
## Pseudocodigo para operacones basicas
## Pseudocódigo para operacones básicas
### Insertar
@ -76,7 +75,7 @@ Contains(head, value)
return true
end Contains
```
### Borrar
```text
@ -146,19 +145,19 @@ ReverseTraversal(head, tail)
end ReverseTraversal
```
## Complexities
## Complejidades
### Time Complexity
### Complejidad del Tiempo
| Access | Search | Insertion | Deletion |
| :-------: | :-------: | :-------: | :-------: |
| O(n) | O(n) | O(1) | O(n) |
| Access | Search | Insertion | Deletion |
| :----: | :----: | :-------: | :------: |
| O(n) | O(n) | O(1) | O(n) |
### Space Complexity
### Complejidad Espacial
O(n)
## References
## Referencias
- [Wikipedia](https://en.wikipedia.org/wiki/Linked_list)
- [YouTube](https://www.youtube.com/watch?v=njTh_OwMljA&index=2&t=1s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)

View File

@ -1,5 +1,8 @@
import playground from '../playground';
describe('playground', () => {
it('should perform playground tasks', () => {
// Place your playground tests here.
it('should return correct results', () => {
// Replace the next dummy test with your playground function tests.
expect(playground()).toBe(120);
});
});

View File

@ -1 +1,12 @@
// Place your playground code here.
// Import any algorithmic dependencies you need for your playground code here.
import factorial from '../algorithms/math/factorial/factorial';
// Write your playground code inside the playground() function.
// Test your code from __tests__/playground.test.js
// Launch playground tests by running: npm test -- 'playground'
function playground() {
// Replace the next line with your playground code.
return factorial(5);
}
export default playground;

View File

@ -1,5 +1,6 @@
export default class Comparator {
/**
* Constructor.
* @param {function(a: *, b: *)} [compareFunction] - It may be custom compare function that, let's
* say may compare custom objects together.
*/