diff --git a/.circleci/config.yml b/.circleci/config.yml index a50b5c40..cd95bb19 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -104,7 +104,7 @@ jobs: # a collection of steps - run: name: generate PDF - command: cd book/config && make VERSION="$(npx -c 'echo "$npm_package_version"')" + command: cd book/config && make VERSION="$(npx -c 'echo "$npm_package_version"')" pdf - store_artifacts: path: book/dist diff --git a/.gitignore b/.gitignore index 363ed4fe..fedfe47e 100644 --- a/.gitignore +++ b/.gitignore @@ -72,6 +72,7 @@ local.properties # Ruby ###################### /.bundle/ +/Gemfile.lock ###################### # Package Files @@ -135,13 +136,13 @@ Desktop.ini # ln -s ~/OneDrive/Authoring/dsaJS/asciidoc/book/fonts . # ln -s ~/OneDrive/Authoring/dsaJS/asciidoc/book/images . ###################### -# Gemfile -# Gemfile.lock -# Makefile -# _conf -# _resources -# extensions -# fonts +Gemfile +Gemfile.lock +Makefile +_conf +_resources +extensions +fonts ###################### diff --git a/README.md b/README.md index 1d399c45..64c0d55a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![CircleCI](https://img.shields.io/circleci/build/github/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/master.svg)](https://circleci.com/gh/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript) [![NPM version](https://badge.fury.io/js/dsa.js.svg)](https://badge.fury.io/js/dsa.js) [![Slack](https://dsajs-slackin.herokuapp.com/badge.svg)](https://dsajs-slackin.herokuapp.com) -> This is the coding implementations of the [DSA.js book](https://books.adrianmejia.com/dsajs-data-structures-algorithms-javascript/) and the repo for the NPM package. +> This is the coding implementations of the [DSA.js book](https://books.adrianmejia.com/dsajs-data-structures-algorithms-javascript/) and the repo for the [npm package](https://www.npmjs.com/package/dsa.js). > In this repository, you can find the implementation of algorithms and data structures. They are implemented and explained in JavaScript. This material can be used as a reference manual for developers. You can refresh specific topics before an interview. Also, you can find ideas to solve problems more efficiently. @@ -21,8 +21,7 @@ https://img.shields.io/bundlephobia/min/dsa.js.svg - 16.7kB https://img.shields.io/github/repo-size/amejiarosario/dsa.js.svg - 98.1 MB --> -![Interactive Data Structures](https://user-images.githubusercontent.com/418605/46118890-ba721180-c1d6-11e8-82bc-6a671428b422.png) - +[![Interactive Data Structures](https://user-images.githubusercontent.com/418605/46118890-ba721180-c1d6-11e8-82bc-6a671428b422.png)](https://embed.kumu.io/85f1a4de5fb8430a10a1bf9c5118e015) ## Table of Contents @@ -97,7 +96,7 @@ The topics are divided in 4 main categories as you can see below: _(You can click on the ⯈ to expand the topics)_ -### 📈 [Algorithms Analysis](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/part01-algorithms-analysis.asc) +### 📈 [Algorithms Analysis](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/part1.adoc) -- [Linked List](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part02/linked-list.asc): each data node has a link to the next (and +- [Linked List](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/linked-list.adoc): each data node has a link to the next (and previous). [Code](https://github.com/amejiarosario/dsa.js/blob/master/src/data-structures/linked-lists/linked-list.js) | - [Linked List Time Complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part02/linked-list.asc#linked-list-complexity-vs-array-complexity) + [Linked List Time Complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/linked-list.adoc#linked-list-complexity-vs-array-complexity) -- [Queue](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part02/queue.asc): data flows in a "first-in, first-out" (FIFO) manner. +- [Queue](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/queue.adoc): data flows in a "first-in, first-out" (FIFO) manner. [Code](https://github.com/amejiarosario/dsa.js/blob/master/src/data-structures/queues/queue.js) | - [Queue Time Complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part02/queue.asc#queue-complexity) + [Queue Time Complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/queue.adoc#queue-complexity) -- [Stack](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part02/stack.asc): data flows in a "last-in, first-out" (LIFO) manner. +- [Stack](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/stack.adoc): data flows in a "last-in, first-out" (LIFO) manner. [Code](https://github.com/amejiarosario/dsa.js/blob/master/src/data-structures/stacks/stack.js) | - [Stack Time Complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part02/stack.asc#stack-complexity) + [Stack Time Complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/stack.adoc#stack-complexity) --- @@ -225,7 +224,7 @@ they take different time to complete. --- -#### [When to use an Array or Linked List. Know the tradeoffs](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part02/array-vs-list-vs-queue-vs-stack.asc) +#### [When to use an Array or Linked List. Know the tradeoffs](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/linear-data-structures-outro.adoc) Use Arrays when… - You need to access data in random order fast (using an index). @@ -246,7 +245,7 @@ Use Linked Lists when: --- - #### [Build a List, Stack and a Queue from scratch](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/part02-linear-data-structures.asc) + #### [Build a List, Stack and a Queue from scratch](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/part2.adoc) Build any of these data structures from scratch: - [Linked List](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/src/data-structures/linked-lists/linked-list.js) @@ -258,7 +257,7 @@ Use Linked Lists when: -### 🌲 [Non-Linear Data Structures](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/part03-graph-data-structures.asc) +### 🌲 [Non-Linear Data Structures](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/part3.adoc) @@ -271,13 +270,13 @@ Use Linked Lists when: --- -#### [HashMaps](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part03/map.asc) +#### [HashMaps](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/map.adoc) Learn how to implement different types of Maps such as: -- [HashMap](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part03/hashmap.asc) -- [TreeMap](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part03/treemap.asc) +- [HashMap](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/map-hashmap.adoc) +- [TreeMap](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/map-treemap.adoc) -Also, [learn the difference between the different Maps implementations](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part03/time-complexity-graph-data-structures.asc): +Also, [learn the difference between the different Maps implementations](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/map-hashmap-vs-treemap.adoc): - `HashMap` is more time-efficient. A `TreeMap` is more space-efficient. - `TreeMap` search complexity is *O(log n)*, while an optimized `HashMap` is *O(1)* on average. @@ -296,9 +295,9 @@ Also, [learn the difference between the different Maps implementations](https:// --- -#### [Know the properties of Graphs and Trees](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/part03-graph-data-structures.asc) +#### [Know the properties of Graphs and Trees](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/part3.adoc) -##### [Graphs](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part03/graph.asc) +##### [Graphs](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/graph.adoc) Know all the graphs properties with many images and illustrations. @@ -309,9 +308,9 @@ Know all the graphs properties with many images and illustrations. parents, loops. [Code](https://github.com/amejiarosario/algorithms.js/blob/master/src/data-structures/graphs/graph.js) | - [Graph Time Complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part03/graph.asc#graph-complexity) + [Graph Time Complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/graph.adoc#graph-complexity) -#### [Trees](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part03/tree-intro.asc) +#### [Trees](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/tree.adoc) Learn all the different kinds of trees and its properties. @@ -322,29 +321,29 @@ Learn all the different kinds of trees and its properties. graph not a tree. [Code](https://github.com/amejiarosario/algorithms.js/tree/master/src/data-structures/trees) | - [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part03/tree-intro.asc) + [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/tree.adoc) - **Binary Trees**: same as tree but only can have two children at most. [Code](https://github.com/amejiarosario/algorithms.js/tree/master/src/data-structures/trees) | - [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part03/tree-intro.asc#binary-tree) + [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/tree.adoc#binary-tree) - **Binary Search Trees** (BST): same as binary tree, but the nodes value keep this order `left < parent < right`. [Code](https://github.com/amejiarosario/algorithms.js/blob/master/src/data-structures/trees/binary-search-tree.js) | - [BST Time complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part03/binary-search-tree.asc#tree-complexity) + [BST Time complexity](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/tree-binary-search-tree.adoc#tree-complexity) - **AVL Trees**: Self-balanced BST to maximize look up time. [Code](https://github.com/amejiarosario/algorithms.js/blob/master/src/data-structures/trees/avl-tree.js) | - [AVL Tree docs](https://github.com/amejiarosario/dsa.js/blob/master/book/C-AVL-tree.asc) + [AVL Tree docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/tree-avl.adoc) | - [Self-balancing & tree rotations docs](https://github.com/amejiarosario/dsa.js/blob/master/book/B-self-balancing-binary-search-trees.asc) + [Self-balancing & tree rotations docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/tree-self-balancing-rotations.adoc) - **Red-Black Trees**: Self-balanced BST more loose than AVL to @@ -362,12 +361,12 @@ Learn all the different kinds of trees and its properties. --- -#### [Implement a binary search tree for fast lookups](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part03/binary-search-tree.asc) +#### [Implement a binary search tree for fast lookups](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/tree-binary-search-tree.adoc) - Learn how to add/remove/update values in a tree: ![inserting node in a tree](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/raw/master/book/images/image36.png) -- [How to make a tree balanced?](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/B-self-balancing-binary-search-trees.asc) +- [How to make a tree balanced?](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/tree-self-balancing-rotations.adoc) From unbalanced BST to balanced BST ``` @@ -384,7 +383,7 @@ From unbalanced BST to balanced BST -### ⚒ [Algorithmic Toolbox](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/part04-algorithmic-toolbox.asc) +### ⚒ [Algorithms Techniques](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/part4.adoc) @@ -398,7 +397,7 @@ From unbalanced BST to balanced BST --- -#### [Never get stuck solving a problem with 7 simple steps](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/part04-algorithmic-toolbox.asc) +#### [Never get stuck solving a problem with 7 simple steps](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/algorithmic-toolbox.adoc) 1. Understand the problem 1. Build a simple example (no edge cases yet) @@ -408,7 +407,7 @@ From unbalanced BST to balanced BST 1. Write Code, yes, now you can code. 1. Test your written code -Full details [here](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/part04-algorithmic-toolbox.asc) +Full details [here](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/algorithmic-toolbox.adoc) --- @@ -420,34 +419,34 @@ Full details [here](https://github.com/amejiarosario/dsa.js-data-structures-and- --- -#### [Master the most popular sorting algorithms](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part04/sorting-algorithms.asc) +#### [Master the most popular sorting algorithms](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/sorting-intro.adoc) We are going to explore three basic sorting algorithms O(n2) which have low overhead: - Bubble Sort. [Code](https://github.com/amejiarosario/dsa.js/blob/master/src/algorithms/sorting/bubble-sort.js) | - [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part04/bubble-sort.asc) + [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/bubble-sort.adoc) - Insertion Sort. [Code](https://github.com/amejiarosario/dsa.js/blob/master/src/algorithms/sorting/insertion-sort.js) | - [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part04/insertion-sort.asc) + [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/insertion-sort.adoc) - Selection Sort. [Code](https://github.com/amejiarosario/dsa.js/blob/master/src/algorithms/sorting/selection-sort.js) | - [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part04/selection-sort.asc) + [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/selection-sort.adoc) and then discuss efficient sorting algorithms O(n log n) such as: - Merge Sort. [Code](https://github.com/amejiarosario/dsa.js/blob/master/src/algorithms/sorting/merge-sort.js) | - [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part04/merge-sort.asc) + [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/merge-sort.adoc) - Quick sort. [Code](https://github.com/amejiarosario/dsa.js/blob/master/src/algorithms/sorting/quick-sort.js) | - [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/content/part04/quick-sort.asc) + [Docs](https://github.com/amejiarosario/dsa.js/blob/master/book/chapters/quick-sort.adoc) --- @@ -459,13 +458,13 @@ and then discuss efficient sorting algorithms O(n log n) such as: --- -#### [Learn different approaches to solve algorithmic problems](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/part04-algorithmic-toolbox.asc) +#### [Learn different approaches to solve algorithmic problems](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/algorithms-intro.adoc) We are going to discuss the following techniques for solving algorithms problems: -- [Greedy Algorithms](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/bbook/content/part04/greedy-algorithms.asc): makes greedy choices using heuristics to find the best solution without looking back. -- [Dynamic Programming](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part04/dynamic-programming.asc): a technique for speeding up recursive algorithms when there are many _overlapping subproblems_. It uses _memoization_ to avoid duplicating work. -- [Divide and Conquer](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part04/divide-and-conquer.asc): _divide_ problems into smaller pieces, _conquer_ each subproblem and then _join_ the results. -- [Backtracking](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/content/part04/backtracking.asc): search _all (or some)_ possible paths. However, it stops and _go back_ as soon as notice the current solution is not working. +- [Greedy Algorithms](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/greedy-algorithms.adoc): makes greedy choices using heuristics to find the best solution without looking back. +- [Dynamic Programming](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/dynamic-programming.adoc): a technique for speeding up recursive algorithms when there are many _overlapping subproblems_. It uses _memoization_ to avoid duplicating work. +- [Divide and Conquer](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/divide-and-conquer.adoc): _divide_ problems into smaller pieces, _conquer_ each subproblem and then _join_ the results. +- [Backtracking](https://github.com/amejiarosario/dsa.js-data-structures-and-algorithms-in-javascript/blob/master/book/chapters/backtracking.adoc): search _all (or some)_ possible paths. However, it stops and _go back_ as soon as notice the current solution is not working. - _Brute Force_: generate all possible solutions and tries all of them. (Use it as a last resort or as the starting point to optimize it with other techniques). --- diff --git a/book/A-time-complexity-cheatsheet.asc b/book/A-time-complexity-cheatsheet.asc deleted file mode 100644 index 64400aaa..00000000 --- a/book/A-time-complexity-cheatsheet.asc +++ /dev/null @@ -1,39 +0,0 @@ -[appendix] -[[a-time-complexity-cheatsheet]] -== Cheatsheet - -This section summerize what we are going to cover in the rest of this book. - -=== Runtimes - -include::content/part01/big-o-examples.asc[tag=table] - -include::content/part01/algorithms-analysis.asc[tag=table] - -=== Linear Data Structures - -include::content/part02/array-vs-list-vs-queue-vs-stack.asc[tag=table] - -=== Trees and Maps Data Structures - -This section covers Binary Search Tree (BST) time complexity (Big O). - -include::content/part03/time-complexity-graph-data-structures.asc[tag=table] - -include::content/part03/graph.asc[tag=table] - -=== Sorting Algorithms - -include::content/part04/sorting-algorithms.asc[tag=table] - -// // https://algs4.cs.princeton.edu/cheatsheet/ -// // http://bigocheatsheet.com/ - -// // https://en.wikipedia.org/wiki/Timsort (Tim Peters) -// // https://bugs.python.org/file4451/timsort.txt -// // https://www.youtube.com/watch?v=emeME__917E&list=PLMCXHnjXnTntLcLmA5SqhMspm7burHi3m - -// // https://en.wikipedia.org/wiki/Sorting_algorithm -// // http://sorting.at/ -// // https://www.toptal.com/developers/sorting-algorithms -// // https://www.infopulse.com/blog/timsort-sorting-algorithm/ diff --git a/book/book-all.adoc b/book/book-all.adoc new file mode 100644 index 00000000..5c872247 --- /dev/null +++ b/book/book-all.adoc @@ -0,0 +1,155 @@ +include::_conf/variables.adoc[] + += {doctitle} + +// remove numbering from titles, and sub-titles e.g. 1.1 +:sectnums!: + +// Copyright © 2018 Adrian Mejia +include::chapters/colophon.adoc[] + +// Abstract and Dedication MUST have a level-0 heading in EPUB and Kindle +// but level-1 in PDF and HTML +ifndef::backend-epub3[:leveloffset: +1] +include::chapters/dedication.adoc[] +ifndef::backend-epub3[:leveloffset: -1] + +// TODO: pending +include::chapters/preface.adoc[] + +include::chapters/cheatsheet.adoc[] + +// add sections to chapters +:sectnums: + +// +// chapters +// + += Algorithms Analysis + +// TODO: pending +include::chapters/algorithms-analysis-intro.adoc[] + +:leveloffset: +1 + +include::chapters/algorithms-analysis.adoc[] + +include::chapters/big-o-examples.adoc[] + +:leveloffset: -1 + += Linear Data Structures + +include::chapters/linear-data-structures-intro.adoc[] + +:leveloffset: +1 + +include::chapters/array.adoc[] + +include::chapters/linked-list.adoc[] + +include::chapters/stack.adoc[] + +include::chapters/queue.adoc[] + +:leveloffset: -1 + += Non-Linear Data Structures + +include::chapters/non-linear-data-structures-intro.adoc[] + +:leveloffset: +1 + +include::chapters/tree.adoc[] + +include::chapters/binary-search-tree.adoc[] + +include::chapters/map.adoc[] + +include::chapters/set.adoc[] + +include::chapters/graph.adoc[] + + +:leveloffset: -1 + += Advanced Non-Linear Data Structures + +// TODO: pending +include::chapters/non-linear-data-structures-intro-advanced.adoc[] + +:leveloffset: +1 + +// TODO: pending +include::chapters/avl-tree.adoc[] + +// TODO: pending (optional) +// include::chapters/red-black-tree.adoc[] + +// TODO: pending +include::chapters/heap.adoc[] + +// TODO: (optional) pending +// include::chapters/trie.adoc[] + + +:leveloffset: -1 + += Algorithms + +// TODO: pending +include::chapters/algorithms-intro.adoc[] + +:leveloffset: +1 + +// TODO: pending +include::chapters/sorting-intro.adoc[] + +// +// Slow Sorting +// + +include::chapters/insertion-sort.adoc[] + +include::chapters/selection-sort.adoc[] + +include::chapters/bubble-sort.adoc[] + +// +// Fast Sorting +// + +include::chapters/merge-sort.adoc[] + +include::chapters/quick-sort.adoc[] + +// TODO: (optional) pending +// include::chapters/heap-sort.adoc[] + +// TODO: (optional) pending +// include::chapters/tim-sort.adoc[] + +// +// Searching +// + +// TODO: pending +include::chapters/graph-search.adoc[] + +:leveloffset: -1 + +// +// end chapters +// + +include::chapters/epigraph.adoc[] + +// TODO: (optional) pending +// include::chapters/appendix.adoc[] + +// TODO: (optional) pending +ifdef::backend-pdf[] +include::chapters/index.adoc[] +endif::[] + diff --git a/book/book-o.adoc b/book/book-o.adoc new file mode 100644 index 00000000..6fc2744b --- /dev/null +++ b/book/book-o.adoc @@ -0,0 +1,194 @@ +include::_conf/variables.adoc[] + += {doctitle} + +// remove numbering from titles, and sub-titles e.g. 1.1 +:sectnums!: + +// Copyright © 2018 Adrian Mejia (g) +include::chapters/colophon.adoc[] + +// Abstract and Dedication MUST have a level-0 heading in EPUB and Kindle +// but level-1 in PDF and HTML +ifndef::backend-epub3[:leveloffset: +1] +include::chapters/dedication.adoc[] +ifndef::backend-epub3[:leveloffset: -1] + +// (g) +include::chapters/preface.adoc[] + +// add sections to chapters +:sectnums: + + +//----------------------------------- +// TODO: commment out sample on final +//----------------------------------- + +include::chapters/sample.adoc[] + +//----------------------------------- +// TODO: end remove ------ +//----------------------------------- + +// +// chapters +// + += Algorithms Analysis + +include::chapters/algorithms-analysis-intro.adoc[] + +:leveloffset: +1 + +// (g) +include::chapters/algorithms-analysis.adoc[] + +// (g) +include::chapters/big-o-examples.adoc[] + +:leveloffset: -1 + += Linear Data Structures + +// (g) +include::chapters/linear-data-structures-intro.adoc[] + +:leveloffset: +1 + +// (g) +include::chapters/array.adoc[] + +// (g) +include::chapters/linked-list.adoc[] + +// (g) +include::chapters/stack.adoc[] + +// (g) +include::chapters/queue.adoc[] + +// (g) +include::chapters/linear-data-structures-outro.adoc[] + +:leveloffset: -1 + + += Non-Linear Data Structures + +// (g) +include::chapters/non-linear-data-structures-intro.adoc[] + +:leveloffset: +1 + +// (g) +include::chapters/tree.adoc[] + + +// (g) +include::chapters/tree-binary-search-tree.adoc[] + +include::chapters/tree-search.adoc[] + +include::chapters/tree-self-balancing-rotations.adoc[] + +:leveloffset: +1 + +include::chapters/tree-avl.adoc[] + +:leveloffset: -1 + +// (g) +// include::chapters/map.adoc[] +include::chapters/map-intro.adoc[] + +:leveloffset: +1 + +// (g) +include::chapters/map-hashmap.adoc[] + +// (g) +include::chapters/map-treemap.adoc[] + +// (g) +include::chapters/map-hashmap-vs-treemap.adoc[] + +:leveloffset: -1 + +// (g) +include::chapters/set.adoc[] + +// (g) +include::chapters/graph.adoc[] + +// TODO: pending +include::chapters/graph-search.adoc[] + +:leveloffset: -1 + += Algorithmic Techniques + +// TODO: pending +include::chapters/algorithms-intro.adoc[] + +:leveloffset: +1 + +// +// Sorting algorithms +// += Sorting Algorithms + +:leveloffset: +1 + +// TODO: pending +include::chapters/sorting-intro.adoc[] + +// Slow Sorting + +include::chapters/insertion-sort.adoc[] + +include::chapters/selection-sort.adoc[] + +include::chapters/bubble-sort.adoc[] + +// Fast Sorting + +include::chapters/merge-sort.adoc[] + +include::chapters/quick-sort.adoc[] + +:leveloffset: -1 + + +// +// Algorithms Techniques +// + +include::chapters/divide-and-conquer.adoc[] + +include::chapters/dynamic-programming.adoc[] + +include::chapters/greedy-algorithms.adoc[] + +include::chapters/backtracking.adoc[] + +// --- end algorithms --- + +:leveloffset: -1 + +:sectnums!: + += Appendix + +:leveloffset: +1 + +// TODO: review and complete when the rest is completed +include::chapters/cheatsheet.adoc[] + +:leveloffset: -1 + +// +// end chapters +// + +include::chapters/epigraph.adoc[] diff --git a/book/book.adoc b/book/book.adoc new file mode 100644 index 00000000..4e0ad075 --- /dev/null +++ b/book/book.adoc @@ -0,0 +1,84 @@ += Data Structures and Algorithms in JavaScript +:book-title: {doctitle} +:author: Adrian Mejia +:email: hello+dsajs@adrianmejia.com +:revnumber: 1.0.0 +:revdate: {docdate} +:revyear: 2019 +:revremark: First Edition +:subject: Algorithms +:keywords: dsa.js, Algorithms, Data Structures, JavaScript, Coding Interviews, Computer Science, Time Complexity, Linked Lists, Graphs, Binary Search Trees +:doctype: book +:media: screen +ifeval::["{media}" != "prepress"] +:front-cover-image: image:cover-a4.png[Front Cover,595,842] +endif::[] +:toc: +:toclevels: 3 +:icons: font +:lang: en +:language: javascript +:experimental: +:pdf-fontsdir: ./fonts +:pdf-stylesdir: ./_resources/pdfstyles +:pdf-style: adrian-screen +:title-logo-image: image:logo.png[Logo,50,50] +// custom variables +:imagesdir: {docdir}/images +:codedir: ../../src +:datadir: {docdir}/data +// :source-highlighter: pygments +// paraiso-light, xcode, monokai +// :pygments-style: xcode +:stem: +:plantuml-config: {docdir}/_conf/umlconfig.txt +// :hide-uri-scheme: +// :chapter-label: Chapter +// :appendix-caption: Appendix +// :chapter-label: +// dark +// :pygments-style: monokai +// light theme with colorful code +// solarized-light, tomorrow +// :source-highlighter: highlightjs +// :highlightjs-theme: tomorrow + +ifndef::ebook-format[:leveloffset: 1] + +[colophon#colophon%nonfacing] +include::chapters/colophon.adoc[] + +[dedication] +include::chapters/dedication.adoc[] + +// [%nonfacing] +// include::chapters/acknowledgements.adoc[] + +[preface] +include::chapters/preface.adoc[] + +// include::chapters/introduction.adoc[] + +// TODO: (optional) include numbers but need to fix the part/chapter labels +// :sectnums: + +include::chapters/part1.adoc[] + +include::chapters/part2.adoc[] + +include::chapters/part3.adoc[] + +include::chapters/part4.adoc[] + +// :sectnums!: + +include::chapters/appendix.adoc[] + +// include::chapters/epigraph.adoc[] + +// include::chapters/about.adoc[] + +ifdef::backend-pdf,backend-docbook[] +[index] += Index +endif::[] diff --git a/book/ch02-git-basics-chapter.asc b/book/ch02-git-basics-chapter.asc deleted file mode 100644 index cb91537b..00000000 --- a/book/ch02-git-basics-chapter.asc +++ /dev/null @@ -1,35 +0,0 @@ -[[ch02-git-basics-chapter]] -== Git Basics - -If you can read only one chapter to get going with Git, this is it. -This chapter covers every basic command you need to do the vast majority of the things you'll eventually spend your time doing with Git. -By the end of the chapter, you should be able to configure and initialize a repository, begin and stop tracking files, and stage and commit changes. -We'll also show you how to set up Git to ignore certain files and file patterns, how to undo mistakes quickly and easily, how to browse the history of your project and view changes between commits, and how to push and pull from remote repositories. - -[[links]] -=== Links - -.Links to DSA -- Chapter: <> -- Section: <> - -// In <> we used it to specify our name, email address and editor preference before we even got started using Git. - -[source,console] ----- -$ git commit ----- - -Doing so launches your editor of choice. -(This is set by your shell's `EDITOR` environment variable -- usually vim or emacs, although you can configure it with whatever you want using the `git config --global core.editor` command as you saw in - -// <>).(((editor, changing default)))(((git commands, config))) - - - - - -=== Summary - -At this point, you can do all the basic local Git operations -- creating or cloning a repository, making changes, staging and committing those changes, and viewing the history of all the changes the repository has been through. -Next, we'll cover Git's killer feature: its branching model. diff --git a/book/chapters/about.adoc b/book/chapters/about.adoc new file mode 100644 index 00000000..73d625c5 --- /dev/null +++ b/book/chapters/about.adoc @@ -0,0 +1 @@ += About the author diff --git a/book/chapters/acknowledgements.adoc b/book/chapters/acknowledgements.adoc new file mode 100644 index 00000000..37ecc759 --- /dev/null +++ b/book/chapters/acknowledgements.adoc @@ -0,0 +1 @@ += Acknowledgements diff --git a/book/chapters/action.adoc b/book/chapters/action.adoc new file mode 100644 index 00000000..1e88adb2 --- /dev/null +++ b/book/chapters/action.adoc @@ -0,0 +1,4 @@ +[[action]] += Action! + +== Additional reading diff --git a/book/content/part04/algorithmic-toolbox.asc b/book/chapters/algorithmic-toolbox.adoc similarity index 90% rename from book/content/part04/algorithmic-toolbox.asc rename to book/chapters/algorithmic-toolbox.adoc index 467d5180..b9041c5a 100644 --- a/book/content/part04/algorithmic-toolbox.asc +++ b/book/chapters/algorithmic-toolbox.adoc @@ -1,10 +1,4 @@ -ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src -endif::[] - -[[algorithms-toolbox]] -=== Algorithmic Toolbox += Algorithmic Toolbox Have you ever given a programming problem and freeze without knowing where to start? Well, in this section we are going to give some tips, so you don't get stuck while coding. @@ -23,7 +17,7 @@ TIP: TL;DR: Don't start coding right away. First, solve the problem, then write .. If anything else fails, how would you solve it the dumbest way possible (brute force). We can optimize it later. . *Test* your algorithm idea with multiple examples . *Optimize* the solution –Only optimize when you have something working don't try to do both at the same time! -.. Can you trade-off space for speed? Use a <> to speed up results! +.. Can you trade-off space for speed? Use a <> to speed up results! .. Do you have a bunch of recursive and overlapping problems? Try <>. .. Re-read requirements and see if you can take advantage of anything. E.g. is the array sorted? . *Write Code*, yes, now you can code. diff --git a/book/content/part01/algorithms-analysis.asc b/book/chapters/algorithms-analysis.adoc similarity index 90% rename from book/content/part01/algorithms-analysis.asc rename to book/chapters/algorithms-analysis.adoc index fab397fe..38361721 100644 --- a/book/content/part01/algorithms-analysis.asc +++ b/book/chapters/algorithms-analysis.adoc @@ -1,16 +1,16 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -=== Fundamentals of Algorithms Analysis += Fundamentals of Algorithms Analysis Probably you are reading this book because you want to write better and faster code. How can you do that? Can you time how long it takes to run a program? Of course, you can! [big]#⏱# However, if you run the same program on a smartwatch, cellphone or desktop computer, it will take different times. -image::image3.png[image,width=528,height=137] +image:image3.png[image,width=528,height=137] Wouldn't it be great if we can compare algorithms regardless of the hardware where we run them? That's what *time complexity* is for! @@ -24,7 +24,7 @@ We could also compare the memory "used" by different algorithms, and we called t Before going deeper into space and time complexity, let's cover the basics real quick. -==== What are Algorithms? +== What are Algorithms? Algorithms (as you might know) are steps of how to do some tasks. When you cook, you follow a recipe (or an algorithm) to prepare a dish. Let's say you want to make a pizza. @@ -47,7 +47,7 @@ If you play a game, you are devising strategies (or algorithms) to help you win. TIP: Algorithms are instructions on how to perform a task. -==== Comparing Algorithms +== Comparing Algorithms (((Comparing Algorithms))) Not all algorithms are created equal. There are “good” and “bad” algorithms. The good ones are fast; the bad ones are slow. Slow algorithms cost more money to run. Inefficient algorithms could make some calculations impossible in our lifespan! @@ -65,18 +65,18 @@ To give you a clearer picture of how different algorithms perform as the input s |Find all permutations of a string |4 sec. |> vigintillion years |> centillion years |∞ |∞ |============================================================================================= -Most algorithms are affected by the size of the input (`n`). Let's say you need to arrange numbers in ascending order. Sorting ten items will naturally take less time than sorting out 2 million. But, how much longer? As the input size grow, some algorithms take proportionally more time, we classify them as <> runtime [or `O(n)`]. Others might take power two longer; we call them <> running time [or `O(n^2^)`]. +Most algorithms are affected by the size of the input (`n`). Let's say you need to arrange numbers in ascending order. Sorting ten items will naturally take less time than sorting out 2 million. But, how much longer? As the input size grow, some algorithms take proportionally more time, we classify them as <> runtime [or `O(n)`]. Others might take power two longer; we call them <> running time [or `O(n^2^)`]. -From another perspective, if you keep the input size the same and run different algorithms implementations, you would notice the difference between an efficient algorithm and a slow one. For example, a good sorting algorithm is <>, and an inefficient algorithm for large inputs is <>. +From another perspective, if you keep the input size the same and run different algorithms implementations, you would notice the difference between an efficient algorithm and a slow one. For example, a good sorting algorithm is <>, and an inefficient algorithm for large inputs is <>. Organizing 1 million elements with merge sort takes 20 seconds while bubble sort takes 12 days, ouch! The amazing thing is that both programs are solving the same problem with equal data and hardware; and yet, there's a big difference in time! After completing this book, you are going to _think algorithmically_. You will be able to scale your programs while you are designing them. -Find bottlenecks of existing software and have an <> to optimize algorithms and make them faster without having to pay more for cloud computing (e.g., AWS EC2 instances). [big]#💸# +Find bottlenecks of existing software and have an <> to optimize algorithms and make them faster without having to pay more for cloud computing (e.g., AWS EC2 instances). [big]#💸# <<< -==== Increasing your code performance +== Increasing your code performance The first step to improve your code performance is to measure it. As somebody said: @@ -86,14 +86,14 @@ Measurement is the first step that leads to control and eventually to improvemen In this section, we are going to learn the basics of measuring our current code performance and compare it with other algorithms. -===== Calculating Time Complexity +=== Calculating Time Complexity (((Time complexity))) Time complexity, in computer science, is a function that describes the number of operations a program will execute given the size of the input `n`. How do you get a function that gives you the number of operations that will be executed? Well, we count line by line and mind code inside loops. Let's do an example to explain this point. For instance, we have a function to find the minimum value on an array called `getMin`. .Translating lines of code to an approximate number of operations -image::image4.png[Operations per line] +image:image4.png[Operations per line] Assuming that each line of code is an operation, we get the following: @@ -103,13 +103,13 @@ _3n + 3_ That means that if you have an array of 3 elements e.g. `getMin([3, 2, 9])`, then it will execute around _3(3)+3 = 12_ operations. Of course, this is not for every case. For instance, Line 12 is only executed if the condition on line 11 is met. As you might learn in the next section, we want to get the big picture and get rid of smaller terms to compare algorithms easier. -==== Space Complexity +== Space Complexity (((Space Complexity))) Space complexity is similar to time complexity. However, instead of the count of operations executed, it will account for the amount of memory used additionally to the input. For calculating the *space complexity* we keep track of the “variables” and memory used. In the `getMin` example, we just create a single variable called `min`. So, the space complexity is 1. On other algorithms, If we have to use an auxiliary array, then the space complexity would be `n`. -===== Simplifying Complexity with Asymptotic Analysis +=== Simplifying Complexity with Asymptotic Analysis (((Asymptotic Analysis))) When we are comparing algorithms, we don't want to have complex expressions. What would you prefer comparing two algorithms like "3n^2^ + 7n" vs. "1000 n + 2000" or compare them as "n^2^ vs. n"? Well, that when the asymptotic analysis comes to the rescue. @@ -129,7 +129,7 @@ In the previous example, we analyzed `getMin` with an array of size 3; what happ As the input size `n` grows bigger and bigger then the expression _3n + 3_ is closer and closer to _3n_. Dropping terms might look like a stretch at first, but you will see that what matters the most is the higher order terms of the function rather than lesser terms and constants. -===== What is Big O Notation? +=== What is Big O Notation? (((Big O))) There’s a notation called *Big O*, where `O` refers to the *order of the function*. @@ -176,7 +176,7 @@ This just an illustration since in different hardware the times will be slightly NOTE: These times are under the assumption of running on 1 GHz CPU and it can execute on average one instruction in 1 nanosecond (usually takes more time). Also, keep in mind that each line might be translated into dozens of CPU instructions depending on the programming language. Regardless, bad algorithms would perform poorly even on a supercomputer. -==== Summary +== Summary In this chapter, we learned how you could measure your algorithm performance using time complexity. Rather than timing how long your program takes to run you can approximate the number of operations it will perform based on the input size. diff --git a/book/part04-algorithmic-toolbox.asc b/book/chapters/algorithms-intro.adoc similarity index 73% rename from book/part04-algorithmic-toolbox.asc rename to book/chapters/algorithms-intro.adoc index 81e4f476..d44ad6e6 100644 --- a/book/part04-algorithmic-toolbox.asc +++ b/book/chapters/algorithms-intro.adoc @@ -1,6 +1,3 @@ -[[part04-algorithmic-toolbox]] -== Algorithmic Toolbox - In this part of the book, we are going to cover examples of classical algorithms in more details. Also, we will provide algorithmic tools for improving your problem-solving skills. @@ -8,7 +5,7 @@ IMPORTANT: There's not a single approach to solve all problems but knowing well- We are going to start with <> // and searching algorithms, -such as <>, <> and some others. +such as <>, <> and some others. Later, you are going to learn some algorithmic paradigms that will help you to identify common patterns and solve problems from different angles. @@ -18,22 +15,3 @@ Later, you are going to learn some algorithmic paradigms that will help you to i - <>: _divide_ problems into smaller pieces, _conquer_ each subproblem and then _join_ the results. - <>: search _all (or some)_ possible paths. However, it stops and _go back_ as soon as notice the current solution is not working. - _Brute Force_: generate all possible solutions and tries all of them. (Use it as a last resort or as the starting point to optimize it with other techniques). - - -include::content/part04/sorting-algorithms.asc[] - -<<< -include::content/part04/divide-and-conquer.asc[] - -<<< -include::content/part04/dynamic-programming.asc[] - -<<< -include::content/part04/greedy-algorithms.asc[] - -<<< -include::content/part04/backtracking.asc[] - -<<< -include::content/part04/algorithmic-toolbox.asc[] - diff --git a/book/chapters/appendix.adoc b/book/chapters/appendix.adoc new file mode 100644 index 00000000..50074282 --- /dev/null +++ b/book/chapters/appendix.adoc @@ -0,0 +1,3 @@ +// Appendix A: +include::cheatsheet.adoc[] + diff --git a/book/content/part02/array.asc b/book/chapters/array.adoc similarity index 94% rename from book/content/part02/array.asc rename to book/chapters/array.adoc index 41bd6ef3..4d89ddda 100644 --- a/book/content/part02/array.asc +++ b/book/chapters/array.adoc @@ -1,15 +1,14 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -[[array]] -=== Array += Array (((Array))) (((Data Structures, Linear, Array))) Arrays are one of the most used data structures. You probably have used it a lot but are you aware of the runtimes of `splice`, `shift`, `indexOf` and other operations? In this chapter, we are going deeper into the most common operations and their runtimes. -==== Array Basics +== Array Basics An array is a collection of things (strings, characters, numbers, objects, etc.). They can be many or zero. @@ -23,11 +22,11 @@ Some programming languages have fixed size arrays like Java and C++. Fixed size Arrays look like this: .Array representation: each value is accessed through an index. -image::image16.png[image,width=388,height=110] +image:image16.png[image,width=388,height=110] Arrays are a sequential collection of elements that can be accessed randomly using an index. Let’s take a look into the different operations that we can do with arrays. -==== Insertion +== Insertion Arrays are built-in into most languages. Inserting an element is simple; you can either add them on creation time or after initialization. Below you can find an example for both cases: @@ -55,7 +54,7 @@ console.log(array2); // [empty × 3, 1, empty × 96, 2] The runtime for inserting elements using index is always is constant: _O(1)_. -===== Inserting at the beginning of the array +=== Inserting at the beginning of the array What if you want to insert a new element at the beginning of the array? You would have to push every item to the right. @@ -76,7 +75,7 @@ The `unshift()` method adds one or more elements to the beginning of an array an Runtime: O(n). **** -===== Inserting at the middle of the array +=== Inserting at the middle of the array Inserting a new element in the middle involves moving part of the array but not all of the items. @@ -98,7 +97,7 @@ The `splice()` method changes the contents of an array by removing existing elem Runtime: O(n). **** -===== Inserting at the end of the array +=== Inserting at the end of the array We can push new values to the end of the array like this: @@ -120,8 +119,7 @@ The `push()` method adds one or more elements to the end of an array and returns Runtime: O(1). **** -[[array-search-by-value]] -==== Searching by value and index +== Searching by value and index Searching by index is very easy using the `[]` operator: @@ -182,11 +180,11 @@ function searchByValue(array, value) { We would have to loop through the whole array (worst case) or until we find it: *O(n)*. -==== Deletion +== Deletion Deleting (similar to insertion) there are three possible scenarios, removing at the beginning, middle or end. -===== Deleting element from the beginning +=== Deleting element from the beginning Deleting from the beginning can be done using the `splice` function and also the `shift`. For simplicity, we will use the latter. @@ -209,7 +207,7 @@ The `shift()` method shift all elements to the left. In turn, it removes the fir Runtime: O(n). **** -===== Deleting element from the middle +=== Deleting element from the middle We can use the `splice` method for deleting an item from the middle of an array. @@ -225,7 +223,7 @@ array.splice(2, 1); // ↪️[2] <1> Deleting from the middle might cause most the elements of the array to move back one position to fill in for the eliminated item. Thus, runtime: O(n). -===== Deleting element from the end +=== Deleting element from the end Removing the last element is very straightforward: @@ -246,7 +244,7 @@ The `pop()` method removes the last element from an array and returns that eleme Runtime: O(1). **** -==== Array Complexity +== Array Complexity To sum up, the time complexity of an array is: (((Tables, Linear DS, Array Complexities))) diff --git a/book/chapters/avl-tree.adoc b/book/chapters/avl-tree.adoc new file mode 100644 index 00000000..569c30df --- /dev/null +++ b/book/chapters/avl-tree.adoc @@ -0,0 +1,5 @@ += AVL Tree + +The AVL tree builds on top of a <> and it keeps it balanced on insertions. It prevents a BST worst case scenario when the tree is totally unbalanced to one side (similar to linked list), then it takes O(n) to find an element instead of O(log n). + + diff --git a/book/content/part04/backtracking.asc b/book/chapters/backtracking.adoc similarity index 93% rename from book/content/part04/backtracking.asc rename to book/chapters/backtracking.adoc index 7faaf9f1..ee58db8a 100644 --- a/book/content/part04/backtracking.asc +++ b/book/chapters/backtracking.adoc @@ -1,9 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -=== Backtracking += Backtracking (((Backtracking))) (((Algorithmic Techniques, Backtracking))) Backtracking algorithms are used to find *all (or some)* solutions that satisfy a constraint. @@ -15,7 +15,7 @@ it stops and steps back (backtracks) to try another alternative. Some examples that use backtracking is a solving Sudoku/crosswords puzzle, and graph operations. ifndef::backend-pdf[] -image::Sudoku_solved_by_bactracking.gif[] +image:Sudoku_solved_by_bactracking.gif[] endif::backend-pdf[] Listing all possible solutions might sound like a brute force. @@ -30,7 +30,7 @@ It stops evaluating a path as soon as some of the conditions are broken and move However, it can only be applied if a quick test can be run to tell if a candidate will contribute to a valid solution. **** -==== How to develop backtracking algorithms? +== How to develop backtracking algorithms? Backtracking algorithms can be tricky to get right or reason about, but we are going to follow this recipe to make it easier. @@ -45,7 +45,7 @@ Let's do an exercise to explain better how backtracking works. // https://leetcode.com/problems/combination-sum/description/ -==== Permutations +== Permutations (((Permutations))) (((Words permutations))) > Return all the permutations (without repetitions) of a word. @@ -63,7 +63,7 @@ For instace, if you are given the word `art` these are the possible permutations Now, let's implement the program to generate all permutations of a word. -NOTE: We already solved this problem using an <>, now let's do it using backtracking. +NOTE: We already solved this problem using an <>, now let's do it using backtracking. .Word permutations using backtracking [source, javascript] @@ -80,7 +80,7 @@ As you can see, we iterate through each element and swap with the following lett In the following tree, you can visualize how the backtracking algorithm is swapping the letters. We are taking the `art` as an example. -[graphviz, Words-Permutations, png] +[graphviz, Words Permutations, svg] .... digraph g { node [shape = record,height=.1]; diff --git a/book/content/part01/big-o-examples.asc b/book/chapters/big-o-examples.adoc similarity index 82% rename from book/content/part01/big-o-examples.asc rename to book/chapters/big-o-examples.adoc index 38cb3ba5..171553b1 100644 --- a/book/content/part01/big-o-examples.asc +++ b/book/chapters/big-o-examples.adoc @@ -1,13 +1,14 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -=== Big O examples += Big O examples +// = Eight Running Times You Should Know There are many kinds of algorithms. Most of them fall into one of the eight of the time complexities that we are going to explore in this chapter. -.Eight Running Time complexity You Should Know +.Most common time complexities - Constant time: _O(1)_ - Logarithmic time: _O(log n)_ - Linear time: _O(n)_ @@ -22,18 +23,17 @@ We a going to provide examples for each one of them. Before we dive in, here’s a plot with all of them. .CPU operations vs. Algorithm runtime as the input size grows -image::image5.png[CPU time needed vs. Algorithm runtime as the input size increases] +image:image5.png[CPU time needed vs. Algorithm runtime as the input size increases] The above chart shows how the running time of an algorithm is related to the amount of work the CPU has to perform. As you can see O(1) and O(log n) are very scalable. However, O(n^2^) and worst can make your computer run for years [big]#😵# on large datasets. We are going to give some examples so you can identify each one. -[[constant]] -==== Constant +== Constant (((Constant))) (((Runtime, Constant))) Represented as *O(1)*, it means that regardless of the input size the number of operations executed is always the same. Let’s see an example. [#constant-example] -===== Finding if an array is empty +=== Finding if an array is empty Let's implement a function that finds out if an array is empty or not. @@ -45,19 +45,18 @@ Let's implement a function that finds out if an array is empty or not. include::{codedir}/runtimes/01-is-empty.js[tag=isEmpty] ---- -Another more real life example is adding an element to the begining of a <>. You can check out the implementation <>. +Another more real life example is adding an element to the begining of a <>. You can check out the implementation <>. As you can see, in both examples (array and linked list) if the input is a collection of 10 elements or 10M it would take the same amount of time to execute. You can't get any more performance than this! -[[logarithmic]] -==== Logarithmic +== Logarithmic (((Logarithmic))) (((Runtime, Logarithmic))) Represented in Big O notation as *O(log n)*, when an algorithm has this running time it means that as the size of the input grows the number of operations grows very slowly. Logarithmic algorithms are very scalable. One example is the *binary search*. indexterm:[Runtime, Logarithmic] -[[logarithmic-example]] -===== Searching on a sorted array +[#logarithmic-example] +=== Searching on a sorted array The binary search only works for sorted lists. It starts searching for an element on the middle of the array and then it moves to the right or left depending if the value you are looking for is bigger or smaller. @@ -72,14 +71,13 @@ This binary search implementation is a recursive algorithm, which means that the Finding the runtime of recursive algorithms is not very obvious sometimes. It requires some tools like recursion trees or the https://adrianmejia.com/blog/2018/04/24/analysis-of-recursive-algorithms/[Master Theorem]. The `binarySearch` divides the input in half each time. As a rule of thumb, when you have an algorithm that divides the data in half on each call you are most likely in front of a logarithmic runtime: _O(log n)_. -[[linear]] -==== Linear +== Linear (((Linear))) (((Runtime, Linear))) Linear algorithms are one of the most common runtimes. It’s represented as *O(n)*. Usually, an algorithm has a linear running time when it iterates over all the elements in the input. -[[linear-example]] -===== Finding duplicates in an array using a map +[#linear-example] +=== Finding duplicates in an array using a map Let’s say that we want to find duplicate elements in an array. What’s the first implementation that comes to mind? Check out this implementation: @@ -99,14 +97,13 @@ As we learned before, the big O cares about the worst-case scenario, where we wo Space complexity is also *O(n)* since we are using an auxiliary data structure. We have a map that in the worst case (no duplicates) it will hold every word. -[[linearithmic]] -==== Linearithmic +== Linearithmic (((Linearithmic))) (((Runtime, Linearithmic))) An algorithm with a linearithmic runtime is represented as _O(n log n)_. This one is important because it is the best runtime for sorting! Let’s see the merge-sort. -[[linearithmic-example]] -===== Sorting elements in an array +[#linearithmic-example] +=== Sorting elements in an array The ((Merge Sort)), like its name indicates, has two functions merge and sort. Let’s start with the sort function: @@ -132,22 +129,21 @@ include::{codedir}/algorithms/sorting/merge-sort.js[tag=merge] The merge function combines two sorted arrays in ascending order. Let’s say that we want to sort the array `[9, 2, 5, 1, 7, 6]`. In the following illustration, you can see what each function does. .Mergesort visualization. Shows the split, sort and merge steps -image::image11.png[Mergesort visualization,width=500,height=600] +image:image11.png[Mergesort visualization,width=500,height=600] How do we obtain the running time of the merge sort algorithm? The mergesort divides the array in half each time in the split phase, _log n_, and the merge function join each splits, _n_. The total work we have *O(n log n)*. There more formal ways to reach to this runtime like using the https://adrianmejia.com/blog/2018/04/24/analysis-of-recursive-algorithms/[Master Method] and https://www.cs.cornell.edu/courses/cs3110/2012sp/lectures/lec20-master/lec20.html[recursion trees]. -[[quadratic]] -==== Quadratic +== Quadratic (((Quadratic))) (((Runtime, Quadratic))) Running times that are quadratic, O(n^2^), are the ones to watch out for. They usually don’t scale well when they have a large amount of data to process. Usually, they have double-nested loops that where each one visits all or most elements in the input. One example of this is a naïve implementation to find duplicate words on an array. -[[quadratic-example]] -===== Finding duplicates in an array (naïve approach) +[#quadratic-example] +=== Finding duplicates in an array (naïve approach) -If you remember we have solved this problem more efficiently on the <> section. We solved this problem before using an _O(n)_, let’s solve it this time with an _O(n^2^)_: +If you remember we have solved this problem more efficiently on the <> section. We solved this problem before using an _O(n)_, let’s solve it this time with an _O(n^2^)_: // image:image12.png[image,width=527,height=389] @@ -159,16 +155,15 @@ include::{codedir}/runtimes/05-has-duplicates-naive.js[tag=hasDuplicates] As you can see, we have two nested loops causing the running time to be quadratic. How much different is a linear vs. quadratic algorithm? -Let’s say you want to find a duplicated middle name in a phone directory book of a city of ~1 million people. If you use this quadratic solution you would have to wait for ~12 days to get an answer [big]#🐢#; while if you use the <> you will get the answer in seconds! [big]#🚀# +Let’s say you want to find a duplicated middle name in a phone directory book of a city of ~1 million people. If you use this quadratic solution you would have to wait for ~12 days to get an answer [big]#🐢#; while if you use the <> you will get the answer in seconds! [big]#🚀# -[[cubic]] -==== Cubic +== Cubic (((Cubic))) (((Runtime, Cubic))) Cubic *O(n^3^)* and higher polynomial functions usually involve many nested loops. As an example of a cubic algorithm is a multi-variable equation solver (using brute force): -[[cubic-example]] -===== Solving a multi-variable equation +[#cubic-example] +=== Solving a multi-variable equation Let’s say we want to find the solution for this multi-variable equation: @@ -188,16 +183,15 @@ WARNING: This just an example, there are better ways to solve multi-variable equ As you can see three nested loops usually translates to O(n^3^). If you have a four variable equation and four nested loops it would be O(n^4^) and so on when we have a runtime in the form of _O(n^c^)_, where _c > 1_, we can refer as a *polynomial runtime*. -[[exponential]] -==== Exponential +== Exponential (((Exponential))) (((Runtime, Exponential))) Exponential runtimes, O(2^n^), means that every time the input grows by one the number of operations doubles. Exponential programs are only usable for a tiny number of elements (<100) otherwise it might not finish on your lifetime. [big]#💀# Let’s do an example. -[[exponential-example]] -===== Finding subsets of a set +[#exponential-example] +=== Finding subsets of a set Finding all distinct subsets of a given set can be implemented as follows: @@ -219,8 +213,7 @@ include::{codedir}/runtimes/07-sub-sets.js[tag=snippet] Every time the input grows by one the resulting array doubles. That’s why it has an *O(2^n^)*. -[[factorial]] -==== Factorial +== Factorial (((Factorial))) (((Runtime, Factorial))) Factorial runtime, O(n!), is not scalable at all. Even with input sizes of ~10 elements, it will take a couple of seconds to compute. It’s that slow! [big]*🍯🐝* @@ -236,8 +229,8 @@ A factorial is the multiplication of all the numbers less than itself down to 1. - 11! = 39,916,800 **** -[[factorial-example]] -===== Getting all permutations of a word +[#factorial-example] +=== Getting all permutations of a word (((Permutations))) (((Words permutations))) One classic example of an _O(n!)_ algorithm is finding all the different words that can be formed with a given set of letters. @@ -254,7 +247,7 @@ As you can see in the `getPermutations` function, the resulting array is the fac Factorial start very slow and then it quickly becomes uncontrollable. A word size of just 11 characters would take a couple of hours in most computers! [big]*🤯* -==== Summary +== Summary We went through 8 of the most common time complexities and provided examples for each of them. Hopefully, this will give you a toolbox to analyze algorithms. (((Tables, Intro, Common time complexities and examples))) @@ -268,35 +261,35 @@ We went through 8 of the most common time complexities and provided examples for |Example(s) |O(1) -|<> -|<> +|<> +|<> |O(log n) -|<> -|<> +|<> +|<> |O(n) -|<> -|<> +|<> +|<> |O(n log n) -|<> -|<> +|<> +|<> |O(n^2^) -|<> -|<> +|<> +|<> |O(n^3^) -|<> -|<> +|<> +|<> |O(2^n^) -|<> -|<> +|<> +|<> |O(n!) -|<> -|<> +|<> +|<> |=== // end::table[] diff --git a/book/content/part04/bubble-sort.asc b/book/chapters/bubble-sort.adoc similarity index 85% rename from book/content/part04/bubble-sort.asc rename to book/chapters/bubble-sort.adoc index f49ddac3..007dda7a 100644 --- a/book/content/part04/bubble-sort.asc +++ b/book/chapters/bubble-sort.adoc @@ -1,9 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -==== Bubble Sort += Bubble Sort (((Bubble Sort))) (((Sorting, Bubble Sort))) (((Sinking Sort))) @@ -15,7 +15,7 @@ However, if the array is entirely out of order, it will require _O(n^2^)_ to sor (((Quadratic))) (((Runtime, Quadratic))) -===== Bubble Sort Implementation +== Bubble Sort Implementation .Bubble Sort implementation in JavaScript [source, javascript] @@ -66,15 +66,15 @@ console.log(b); //️↪️ 1 Without the destructuring assignment, swapping two values requires a temporary variable. **** -Bubble sort has a <> running time, as you might infer from the nested for-loop. +Bubble sort has a <> running time, as you might infer from the nested for-loop. -===== Bubble Sort Properties +== Bubble Sort Properties - <>: [big]#✅# Yes - <>: [big]#✅# Yes - <>: [big]#✅# Yes - <>: [big]#✅# Yes, _O(n)_ when already sorted -- Time Complexity: [big]#⛔️# <> _O(n^2^)_ -- Space Complexity: [big]#✅# <> _O(1)_ +- Time Complexity: [big]#⛔️# <> _O(n^2^)_ +- Space Complexity: [big]#✅# <> _O(1)_ indexterm:[Runtime, Quadratic] diff --git a/book/chapters/cheatsheet.adoc b/book/chapters/cheatsheet.adoc new file mode 100644 index 00000000..06308b35 --- /dev/null +++ b/book/chapters/cheatsheet.adoc @@ -0,0 +1,38 @@ +[appendix] += Time Complexity Cheatsheet + +This section summerize what we are going to cover in the rest of this book. + +== Runtimes + +include::big-o-examples.adoc[tag=table] + +include::algorithms-analysis.adoc[tag=table] + +== Linear Data Structures + +include::linear-data-structures-outro.adoc[tag=table] + +== Trees and Maps Data Structures + +This section covers Binary Search Tree (BST) time complexity (Big O). + +include::part3.adoc[tag=table] + +include::graph.adoc[tag=table] + +== Sorting Algorithms + +include::sorting-summary.adoc[tag=table] + +// https://algs4.cs.princeton.edu/cheatsheet/ +// http://bigocheatsheet.com/ + +// https://en.wikipedia.org/wiki/Timsort (Tim Peters) +// https://bugs.python.org/file4451/timsort.txt +// https://www.youtube.com/watch?v=emeME__917E&list=PLMCXHnjXnTntLcLmA5SqhMspm7burHi3m + +// https://en.wikipedia.org/wiki/Sorting_algorithm +// http://sorting.at/ +// https://www.toptal.com/developers/sorting-algorithms +// https://www.infopulse.com/blog/timsort-sorting-algorithm/ diff --git a/book/content/colophon.asc b/book/chapters/colophon.adoc similarity index 87% rename from book/content/colophon.asc rename to book/chapters/colophon.adoc index c6860171..5be4908a 100644 --- a/book/content/colophon.asc +++ b/book/chapters/colophon.adoc @@ -1,5 +1,5 @@ [colophon#colophon%nonfacing] -== Data Structures & Algorithms in JavaScript += {doctitle} Copyright © {docyear} Adrian Mejia @@ -11,5 +11,4 @@ No part of this publication may be produced, store in a retrieval system, or tra While every precaution has been taking in the preparation of this book, the publisher and author assume no responsibility for errors or omissions, or damages resulting from the use of the information contained herein. -// {revremark}, {revdate}. -Version {revnumber}, {revdate}. +{revremark}, {revdate}. diff --git a/book/chapters/dedication.adoc b/book/chapters/dedication.adoc new file mode 100644 index 00000000..e9c9590b --- /dev/null +++ b/book/chapters/dedication.adoc @@ -0,0 +1,5 @@ +[dedication] += Dedication + +To my wife Nathalie that supported me in my long hours of writing and my baby girl Abigail. + diff --git a/book/content/part04/divide-and-conquer.asc b/book/chapters/divide-and-conquer-fibonacci.adoc similarity index 61% rename from book/content/part04/divide-and-conquer.asc rename to book/chapters/divide-and-conquer-fibonacci.adoc index be80b106..07d99a3a 100644 --- a/book/content/part04/divide-and-conquer.asc +++ b/book/chapters/divide-and-conquer-fibonacci.adoc @@ -1,31 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -=== Divide and Conquer - -(((Divide and Conquer))) -(((Algorithmic Techniques, Divide and Conquer))) -Divide and conquer is a strategy for solving algorithmic problems. -It splits the input into manageable parts recursively and finally joins solved pieces to form the solution. - -We have already implemented some algorithms using the divide and conquer technique. - -.Examples of divide and conquer algorithms: -- <>: *divides* the input into pairs, sort them, and them *join* all the pieces in ascending order. -- <>: *splits* the data by a random number called "pivot", then move everything smaller than the pivot to the left and anything more significant to the right. Repeat the process on the left and right side. Note: since this works in place doesn't need a "join" part. -- <>: find a value in a sorted collection by *splitting* the data in half until it sees the value. -- <>: *Take out* the first element from the input and solve permutation for the remainder of the data recursively, then *join* results and append the items that were taken out. - -.In general, the divide and conquer algorithms have the following pattern: -1. *Divide* data into subproblems. -2. *Conquer* each subproblem. -3. *Combine* results. - -As you might know, there are multiple ways to solve a problem. Let's solve the Fibonacci numbers using a divide and conquer algorithm. Later we are going to provide a more performant solution using dynamic programming. - -==== Recursive Fibonacci Numbers += Recursive Fibonacci Numbers (((Fibonacci))) To illustrate how we can solve a problem using divide and conquer, let's write a program to find the n-th Fibonacci number. @@ -54,7 +32,7 @@ The implementation above does the job, but what's the runtime? For that, let's take a look at the job performed calculating the `fib(5)` number. Since `fib(5) = fib(4) + fib(3)`, we need to find the answer for `fib(4)` and `fib(3)`. We do that recursively until we reach the base cases of `fib(1)` and `fib(0)`. If we represent the calls in a tree, we would have the following: // http://bit.ly/2UmwzZV -[graphviz, recursive-fibonacci-call-tree, png] +[graphviz, Recursive Fibonacci call tree, png] .... graph G { "fib(5)" -- { "fib(4)", "fib(3)" } @@ -92,5 +70,3 @@ In the call tree, you can notice that every element in red and with asterisks `* Those who cannot remember the past are condemned to repeat it. For these cases when subproblems repeat themselves, we can optimize them using <>. Let's do that in the next section. - - diff --git a/book/chapters/divide-and-conquer-intro.adoc b/book/chapters/divide-and-conquer-intro.adoc new file mode 100644 index 00000000..5876599f --- /dev/null +++ b/book/chapters/divide-and-conquer-intro.adoc @@ -0,0 +1,17 @@ +(((Divide and Conquer))) +(((Algorithmic Techniques, Divide and Conquer))) +Divide and conquer is a strategy for solving algorithmic problems. +It splits the input into manageable parts recursively and finally joins solved pieces to form the solution. + +We have already implemented some algorithms using the divide and conquer technique. + +.Examples of divide and conquer algorithms: +- <>: *divides* the input into pairs, sort them, and them *join* all the pieces in ascending order. +- <>: *splits* the data by a random number called "pivot", then move everything smaller than the pivot to the left and anything more significant to the right. Repeat the process on the left and right side. Note: since this works in place doesn't need a "join" part. +- <>: find a value in a sorted collection by *splitting* the data in half until it sees the value. +- <>: *Take out* the first element from the input and solve permutation for the remainder of the data recursively, then *join* results and append the items that were taken out. + +.In general, the divide and conquer algorithms have the following pattern: +1. *Divide* data into subproblems. +2. *Conquer* each subproblem. +3. *Combine* results. diff --git a/book/chapters/divide-and-conquer.adoc b/book/chapters/divide-and-conquer.adoc new file mode 100644 index 00000000..b394687c --- /dev/null +++ b/book/chapters/divide-and-conquer.adoc @@ -0,0 +1,9 @@ += Divide and Conquer + +include::divide-and-conquer-intro.adoc[] + +:leveloffset: +1 + +include::divide-and-conquer-fibonacci.adoc[] + +:leveloffset: -1 diff --git a/book/chapters/dynamic-programming-fibonacci.adoc b/book/chapters/dynamic-programming-fibonacci.adoc new file mode 100644 index 00000000..86e513ec --- /dev/null +++ b/book/chapters/dynamic-programming-fibonacci.adoc @@ -0,0 +1,35 @@ +ifndef::imagesdir[] +:imagesdir: ../images +:codedir: ../../src +endif::[] + += Fibonacci Sequence with Dynamic Programming +(((Fibonacci))) +Let's solve the same Fibonacci problem but this time with dynamic programming. + +When we have recursive functions doing duplicated work is the perfect place for a dynamic programming optimization. We can save (or cache) the results of previous operations and speed up future computations. + +.Recursive Fibonacci Implemenation using Dynamic Programming +[source, javascript] +---- +include::{codedir}/algorithms/fibanacci-dynamic-programming.js[tag=snippet,indent=0] +---- + +This implementation checks if we already calculated the value, if so it will save it for later use. + +[graphviz, Recursive Fibonacci call tree with dp, svg] +.... +graph G { + "fib(5)" -- { "fib(4)" } + "fib(4)" -- { "fib(3)" } + "fib(3)" -- { "fib(2)" } + "fib(2)" -- { "fib(1)", "fib(0)" } +} +.... + +This graph looks pretty linear now. It's runtime _O(n)_! +(((Linear))) +(((Runtime, Linear))) + +(((Memoization))) +TIP: Saving previous results for later is a technique called "memoization". This is very common to optimize recursive algorithms with overlapping subproblems. It can make exponential algorithms linear! diff --git a/book/chapters/dynamic-programming-intro.adoc b/book/chapters/dynamic-programming-intro.adoc new file mode 100644 index 00000000..f40e9371 --- /dev/null +++ b/book/chapters/dynamic-programming-intro.adoc @@ -0,0 +1,30 @@ +ifndef::imagesdir[] +:imagesdir: ../images +:codedir: ../../src +endif::[] + +(((Dynamic Programming))) +(((Algorithmic Techniques, Dynamic Programming))) +Dynamic programming (dp) is a way to solve algorithmic problems with *overlapping subproblems*. Algorithms using dp find the base case and building a solution from the ground-up. Dp _keep track_ of previous results to avoid re-computing the same operations. + +// https://twitter.com/amejiarosario/status/1103050924933726208 +// https://www.quora.com/How-should-I-explain-dynamic-programming-to-a-4-year-old/answer/Jonathan-Paulson +// https://medium.com/@codingfreak/top-50-dynamic-programming-practice-problems-4208fed71aa3 +// https://www.slideshare.net/balamoorthy39/greedy-algorithm-knapsack-problem + +.How to explain dynamic programming to kids? 👶 +**** + +$$*$$*_Write down 1+1+1+1+1+1+1+1+1+1_*$$*$$ + +--{sp} What's that equal to? + +--{sp} $$*$$*_Kid counting one by one_*$$*$$ Ten! + +--{sp} Add another "+1". What's the total now? + +--{sp} $$*$$*_Quickly_*$$*$$ Eleven! + +--{sp} Why you get the result so quickly? Ah, you got it faster by adding one to the memorized previous answer. So Dynamic Programming is a fancy way of saying: "remembering past solutions to save time later." +**** + diff --git a/book/chapters/dynamic-programming-knapsack-problem.adoc b/book/chapters/dynamic-programming-knapsack-problem.adoc new file mode 100644 index 00000000..b581632f --- /dev/null +++ b/book/chapters/dynamic-programming-knapsack-problem.adoc @@ -0,0 +1,52 @@ +ifndef::imagesdir[] +:imagesdir: ../images +:codedir: ../../src +endif::[] + += Knapsack Problem + +The knapsack (backpack [big]#🎒#) problem is the following: + +> A thief breaks into a museum with a backpack that can carry certain weight. +What items shoud he pick to maximize his loot? + +Take a look at the following example to understand better the problem. + +.Knapsack Problem Examples +[source, javascript] +---- + +// Input: +const museumGoods = [ + { value: 1, weight: 1}, + { value: 4, weight: 3 }, + { value: 5, weight: 4 }, + { value: 7, weight: 5 }, +] + +const maxBackpackWeight = 7; + +// Solution: +const backpack = solveKnapsackProblem(museumGoods, maxBackpackWeight); + +// Output: +expect(backpack.items).to.equal([ + { value: 4, weight: 3 }, + { value: 5, weight: 4 } +]) + +expect(backpack.weight).toBeLessThanOrEqual(7); +expect(backpack.value).toBe(9); +---- + +How can we solve this problem? You cannot take them all since total weight is 13 and we only can carry 7. You should not take only one, since that would not be the maximum loot and you would + +One idea would be sort the items by weight and take the items if they do not exceed the max weight. +In that case, the result would be: + +---- + { value: 7, weight: 5 }, + { value: 1, weight: 1}, +---- + +As you can see, this solution is not optimal. The value total value is `8` and the weight just `6`. diff --git a/book/chapters/dynamic-programming.adoc b/book/chapters/dynamic-programming.adoc new file mode 100644 index 00000000..269a4178 --- /dev/null +++ b/book/chapters/dynamic-programming.adoc @@ -0,0 +1,16 @@ +ifndef::imagesdir[] +:imagesdir: ../images +:codedir: ../../src +endif::[] + += Dynamic Programming + +include::dynamic-programming-intro.adoc[] + +:leveloffset: +1 + +include::dynamic-programming-fibonacci.adoc[] + +// include::chapters/dynamic-programming-knapsack-problem.adoc[] + +:leveloffset: -1 diff --git a/book/chapters/epigraph.adoc b/book/chapters/epigraph.adoc new file mode 100644 index 00000000..a70dd6d3 --- /dev/null +++ b/book/chapters/epigraph.adoc @@ -0,0 +1,4 @@ +[epigraph] += Epigraph + +Thanks for reading this book. Stay effective! diff --git a/book/content/part03/graph-search.asc b/book/chapters/graph-search.adoc similarity index 65% rename from book/content/part03/graph-search.asc rename to book/chapters/graph-search.adoc index d19d5fd6..cb64a3bc 100644 --- a/book/content/part03/graph-search.asc +++ b/book/chapters/graph-search.adoc @@ -1,9 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -=== Graph Search += Graph Search Graph search allows you to visit search elements. @@ -11,38 +11,36 @@ WARNING: Graph search is very similar to <>. So, if yo There are two ways to navigate the graph, one is using Depth-First Search (DFS) and the other one is Breadth-First Search (BFS). Let's see the difference using the following graph. -image::directed-graph.png[directed graph] +[graphviz, directed graph, png] +.... +digraph G { -// [graphviz, directed graph, png] -// .... -// digraph G { + node [fillcolor="#F8E71C" style=filled shape=circle] 0; + node [fillcolor="#F5A623"] 1; + node [fillcolor="#B8E986"] 2; + node [fillcolor="#BD10E0"] 3; + node [fillcolor="#50E3C2"] 4; + node [fillcolor="#4A90E2"] 5; + // node [fillcolor="#FF5252"] 6; -// node [fillcolor="#F8E71C" style=filled shape=circle] 0; -// node [fillcolor="#F5A623"] 1; -// node [fillcolor="#B8E986"] 2; -// node [fillcolor="#BD10E0"] 3; -// node [fillcolor="#50E3C2"] 4; -// node [fillcolor="#4A90E2"] 5; -// // node [fillcolor="#FF5252"] 6; + 0 -> 5 + 0 -> 4 + 0 -> 1 + 1 -> 4 + 1 -> 3 + 2 -> 1 + 3 -> 4 + 3 -> 2 -// 0 -> 5 -// 0 -> 4 -// 0 -> 1 -// 1 -> 4 -// 1 -> 3 -// 2 -> 1 -// 3 -> 4 -// 3 -> 2 + // label="DFS" -// // label="DFS" + { rank=same; 3, 1 } + { rank=same; 0, 4 } -// { rank=same; 3, 1 } -// { rank=same; 0, 4 } +} +.... -// } -// .... - -==== Depth-First Search for Graphs +== Depth-First Search for Graphs With Depth-First Search (DFS) we go deep before going wide. @@ -54,7 +52,7 @@ So, DFS would visit the graph: `[0, 5, 1, 3, 2, 4]`. // TODO: add arrows to show DFS and create another one for BFS -==== Breadth-First Search for Graphs +== Breadth-First Search for Graphs With Breadth-First Search (BFS) we go wide before going deep. @@ -66,7 +64,7 @@ After all the children of node `0` are visited it continue with all the children In summary, BFS would visit the graph: `[0, 5, 1, 4, 3, 2]` -==== Depth-First Search vs. Breadth-First Search in a Graph +== Depth-First Search vs. Breadth-First Search in a Graph DFS and BFS can implementation can be almost identical; the difference is the underlying data structured. In our implementation, we have a generic `graphSearch` where we pass the first element to start the search the data structure that we can to use: @@ -76,13 +74,13 @@ DFS and BFS can implementation can be almost identical; the difference is the un include::{codedir}/data-structures/graphs/graph.js[tag=graphSearch,indent=0] ---- -Using an <> (LIFO) for DFS will make use keep visiting the last node children while having a <> (FIFO) will allow to visit adjacent nodes first and "queue" their children for later visiting. +Using an <> (LIFO) for DFS will make use keep visiting the last node children while having a <> (FIFO) will allow to visit adjacent nodes first and "queue" their children for later visiting. -TIP: you can also implement the DFS as a recursive function, similar to what we did in the <>. +TIP: you can also implement the DFS as a recursive function, similar to what we did in the <>. You might wonder what the difference between search algorithms in a tree and a graph is? Check out the next section. -==== DFS/BFS on Tree vs Graph +== DFS/BFS on Tree vs Graph The difference between searching a tree and a graph is that the tree always has a starting point (root node). However, in a graph, you can start searching anywhere. There's no root. diff --git a/book/content/part03/graph.asc b/book/chapters/graph.adoc similarity index 91% rename from book/content/part03/graph.asc rename to book/chapters/graph.adoc index 35d15b40..a4ee9352 100644 --- a/book/content/part03/graph.asc +++ b/book/chapters/graph.adoc @@ -1,10 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -[[graph]] -=== Graph += Graph (((Graph))) (((Data Structures, Non-Linear, Graph))) Graphs are one of my favorite data structures. @@ -27,32 +26,32 @@ The only difference is that it uses an *array* of adjacent nodes instead of just Other difference between a linked list and graph is that a linked list always has a root node (or first element), while the graph doesn’t. You can start traversing a graph from anywhere. Let’s examine these graph properties! -==== Graph Properties +== Graph Properties The connection between two nodes is called *edge*. Also, nodes might be called *vertex*. .Graph is composed of vertices/nodes and edges -image::image42.png[image,width=305,height=233] +image:image42.png[image,width=305,height=233] -===== Directed Graph vs Undirected +=== Directed Graph vs Undirected A graph can be either *directed* or *undirected*. .Graph: directed vs undirected -image::image43.jpg[image,width=469,height=192] +image:image43.jpg[image,width=469,height=192] An *undirected graph* has edges that are *two-way street*. E.g., On the undirected example, you can traverse from the green node to the orange and vice versa. A *directed graph (digraph)* has edges that are *one-way street*. E.g., On the directed example, you can only go from green node to orange and not the other way around. When one node has an edge to itself is called a *self-loop*. -===== Graph Cycles +=== Graph Cycles A graph can have *cycles* or not. .Cyclic vs Acyclic Graphs. -image::image44.jpg[image,width=444,height=194] +image:image44.jpg[image,width=444,height=194] (((Cyclic Graph))) A *cyclic graph* is the one that you can pass through a node more than once. @@ -65,10 +64,10 @@ An acyclic graph is the one that you can’t pass through a node more than once. (((DAG))) The *Directed Acyclic Graph (DAG)* is unique. It has many applications like scheduling tasks, spreadsheets change propagation, and so forth. DAG is also called *Tree* data structure only when each node has only *one parent*. -===== Connected vs Disconnected vs Complete Graphs +=== Connected vs Disconnected vs Complete Graphs .Different kinds of graphs: disconnected, connected, and complete. -image::image45.png[image,width=1528,height=300] +image:image45.png[image,width=1528,height=300] A *disconnected graph* is one that has one or more subgraph. In other words, a graph is *disconnected* if two nodes don’t have a path between them. @@ -76,16 +75,16 @@ A *connected graph* is the opposite to disconnected, there’s a path between ev A *complete graph* is where every node is adjacent to all the other nodes in the graph. E.g., If there are seven nodes, every node has six edges. -===== Weighted Graphs +=== Weighted Graphs (((Weighted Graphs))) Weighted graphs have labels in the edges (a.k.a *weight* or *cost*). The link weight can represent many things like distance, travel time, or anything else. .Weighted Graph representing USA airports distance in miles. -image::image46.png[image,width=528,height=337] +image:image46.png[image,width=528,height=337] For instance, a weighted graph can have a distance between nodes. So, algorithms can use the weight and optimize the path between them. -==== Exciting Graph applications in real-world +== Exciting Graph applications in real-world Now that we know what graphs are and some of their properties. Let’s discuss some real-life usages of graphs. @@ -108,19 +107,19 @@ Graphs become a metaphor where nodes and edges model something from our physical There are endless applications for graphs in electronics, social networks, recommendation systems and many more. That’s cool and all, but how do we represent graphs in code? Let’s see that in the next section. -==== Representing Graphs +== Representing Graphs There are two main ways to graphs one is: * Adjacency Matrix * Adjacency List -===== Adjacency Matrix +=== Adjacency Matrix (((Adjacency Matrix))) Representing graphs as adjacency matrix is done using a two-dimensional array. For instance, let’s say we have the following graph: .Graph and its adjacency matrix. -image::image47.png[image,width=438,height=253] +image:image47.png[image,width=438,height=253] The number of vertices |V| define the size of the matrix. In the example, we have five vertices, so we have a 5x5 matrix. @@ -162,16 +161,16 @@ TIP: When the graph has few connections compared to the number of nodes we say t The space complexity of the adjacency matrix is *O(|V|^2^)*, where |V| is the number of vertices/nodes. -===== Adjacency List +=== Adjacency List (((Adjacency List))) Another way to represent a graph is by using an adjacency list. This time instead of using an array (matrix) we use a list. .Graph represented as an Adjacency List. -image::image48.png[image,width=528,height=237] +image:image48.png[image,width=528,height=237] If we want to add a new node to the list, we can do it by adding one element to the end of the array of nodes *O(1)*. In the next section, we are going to explore the running times of all operations in an adjacency list. -==== Implementing a Graph data structure +== Implementing a Graph data structure Since adjacency lists are more efficient (than adjacency matrix), we are going to use to implement a graph data structure. @@ -185,7 +184,7 @@ include::{codedir}/data-structures/graphs/graph.js[tag=constructor] Notice that the constructor takes a parameter. The `edgeDirection` allow us to use one class for both undirected and directed graphs. -==== Adding a vertex +== Adding a vertex For adding a vertex, we first need to check if the node already exists. If so, we return the node. @@ -200,7 +199,7 @@ include::{codedir}/data-structures/graphs/graph.js[tag=addVertex, indent=0] If the node doesn't exist, then we create the new node and add it to a `HashMap`. -TIP: <> stores key/pair value very efficiently. Lookup is `O(1)`. +TIP: <> stores key/pair value very efficiently. Lookup is `O(1)`. The `key` is the node's value, while the `value` is the newly created node. @@ -213,7 +212,7 @@ include::{codedir}/data-structures/graphs/node.js[tag=constructor, indent=0] ---- -==== Deleting a vertex +== Deleting a vertex .Graphs's `removeVertex` method [source, javascript] @@ -236,7 +235,7 @@ include::{codedir}/data-structures/graphs/node.js[tag=removeAdjacent, indent=0] All adjacencies are stored as a HashSet to provide constant time deletion. -==== Adding an edge +== Adding an edge An edge is a connection between two nodes (vertices). If the graph is undirected means that every link is a two-way street. When we create the edge from node 1 to node 2, we also need to establish a connection between node 2 and 1 for undirected graphs. @@ -259,7 +258,7 @@ We can add adjacencies using the `addAdjacent` method from the Node class. include::{codedir}/data-structures/graphs/node.js[tag=addAdjacent, indent=0] ---- -==== Querying Adjacency +== Querying Adjacency .Graphs's `areAdjacents` method [source, javascript] @@ -274,7 +273,7 @@ include::{codedir}/data-structures/graphs/node.js[tag=isAdjacent, indent=0] ---- -==== Deleting an edge +== Deleting an edge .Graphs's `removeEdge` method [source, javascript] @@ -288,7 +287,7 @@ include::{codedir}/data-structures/graphs/graph.js[tag=removeEdge, indent=0] include::{codedir}/data-structures/graphs/node.js[tag=removeAdjacent, indent=0] ---- <<< -==== Graph Complexity +== Graph Complexity (((Tables, Non-Linear DS, Graph adjacency matrix/list complexities))) // tag::table[] diff --git a/book/chapters/greedy-algorithms-intro.adoc b/book/chapters/greedy-algorithms-intro.adoc new file mode 100644 index 00000000..f97bb42a --- /dev/null +++ b/book/chapters/greedy-algorithms-intro.adoc @@ -0,0 +1,50 @@ +ifndef::imagesdir[] +:imagesdir: ../images +:codedir: ../../src +endif::[] + +(((Greedy Algorithms))) +(((Algorithmic Techniques, Greedy Algorithms))) +Greedy algorithms are designed to find a solution by going one step at a time and using heuristics to determine the best choice. +They are quick but not always lead to most optimum results since it might not take into consideration all the options to give a solution. + +An excellent example of a greedy algorithm that doesn't work well is finding the largest sum on a tree. + +[graphviz, Find the largest sum, svg] +.... +graph G { + 5 -- 3 [color="#B8E986", penwidth=2] + 5 -- 7 [color="#FF5252", penwidth=2] + 3 -- 87 [color="#B8E986", penwidth=2] + 3 -- 1 + 7 -- 2 + 7 -- 4 [color="#FF5252", penwidth=2] + + label="Optimal vs. Greedy path" +} +.... + +Let's say the greedy heuristics are set to take the more significant value. The greedy algorithm will start at the root and say "Which number is bigger 3 or 7?" Then go with 7 and later 4. As you can see in the diagram, the most significant sum would be the path `7 - 3 - 87`. A greedy algorithm never goes back on its options. This greedy choice makes it different from dynamic programming which exhaustive and it's guaranteed to find the best option. However, when they work well, they are usually faster than other options. + +Greedy algorithms are well suited when an optimal local solution is also a globally optimal solution. + +[TIP] +==== +Greedy algorithms make the choice that looks best at the moment based on a heuristic such as smallest, largest, best ratio, and so on. +This algorithm only gives one shot at finding the solution and never goes back to consider other options. +==== + +Don't get the wrong idea; some greedy algorithms work very well if they are designed correctly. + +.Some examples of greedy algorithms that works well: +- <>: we select the best (minimum value) remove it from the input and then select the next minimum until everything is processed. +- <>: the "merge" uses a greedy algorithm, where it combines two sorted arrays by looking at their current values and choosing the best (minimum) at every time. +indexterm:[Merge Sort] + + +.In general, we can follow these steps to design Greedy Algorithms: +1. Take a sample from the input data (usually in a data structure like array/list, tree, graph). +2. Greedy choice: use a heuristic function that will choose the best candidate. E.g., Largest/smallest number, best ratio, etc. +3. Reduce the processed input and repeat step #1 and #2 until all data is gone. +4. Return solution. +5. Check correctness with different examples and edge cases. diff --git a/book/chapters/greedy-algorithms-knapsack-problem.adoc b/book/chapters/greedy-algorithms-knapsack-problem.adoc new file mode 100644 index 00000000..7993b180 --- /dev/null +++ b/book/chapters/greedy-algorithms-knapsack-problem.adoc @@ -0,0 +1,57 @@ +ifndef::imagesdir[] +:imagesdir: ../images +:codedir: ../../src +endif::[] + += Fractional Knapsack Problem + +We are going to use the "Fractional Knapsack Problem" to learn how to design greedy algorithms. The problem is the following: + +> You are going to resell legumes (rice, beans, chickpeas, lentils) and you only brought a knapsack. What proportion of items can you choose to get the highest loot without exceeding the maximum weight of the bag? + +Let's say we have the following items available. + +.Knpasack Input +[source, javascript] +---- +const items = [ + { value: 1, weight: 1}, + { value: 4, weight: 3 }, + { value: 5, weight: 4 }, + { value: 7, weight: 5 }, +]; + +const maxWeight = 7; +---- + +So, we have four items that we can choose from. We can't take them all because the total weight is `13` and the maximum we can carry is `7`. We can't just take the first one because with value `1` because it is not the best profit. + +How would you solve this problem? + +First, we have to define what parameters are we going to use to make our *greedy choice*. This some ideas: + +- We can take items with the *largest* value in hopes to maximize profit. Based on that we can make take the last item and first having a total weight of 7 and a total cost of 8. + +- Also, we could take items *smallest* weight so we can fit as much as possible. Let's analyze both options. So we can choose the first two items for a total value of 5 and a total weight of 4. This is worse than picking the largest value! [big]#👎# + +- One last idea, we can take items based on the *best* value/weight ratio and take fractions of an article to fill up the knapsack to maximum weight. In that case, we can buy the last item in full and 2/3 of the 2nd item. We get a total value of `9.67` and a total weight of `7`. This heuristics seems to be the most profitable. [big]#👍# + +.Items value/weight ratio +---- + { value: 1, weight: 1 }, // 1/1 = 1 + { value: 4, weight: 3 }, // 4/3 = 1.33 ✅ + { value: 5, weight: 4 }, // 5/4 = 1.25 + { value: 7, weight: 5 }, // 7/5 = 1.4 ✅ +---- + +Let's implement this algorithm! + +.Factional Knapsack Problem Implementation +[source, javascript] +---- +include::{codedir}/algorithms/knapsack-fractional.js[tag=snippet,indent=0] +---- + +What's the runtime of this algorithm? + +We have to sort the array based on value/weight ratio. Sorting runtime is O(n log n). The rest is linear operations, so we the answer is _O(n log n)_ for our greedy algorithm. diff --git a/book/chapters/greedy-algorithms.adoc b/book/chapters/greedy-algorithms.adoc new file mode 100644 index 00000000..43685ec7 --- /dev/null +++ b/book/chapters/greedy-algorithms.adoc @@ -0,0 +1,9 @@ += Greedy Algorithms + +include::greedy-algorithms-intro.adoc[] + +:leveloffset: +1 + +include::greedy-algorithms-knapsack-problem.adoc[] + +:leveloffset: -1 diff --git a/book/chapters/heap-sort.adoc b/book/chapters/heap-sort.adoc new file mode 100644 index 00000000..421cab79 --- /dev/null +++ b/book/chapters/heap-sort.adoc @@ -0,0 +1,7 @@ += Heap Sort + +Voluptate consequat magna laborum consectetur fugiat deserunt. Id sit est ullamco magna sint laborum proident. Exercitation cupidatat exercitation excepteur ex pariatur qui qui sint amet consectetur laborum ex mollit dolore. + +Et do sunt do labore culpa est eu ut fugiat eiusmod ea excepteur. Irure commodo adipisicing in aute aliquip laborum laboris reprehenderit incididunt in sunt. Cupidatat veniam est culpa ex eu aute voluptate tempor aliqua ullamco sunt et consectetur. Eu laboris mollit culpa consequat. Sunt mollit quis dolor nostrud. In duis mollit do adipisicing veniam do deserunt exercitation Lorem deserunt aliquip. Ea esse reprehenderit incididunt eu deserunt sit nulla sint non eiusmod nisi eu et irure. + +Ad commodo anim nulla occaecat non. Aute fugiat laborum ut mollit exercitation aute proident reprehenderit culpa consectetur. Cillum officia laborum proident labore sunt est eiusmod proident. Lorem nostrud ea qui tempor culpa ullamco ipsum. Dolore nulla minim qui incididunt qui sint consectetur quis tempor esse minim. Do id consequat commodo sit officia aliqua officia reprehenderit eiusmod elit do amet. diff --git a/book/chapters/heap.adoc b/book/chapters/heap.adoc new file mode 100644 index 00000000..104af153 --- /dev/null +++ b/book/chapters/heap.adoc @@ -0,0 +1,3 @@ += Heap + +Sit nostrud Lorem nulla ipsum occaecat enim eiusmod adipisicing velit et cupidatat laboris incididunt. Sunt ex eiusmod amet nulla quis. Officia elit non sunt esse sint. Non enim do laborum adipisicing officia et aliquip cillum ut nisi ipsum. Minim duis minim velit amet laborum aliquip pariatur irure deserunt ex. diff --git a/book/chapters/index.adoc b/book/chapters/index.adoc new file mode 100644 index 00000000..85e81b85 --- /dev/null +++ b/book/chapters/index.adoc @@ -0,0 +1,2 @@ +[index] += Index diff --git a/book/chapters/insertion-selection-bubble-sort.adoc b/book/chapters/insertion-selection-bubble-sort.adoc new file mode 100644 index 00000000..4b0ea8e2 --- /dev/null +++ b/book/chapters/insertion-selection-bubble-sort.adoc @@ -0,0 +1,3 @@ += Insertion vs Selection vs Bubble Sort + +All these three algorithms diff --git a/book/content/part04/insertion-sort.asc b/book/chapters/insertion-sort.adoc similarity index 81% rename from book/content/part04/insertion-sort.asc rename to book/chapters/insertion-sort.adoc index c8bc818d..c44c4c5e 100644 --- a/book/content/part04/insertion-sort.asc +++ b/book/chapters/insertion-sort.adoc @@ -1,10 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -[[insertion-sort]] -==== Insertion Sort += Insertion Sort (((Sorting, Insertion Sort))) (((Insertion Sort))) @@ -12,7 +11,7 @@ Insertion sort is a simple sorting algorithm. It is one of the most natural ways // Good illustration on of sorting a deck of cards: https://www.khanacademy.org/computing/computer-science/algorithms/insertion-sort/a/insertion-sort -===== Insertion Sort Implementation +== Insertion Sort Implementation Insertion sort does the following: It starts from the 2nd element, and it tries to find anything to the left that could be bigger than the current item. It will swap all the elements with higher value @@ -29,14 +28,14 @@ include::{codedir}/algorithms/sorting/insertion-sort.js[tag=sort, indent=0] <4> We check every element on the left side and swap any of them that are out of order -===== Insertion Sort Properties +== Insertion Sort Properties - <>: [big]#✅# Yes - <>: [big]#✅# Yes - <>: [big]#✅# Yes - <>: [big]#✅# Yes -- Time Complexity: [big]#⛔️# <> _O(n^2^)_ -- Space Complexity: [big]#✅# <> _O(1)_ +- Time Complexity: [big]#⛔️# <> _O(n^2^)_ +- Space Complexity: [big]#✅# <> _O(1)_ (((Quadratic))) (((Runtime, Quadratic))) diff --git a/book/part02-linear-data-structures.asc b/book/chapters/linear-data-structures-intro.adoc similarity index 59% rename from book/part02-linear-data-structures.asc rename to book/chapters/linear-data-structures-intro.adoc index ad0db79e..a9b5d3b3 100644 --- a/book/part02-linear-data-structures.asc +++ b/book/chapters/linear-data-structures-intro.adoc @@ -1,17 +1,16 @@ -[[part02-linear-data-structures]] -== Linear Data Structures - +[partintro] +-- Data Structures comes in many flavors. There’s no one to rule them all. You have to know the tradeoffs so you can choose the right one for the job. Even though in your day-to-day, you might not need to re-implementing them, knowing how they work internally would help you how when to use over the other or even tweak them to create a new one. We are going to explore the most common data structures time and space complexity. .In this part we are going to learn about the following linear data structures: -- <> -- <> -- <> -- <> +- <> +- <> +- <> +- <> -Later, in the next part, we are going to explore non-linear data structures like <> and <>. +Later, in the next part, we are going to explore non-linear data structures like <> and <>. ifdef::backend-html5[] If you want to have a general overview of each one, take a look at the following interactive diagram: @@ -22,18 +21,4 @@ If you want to have a general overview of each one, take a look at the following +++ endif::[] -include::content/part02/array.asc[] - -<<< -include::content/part02/linked-list.asc[] - -<<< -include::content/part02/stack.asc[] - -<<< -include::content/part02/queue.asc[] - -<<< -include::content/part02/array-vs-list-vs-queue-vs-stack.asc[] - - +-- diff --git a/book/content/part02/array-vs-list-vs-queue-vs-stack.asc b/book/chapters/linear-data-structures-outro.adoc similarity index 57% rename from book/content/part02/array-vs-list-vs-queue-vs-stack.asc rename to book/chapters/linear-data-structures-outro.adoc index 127ce33b..dc0a0696 100644 --- a/book/content/part02/array-vs-list-vs-queue-vs-stack.asc +++ b/book/chapters/linear-data-structures-outro.adoc @@ -1,9 +1,4 @@ -ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src -endif::[] - -=== Array vs. Linked List & Queue vs. Stack += Array vs. Linked List & Queue vs. Stack In this part of the book, we explored the most used linear data structures such as Arrays, Linked Lists, Stacks and Queues. We implemented them and discussed the runtime of their operations. @@ -18,24 +13,23 @@ In this part of the book, we explored the most used linear data structures such .Use a Queue when: * You need to access your data in a first-come, first served basis (FIFO). -* You need to implement a <> +* You need to implement a <> .Use a Stack when: * You need to access your data as last-in, first-out (LIFO). -* You need to implement a <> +* You need to implement a <> (((Tables, Linear DS, Array/Lists/Stack/Queue complexities))) -[[linear-data-structures-table]] // tag::table[] .Time/Space Complexity of Linear Data Structures (Array, LinkedList, Stack & Queues) |=== .2+.^s| Data Structure 2+^s| Searching By 3+^s| Inserting at the 3+^s| Deleting from .2+.^s| Space ^|_Index/Key_ ^|_Value_ ^|_beginning_ ^|_middle_ ^|_end_ ^|_beginning_ ^|_middle_ ^|_end_ -| <> ^|O(1) ^|O(n) ^|O(n) ^|O(n) ^|O(1) ^|O(n) ^|O(n) ^|O(1) ^|O(n) -| <> ^|O(n) ^|O(n) ^|O(1) ^|O(n) ^|O(1) ^|O(1) ^|O(n) ^|*O(n)* ^|O(n) -| <> ^|O(n) ^|O(n) ^|O(1) ^|O(n) ^|O(1) ^|O(1) ^|O(n) ^|*O(1)* ^|O(n) -| <> ^|- ^|- ^|- ^|- ^|O(1) ^|- ^|- ^|O(1) ^|O(n) +| <> ^|O(1) ^|O(n) ^|O(n) ^|O(n) ^|O(1) ^|O(n) ^|O(n) ^|O(1) ^|O(n) +| <> ^|O(n) ^|O(n) ^|O(1) ^|O(n) ^|O(1) ^|O(1) ^|O(n) ^|*O(n)* ^|O(n) +| <> ^|O(n) ^|O(n) ^|O(1) ^|O(n) ^|O(1) ^|O(1) ^|O(n) ^|*O(1)* ^|O(n) +| <> ^|- ^|- ^|- ^|- ^|O(1) ^|- ^|- ^|O(1) ^|O(n) | Queue (w/array) ^|- ^|- ^|- ^|- ^|*O(n)* ^|- ^|- ^|O(1) ^|O(n) -| <> (w/list) ^|- ^|- ^|- ^|- ^|O(1) ^|- ^|- ^|O(1) ^|O(n) +| <> (w/list) ^|- ^|- ^|- ^|- ^|O(1) ^|- ^|- ^|O(1) ^|O(n) |=== // end::table[] diff --git a/book/content/part02/linked-list.asc b/book/chapters/linked-list.adoc similarity index 89% rename from book/content/part02/linked-list.asc rename to book/chapters/linked-list.adoc index f253ac34..50f103cf 100644 --- a/book/content/part02/linked-list.asc +++ b/book/chapters/linked-list.adoc @@ -1,10 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -[[linked-list]] -=== Linked List += Linked List (((Linked List))) (((List))) (((Data Structures, Linear, Linked List))) @@ -15,23 +14,22 @@ A list (or Linked List) is a linear data structure where each node is "linked" t - Doubly: every node has a reference to the next and previous object - Circular: the last element points to the first one. -[[singly-linked-list]] -==== Singly Linked List + +== Singly Linked List Each element or node is *connected* to the next one by a reference. When a node only has one connection it's called *singly linked list*: .Singly Linked List Representation: each node has a reference (blue arrow) to the next one. -image::image19.png[image,width=498,height=97] +image:image19.png[image,width=498,height=97] Usually, a Linked List is referenced by the first element in called *head* (or *root* node). For instance, if you want to get the `cat` element from the example above, then the only way to get there is using the `next` field on the head node. You would get `art` first, then use the next field recursively until you eventually get the `cat` element. -[[doubly-linked-list]] -==== Doubly Linked List +== Doubly Linked List When each node has a connection to the `next` item and also the `previous` one, then we have a *doubly linked list*. .Doubly Linked List: each node has a reference to the next and previous element. -image::image20.png[image,width=528,height=74] +image:image20.png[image,width=528,height=74] With a doubly list you can not only move forward but also backward. If you keep the reference to the last element (`cat`) you can step back and reach the middle part. @@ -45,7 +43,7 @@ If we implement the code for the `Node` elements, it would be something like thi include::{codedir}/data-structures/linked-lists/node.js[tag=snippet] ---- -==== Linked List vs. Array +== Linked List vs. Array Arrays allow you to access data anywhere in the collection using an index. However, Linked List visits nodes in sequential order. In the worst case scenario, it takes _O(n)_ to get an element from a Linked List. You might be wondering: Isn’t always an array more efficient with _O(1)_ access time? It depends. @@ -55,7 +53,7 @@ Another difference is that adding/deleting at the beginning on an array takes O( A drawback of a linked list is that if you want to insert/delete an element at the end of the list, you would have to navigate the whole collection to find the last one O(n). However, this can be solved by keeping track of the last element in the list. We are going to implement that! -==== Implementing a Linked List +== Implementing a Linked List We are going to implement a doubly linked list. First, let's start with the constructor. @@ -72,7 +70,7 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=constructor] In our constructor, we keep a reference of the `first` and also `last` node for performance reasons. -==== Searching by value +== Searching by value Finding an element by value there’s no other way than iterating through the whole list. @@ -99,7 +97,7 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=find, indent= We are going to use this `find` method again to implement searching by index. -==== Searching by index +== Searching by index Searching by index is very similar, we iterate through the list until we find the element that matches the position. @@ -111,21 +109,22 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=searchByIndex If there’s no match, we return `undefined` then. The runtime is _O(n)_. As you might notice the search by index and by position methods looks pretty similar. If you want to take a look at the whole implementation https://github.com/amejiarosario/dsa.js/blob/7694c20d13f6c53457ee24fbdfd3c0ac57139ff4/src/data-structures/linked-lists/linked-list.js#L8[click here]. -==== Insertion +== Insertion Similar to the array, with a linked list you can add elements at the beginning, end or anywhere in the middle of the list. So, let's implement each case. -[[linked-list-inserting-beginning]] -===== Inserting elements at the beginning of the list +=== Inserting elements at the beginning of the list We are going to use the `Node` class to create a new element and stick it at the beginning of the list as shown below. .Insert at the beginning by linking the new node with the current first node. -image::image23.png[image,width=498,height=217] +image:image23.png[image,width=498,height=217] To insert at the beginning, we create a new node with the next reference to the current first node. Then we make first the new node. In code, it would look something like this: +[#linked-list-inserting-beginning] + .Add item to the beginning of a Linked List [source, javascript] ---- @@ -135,12 +134,12 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=addFirst, ind As you can see, we create a new node and make it the first one. -===== Inserting element at the end of the list +=== Inserting element at the end of the list Appending an element at the end of the list can be done very effectively if we have a pointer to the `last` item in the list. Otherwise, you would have to iterate through the whole list. .Add element to the end of the linked list -image::image24.png[image,width=498,height=208] +image:image24.png[image,width=498,height=208] .Linked List's add to the end of the list implementation [source, javascript] @@ -151,7 +150,7 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=addLast, inde If there’s no element in the list yet, the first and last node would be the same. If there’s something, then, we go to the `last` item and add the reference `next` to the new node. That’s it! We got a constant time for inserting at the beginning and the end of the list: *O(1)*. -===== Inserting element at the middle of the list +=== Inserting element at the middle of the list For inserting an element at the middle of the list, you would need to specify the position (index) in the collection. Then, you create the new node and update the references to it. @@ -171,7 +170,7 @@ art <-> dog <-> cat We want to insert the `new` node in the 2^nd^ position. For that we first create the "new" node and update the references around it. .Inserting node in the middle of a doubly linked list. -image::image25.png[image,width=528,height=358] +image:image25.png[image,width=528,height=358] Take a look into the implementation of https://github.com/amejiarosario/dsa.js/blob/master/src/data-structures/linked-lists/linked-list.js#L83[LinkedList.add]: @@ -190,16 +189,16 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=addMiddle, in Take notice that we reused, `addFirst` and `addLast` methods. For all the other cases the insertion is in the middle. We use `current.previous.next` and `current.next` to update the surrounding elements and make them point to the new node. Inserting on the middle takes *O(n)* because we have to iterate through the list using the `get` method. -==== Deletion +== Deletion Deleting is an interesting one. We don’t delete an element; we remove all references to that node. Let’s go case by case to explore what happens. -===== Deleting element from the head +=== Deleting element from the head Deleting the first element (or head) is a matter of removing all references to it. .Deleting an element from the head of the list -image::image26.png[image,width=528,height=74] +image:image26.png[image,width=528,height=74] For instance, to remove the head (“art”) node, we change the variable `first` to point to the second node “dog”. We also remove the variable `previous` from the "dog" node, so it doesn't point to the “art” node. The garbage collector will get rid of the “art” node when it seems nothing is using it anymore. @@ -211,12 +210,12 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=removeFirst, As you can see, when we want to remove the first node we make the 2nd element the first one. -===== Deleting element from the tail +=== Deleting element from the tail Removing the last element from the list would require to iterate from the head until we find the last one, that’s O(n). But, If we have a reference to the last element, which we do, We can do it in _O(1)_ instead! .Removing last element from the list using the last reference. -image::image27.png[image,width=528,height=221] +image:image27.png[image,width=528,height=221] For instance, if we want to remove the last node “cat”. We use the last pointer to avoid iterating through the whole list. We check `last.previous` to get the “dog” node and make it the new `last` and remove its next reference to “cat”. Since nothing is pointing to “cat” then is out of the list and eventually is deleted from memory by the garbage collector. @@ -230,12 +229,12 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=removeLast, i The code is very similar to `removeFirst`, but instead of first we update `last` reference, and instead of nullifying `previous` we nullify its `next` reference. -===== Deleting element from the middle +=== Deleting element from the middle To remove a node from the middle, we make the surrounding nodes to bypass the one we want to delete. .Remove the middle node -image::image28.png[image,width=528,height=259] +image:image28.png[image,width=528,height=259] In the illustration, we are removing the middle node “dog” by making art’s `next` variable to point to cat and cat’s `previous` to be “art” totally bypassing “dog”. @@ -250,7 +249,7 @@ include::{codedir}/data-structures/linked-lists/linked-list.js[tag=removeByPosit Notice that we are using the `get` method to get the node at the current position. That method loops through the list until it found the node at the specified location. This iteration has a runtime of _O(n)_. -==== Linked List Complexity vs. Array Complexity +== Linked List Complexity vs. Array Complexity So far, we have seen two liner data structures with different use cases. Here’s a summary: (((Tables, Linear DS, Array/Lists complexities))) @@ -279,9 +278,9 @@ Use arrays when: Use a doubly linked list when: -* You want to access elements in a *sequential* manner only like <> or <>. +* You want to access elements in a *sequential* manner only like <> or <>. * You want to insert elements at the start and end of the list. The linked list has O(1) while array has O(n). * You want to save some memory when dealing with possibly large data sets. Arrays pre-allocate a large chunk of contiguous memory on initialization. Lists are more “grow as you go”. -For the next two linear data structures <> and <>, we are going to use a doubly linked list to implement them. We could use an array as well, but since inserting/deleting from the start perform better on linked-list, we are going use that. +For the next two linear data structures <> and <>, we are going to use a doubly linked list to implement them. We could use an array as well, but since inserting/deleting from the start perform better on linked-list, we are going use that. diff --git a/book/content/part03/map.asc b/book/chapters/map-hashmap-vs-treemap.adoc similarity index 55% rename from book/content/part03/map.asc rename to book/chapters/map-hashmap-vs-treemap.adoc index d03dae4f..732d81e1 100644 --- a/book/content/part03/map.asc +++ b/book/chapters/map-hashmap-vs-treemap.adoc @@ -1,39 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -[[map]] -=== Map -(((Map))) -(((Data Structures, Non-Linear, Map))) -A map is a data structure to store pairs of data: *key* and *value*. In an array, you can only store values. The array’s key is always the position index. However, in a *Map* the key can be whatever you want. - -IMPORTANT: Map is a data structure that _maps_ *keys* to *values*. - -Many languages have maps already built-in. JavaScript/Node has `Map`: - -.JavaScript Built-in Map Usage -[source, javascript] ----- -include::{codedir}/data-structures/maps/map.js[tag=snippet, indent=0] ----- - -In short, you set `key`/`value` pair and then you can get the `value` using the `key`. - -The attractive part of Maps is that they are very performant usually *O(1)* or *O(log n)* depending on the implementation. We can implement the maps using two different underlying data structures: - -* *HashMap*: it’s a map implementation using an *array* and a *hash function*. The job of the hash function is to convert the `key` into an index that maps to the `value`. Optimized HashMap can have an average runtime of *O(1)*. -* *TreeMap*: it’s a map implementation that uses a self-balanced Binary Search Tree (like <>). The BST nodes store the key, and the value and nodes are sorted by key guaranteeing an *O(log n)* look up. - -<<< -include::hashmap.asc[] - -<<< -include::treemap.asc[] - -<<< -==== HashMap vs TreeMap += HashMap vs TreeMap .A map can be implemented using hash functions or binary search tree: - *HashMap*: it’s a map implementation using an *array* and *hash function*. The job of the hash function is to convert the key into an index that contains the matching data. Optimized HashMap can have an average runtime of *O(1)*. @@ -42,12 +12,12 @@ include::treemap.asc[] .When to use a TreeMap vs. HashMap? * `HashMap` is more time-efficient. A `TreeMap` is more space-efficient. -* `TreeMap` search complexity is *O(log n)*, while an optimized `HashMap` is *O(1)* on average. +* `TreeMap` search complexity is *O(log n)*, while an optimized `HashMap` is *O(1)* on average.  * `HashMap`’s keys are in insertion order (or random depending in the implementation). `TreeMap`’s keys are always sorted. * `TreeMap` offers some statistical data for free such as: get minimum, get maximum, median, find ranges of keys. `HashMap` doesn’t. * `TreeMap` has a guarantee always an *O(log n)*, while `HashMap`s has an amortized time of *O(1)* but in the rare case of a rehash, it would take an *O(n)*. -===== TreeMap Time complexity vs HashMap +== TreeMap Time complexity vs HashMap As we discussed so far, there is a trade-off between the implementations. (((Tables, Non-Linear DS, HashMap/TreeMap complexities))) diff --git a/book/content/part03/hashmap.asc b/book/chapters/map-hashmap.adoc similarity index 95% rename from book/content/part03/hashmap.asc rename to book/chapters/map-hashmap.adoc index 2a00067f..9f4ceabb 100644 --- a/book/content/part03/hashmap.asc +++ b/book/chapters/map-hashmap.adoc @@ -1,10 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -[[hashmap]] -==== HashMap += HashMap (((HashMap))) (((HashTable))) (((Data Structures, Non-Linear, HashMap))) @@ -24,7 +23,7 @@ How are the keys mapped to their values? Using a hash function. Here’s an illustration: .Internal HashMap representation -image::image41.png[image,width=528,height=299] +image:image41.png[image,width=528,height=299] .This is the main idea: @@ -37,7 +36,7 @@ In a HashMap, a *collision* is when different keys lead to the same index. They Having a big bucket size can avoid a collision but also can waste too much memory. We are going to build an _optimized_ HashMap that re-sizes itself when it is getting full. This auto-resizing avoids collisions and don't need to allocate too much memory upfront. Let’s start with the *hash function*. -===== Designing an optimized hash function +== Designing an optimized hash function To minimize collisions, we need to create an excellent hash function. @@ -53,7 +52,7 @@ It’s no practical and memory-wise wasteful to have a perfect hash function, so Before doing a great hash function, let's see what a lousy hash function looks like. 😉 -====== Analysing collisions on bad hash code functions +=== Analysing collisions on bad hash code functions The goal of a hash code function is to convert any value given into a positive integer — a common way to accomplish with summing each string’s Unicode value. @@ -153,7 +152,7 @@ Now it’s more evenly distributed!! [big]#😎👍# Let’s design a better HashMap with what we learned. -====== Implementing an optimized hash function +=== Implementing an optimized hash function We are going to use a battle-tested non-cryptographic hash function called FNV Hash. @@ -190,7 +189,7 @@ FVN-1a hash function is a good trade-off between speed and collision prevention. Now that we have a proper hash function. Let’s move on with the rest of the HashMap implementation. -===== Implementing a HashMap in JavaScript +== Implementing a HashMap in JavaScript Let’s start by creating a class and its constructor to initialize the hash map. We are going to have an array called `buckets` to hold all the data. @@ -210,7 +209,7 @@ include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=getLoadFactor, Notice that we are also keeping track of collisions (for benchmarking purposes) and a load factor. *The load factor* measures how full the hash map is. We don’t want to be fuller than 75%. If the HashMap is getting too full, then we are going to fix it doing a *rehash* (more on that later). -====== Inserting elements in a HashMap +=== Inserting elements in a HashMap To insert values into a HashMap, we first convert the *key* into an *array index* using the hash and compression function. Each bucket of the array will have an object with the shape of `{key, value}`. @@ -229,7 +228,7 @@ include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=set, indent=0] Notice, that we are using a function called `getEntry` to check if the key already exists. It gets the index of the bucket corresponding to the key and then checks if the entry with the given key exists. We are going to implement this function in a bit. -====== Getting values out of a HashMap +=== Getting values out of a HashMap For getting values out of the Map, we do something similar to inserting. We convert the key into an `index` using the hash function, then we that `index` we look for the value in the bucket. @@ -240,7 +239,7 @@ include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=getEntry, inde ---- <1> Convert key to an array index. <2> If the bucket is empty create a new linked list -<3> Use Linked list's <> method to find value on the bucket. +<3> Use Linked list's <> method to find value on the bucket. <4> Return `bucket` and `entry` if found. With the help of the `getEntry` method, we can do the `HashMap.get` and `HashMap.has` methods: @@ -261,7 +260,7 @@ include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=has, indent=0] For `HashMap.has` we only care if the value exists or not, while that for `HashMap.get` we want to return the value or `undefined` if it doesn’t exist. -====== Deleting from a HashMap +=== Deleting from a HashMap Removing items from a HashMap is not too different from what we did before. @@ -275,7 +274,7 @@ If the bucket doesn’t exist or is empty, we don't have to do anything else. If https://github.com/amejiarosario/dsa.js/blob/7694c20d13f6c53457ee24fbdfd3c0ac57139ff4/src/data-structures/linked-lists/linked-list.js#L218[`LinkedList.remove` ] method. -===== Rehashing a HashMap +== Rehashing a HashMap Rehashing is a technique to minimize collisions when a hash map is getting full. It doubles the size of the map and recomputes all the hash codes and insert data in the new buckets. @@ -290,7 +289,7 @@ include::{codedir}/data-structures/maps/hash-maps/hash-map.js[tag=rehash, indent In the https://github.com/amejiarosario/dsa.js/blob/7694c20d13f6c53457ee24fbdfd3c0ac57139ff4/src/data-structures/maps/hash-maps/primes.js#L33[prime.js] file you can find the implementation for finding the next prime. Also, you can see the full HashMap implementation on this file: https://github.com/amejiarosario/dsa.js/blob/f69b744a1bddd3d99243ca64b3ad46f3f2dd7342/src/data-structures/maps/hash-maps/hash-map.js#L1[hashmap.js] -===== HashMap time complexity +== HashMap time complexity Hash Map it’s very optimal for searching values by key in constant time *O(1)*. However, searching by value is not any better than an array since we have to visit every value *O(n)*. (((Tables, Non-Linear DS, HashMap complexities))) diff --git a/book/chapters/map-intro.adoc b/book/chapters/map-intro.adoc new file mode 100644 index 00000000..9bbd3849 --- /dev/null +++ b/book/chapters/map-intro.adoc @@ -0,0 +1,26 @@ +ifndef::imagesdir[] +:imagesdir: ../images +:codedir: ../../src +endif::[] + += Map +(((Map))) +(((Data Structures, Non-Linear, Map))) +A map is a data structure to store pairs of data: *key* and *value*. In an array, you can only store values. The array’s key is always the position index. However, in a *Map* the key can be whatever you want. + +IMPORTANT: Map is a data structure that _maps_ *keys* to *values*. + +Many languages have maps already built-in. JavaScript/Node has `Map`: + +.JavaScript Built-in Map Usage +[source, javascript] +---- +include::{codedir}/data-structures/maps/map.js[tag=snippet, indent=0] +---- + +In short, you set `key`/`value` pair and then you can get the `value` using the `key`. + +The attractive part of Maps is that they are very performant usually *O(1)* or *O(log n)* depending on the implementation. We can implement the maps using two different underlying data structures: + +* *HashMap*: it’s a map implementation using an *array* and a *hash function*. The job of the hash function is to convert the `key` into an index that maps to the `value`. Optimized HashMap can have an average runtime of *O(1)*. +* *TreeMap*: it’s a map implementation that uses a self-balanced Binary Search Tree (like <>). The BST nodes store the key, and the value and nodes are sorted by key guaranteeing an *O(log n)* look up. diff --git a/book/content/part03/treemap.asc b/book/chapters/map-treemap.adoc similarity index 94% rename from book/content/part03/treemap.asc rename to book/chapters/map-treemap.adoc index 61e91436..d862689f 100644 --- a/book/content/part03/treemap.asc +++ b/book/chapters/map-treemap.adoc @@ -1,10 +1,9 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -[[treemap]] -==== TreeMap += TreeMap (((TreeMap))) (((Data Structures, Non-Linear, TreeMap))) A TreeMap is a Map implementation using Binary Search Trees. @@ -36,7 +35,7 @@ class TreeMap { } ---- -===== Inserting values into a TreeMap +== Inserting values into a TreeMap For inserting a value on a TreeMap, we first need to inialize the tree: @@ -58,7 +57,7 @@ include::{codedir}/data-structures/maps/tree-maps/tree-map.js[tag=set, indent=0] Adding values is very easy (once we have the underlying tree implementation). -===== Getting values out of a TreeMap +== Getting values out of a TreeMap When We search by key in a tree map, it takes *O(log n)*. This is the implementation: @@ -82,7 +81,7 @@ include::{codedir}/data-structures/maps/tree-maps/tree-map.js[tag=iterators, ind Generators are useful for producing values that can you can iterate in a `for...of` loop. Generators use the `function*` syntax which expects to have a `yield` with a value. **** -===== Deleting values from a TreeMap +== Deleting values from a TreeMap Removing elements from TreeMap is simple. diff --git a/book/chapters/map.adoc b/book/chapters/map.adoc new file mode 100644 index 00000000..63307e68 --- /dev/null +++ b/book/chapters/map.adoc @@ -0,0 +1,18 @@ +<<< +include::map-intro.adoc[] + +:leveloffset: +1 + +// (g) +<<< +include::map-hashmap.adoc[] + +// (g) +<<< +include::map-treemap.adoc[] + +// (g) +<<< +include::map-hashmap-vs-treemap.adoc[] + +:leveloffset: -1 diff --git a/book/content/part04/merge-sort.asc b/book/chapters/merge-sort.adoc similarity index 82% rename from book/content/part04/merge-sort.asc rename to book/chapters/merge-sort.adoc index 65e90f0d..91cb45b0 100644 --- a/book/content/part04/merge-sort.asc +++ b/book/chapters/merge-sort.adoc @@ -1,22 +1,19 @@ ifndef::imagesdir[] -:imagesdir: ../../ -:codedir: ../../../src +:imagesdir: ../images +:codedir: ../../src endif::[] -[[merge-sort]] -==== Merge Sort - += Merge Sort (((Sorting, Merge Sort))) (((Merge Sort))) - Merge Sort is an efficient sorting algorithm that uses <> paradigm to accomplish its task faster. However, It uses auxiliary memory in the process of sorting. indexterm:[Divide and Conquer] Merge sort algorithm splits the array into halves until 2 or fewer elements are left. It sorts these two elements and then merges back all halves until the whole collection is sorted. -image::image11.png[Mergesort visualization,width=500,height=600] +image:image11.png[Mergesort visualization,width=500,height=600] -===== Merge Sort Implementation +== Merge Sort Implementation .Merge Sort implementation in JavaScript (mergeSort) [source, javascript] @@ -47,17 +44,17 @@ include::{codedir}/algorithms/sorting/merge-sort.js[tag=merge, indent=0] <2> If `array1` current element (`i1`) has the lowest value, we insert it into the `mergedArray` if not we then insert `array2` element. <3> `mergedArray` is `array1` and `array2` combined in ascending order (sorted). -Merge sort has an _O(n log n)_ running time. For more details about how to extract the runtime go to <> section. +Merge sort has an _O(n log n)_ running time. For more details about how to extract the runtime go to <> section. -===== Merge Sort Properties +== Merge Sort Properties - <>: [big]#✅# Yes - <>: [big]#️❌# No, it requires auxiliary memory O(n). - <>: [big]#️❌# No, new elements will require to sort the whole array. - <>: [big]#️❌# No, mostly sorted array takes the same time O(n log n). - Recursive: Yes -- Time Complexity: [big]#✅# <> _O(n log n)_ -- Space Complexity: [big]#⚠️# <> _O(n)_, use auxiliary memory +- Time Complexity: [big]#✅# <> _O(n log n)_ +- Space Complexity: [big]#⚠️# <> _O(n)_, use auxiliary memory (((Linearithmic))) (((Runtime, Linearithmic))) diff --git a/book/chapters/non-linear-data-structures-intro.adoc b/book/chapters/non-linear-data-structures-intro.adoc new file mode 100644 index 00000000..0a1455e1 --- /dev/null +++ b/book/chapters/non-linear-data-structures-intro.adoc @@ -0,0 +1,12 @@ +[partintro] +-- +Non-Linear data structures are everywhere whether you realize it or not. You can find them in databases, Web (HTML DOM tree), search algorithms, finding the best route to get home and many more uses. We are going to learn the basic concepts and when to choose one over the other. + +.In this chapter we are going to learn: +- Exciting <> data structure applications +- Searching efficiently with a <> data structures. +- One of the most versatile data structure of all <>. +- Keeping dups out with a <>. + +By the end of this section, you will know the data structures trade-offs and when to use one over the other. +-- diff --git a/book/chapters/part1.adoc b/book/chapters/part1.adoc new file mode 100644 index 00000000..1b262f58 --- /dev/null +++ b/book/chapters/part1.adoc @@ -0,0 +1,20 @@ +[[chapter-1]] += Algorithms Analysis + +[partintro] +-- +In this part, we are going to cover the basics of algorithms analysis. Also, we will discuss the most common runtimes of algorithms and provide a code example for each one. +-- + + +:leveloffset: +1 + +// (g) +include::algorithms-analysis.adoc[] + +// (g) +include::big-o-examples.adoc[] + +:leveloffset: -1 + + diff --git a/book/chapters/part2.adoc b/book/chapters/part2.adoc new file mode 100644 index 00000000..7bc821d0 --- /dev/null +++ b/book/chapters/part2.adoc @@ -0,0 +1,29 @@ +[[chapter-2]] += Linear Data Structures + +// (g) +include::linear-data-structures-intro.adoc[] + +:leveloffset: +1 + +// (g) +include::array.adoc[] + +// (g) +<<< +include::linked-list.adoc[] + +// (g) +<<< +include::stack.adoc[] + +// (g) +<<< +include::queue.adoc[] + +// (g) +<<< +include::linear-data-structures-outro.adoc[] + +:leveloffset: -1 + diff --git a/book/chapters/part3.adoc b/book/chapters/part3.adoc new file mode 100644 index 00000000..f64d9d2f --- /dev/null +++ b/book/chapters/part3.adoc @@ -0,0 +1,63 @@ +[[chapter-3]] += Non-Linear Data Structures + +// (g) +include::non-linear-data-structures-intro.adoc[] + +:leveloffset: +1 + +// (g) +include::tree.adoc[] + + +// (g) +<<< +include::tree-binary-search-tree.adoc[] + +<<< +include::tree-search.adoc[] + +<<< +include::tree-self-balancing-rotations.adoc[] + +:leveloffset: +1 + +<<< +include::tree-avl.adoc[] + +:leveloffset: -1 + +// (g) +include::map.adoc[] + +// (g) +include::set.adoc[] + +// (g) +include::graph.adoc[] + +include::graph-search.adoc[] + +// Graph summary += Summary + +In this section, we learned about Graphs applications, properties and how we can create them. We mention that you can represent a graph as a matrix or as a list of adjacencies. We went for implementing the later since it's more space efficient. We cover the basic graph operations like adding and removing nodes and edges. In the algorithms section, we are going to cover searching values in the graph. +(((Tables, Non-Linear DS, BST/Maps/Sets Complexities))) + +// tag::table[] +.Time and Space Complexity for Non-Linear Data Structures +|=== +.2+.^s| Data Structure 2+^s| Searching By .2+^.^s| Insert .2+^.^s| Delete .2+^.^s| Space Complexity +^|_Index/Key_ ^|_Value_ +| <> ^|- ^|O(n) ^|O(n) ^|O(n) ^|O(n) +| <> ^|- ^|O(log n) ^|O(log n) ^|O(log n) ^|O(n) +| Hash Map (naïve) ^|O(n) ^|O(n) ^|O(n) ^|O(n) ^|O(n) +| <> (optimized) ^|O(1)* ^|O(n) ^|O(1)* ^|O(1)* ^|O(1)* +| <> (Red-Black Tree) ^|O(log n) ^|O(n) ^|O(log n) ^|O(log n) ^|O(log n) +| <> ^|- ^|O(n) ^|O(1)* ^|O(1)* ^|O(1)* +| <> ^|- ^|O(n) ^|O(log n) ^|O(log n) ^|O(log n) +|=== +{empty}* = Amortized run time. E.g. rehashing might affect run time to *O(n)*. +// end::table[] + +:leveloffset: -1 diff --git a/book/chapters/part4.adoc b/book/chapters/part4.adoc new file mode 100644 index 00000000..2494091a --- /dev/null +++ b/book/chapters/part4.adoc @@ -0,0 +1,62 @@ +[[chapter-4]] += Algorithmic Techniques + +// TODO: pending +include::algorithms-intro.adoc[] + +:leveloffset: +1 + +include::sorting-intro.adoc[] + +:leveloffset: +1 + +include::sorting-properties.adoc[] + + +// Slow Sorting +<<< +include::bubble-sort.adoc[] + +<<< +include::insertion-sort.adoc[] + +<<< +include::selection-sort.adoc[] + + +// include::insertion-selection-bubble-sort.adoc[] + +// Fast Sorting +<<< +include::merge-sort.adoc[] + +<<< +include::quick-sort.adoc[] + +<<< +include::sorting-summary.adoc[] + +:leveloffset: -1 + + +// +// Algorithms Techniques +// +<<< +include::divide-and-conquer.adoc[] + +<<< +include::dynamic-programming.adoc[] + +<<< +include::greedy-algorithms.adoc[] + +<<< +include::backtracking.adoc[] + +<<< +include::algorithmic-toolbox.adoc[] + +// --- end algorithms --- + +:leveloffset: -1 diff --git a/book/content/preface.asc b/book/chapters/preface.adoc similarity index 85% rename from book/content/preface.asc rename to book/chapters/preface.adoc index 7f983404..86222879 100644 --- a/book/content/preface.asc +++ b/book/chapters/preface.adoc @@ -1,26 +1,26 @@ [preface] -== Preface += Preface -=== What is in this book? +== What is in this book? -_{doctitle}_ is a book that can be read from cover to cover, where each section builds on top of the previous one. Also, it can be used as a reference manual where developers can refresh specific topics before an interview or looking for ideas to solve a problem optimally. (Check out the <> and <>) +_{book-title}_ is a book that can be read from cover to cover, where each section builds on top of the previous one. Also, it can be used as a reference manual where developers can refresh specific topics before an interview or looking for ideas to solve a problem optimally. (Check out the <