Типы данных
(Wiki) набор инструкций, описывающих порядок действий исполнителя для достижения некоторого результата. В старой трактовке вместо слова «порядок» использовалось слово «последовательность», но по мере развития параллельности в работе компьютеров слово «последовательность» стали заменять более общим словом «порядок». Независимые инструкции могут выполняться в произвольном порядке, параллельно, если это позволяют используемые исполнители.
пошаговый набор инструкций для выполнения задачи
Алгоритм должен представлять процесс решения задачи как последовательное выполнение простых (или ранее определенных) шагов. Каждое действие, предусмотренное алгоритмом, исполняется только после выполнения предыдущего шага.
class Person {
getUp() {...}
takeShower() {...}
haveBreakfast() {...}
getDressed() {...}
goToWork() {
getUp();
takeShower();
haveBreakfast();
getDressed();
}
}
Каждый шаг алгоритма должен быть четко и недвусмысленно определен и не должен допускать произвольной трактовки исполнителем.
let numbers = [1, 2, 3, 4];
let doubles = getDoubles(numbers);
function getDoubles(array) {
return numbers.map(function(x) {
return x * 2;
});
}
// doubles - [2, 4, 6, 8]
// numbers - [1, 2, 3, 4]
Алгоритм должен приводить к решению поставленной задачи за конечное число шагов.
while(true) {
doSmth();
}
let counter = 10;
while(counter > 0) {
console.log(counter--);
}
/*************************************/
let isModalActive = true;
while(isModalActive) {
showSmth();
//do some staff
}
/*if user press Close button*/
closeHandler() {
isModalActive = false;
}
Алгоритм решения задачи разрабатывается в общем виде, т.е. он должен быть применим для некоторого класса задач, различающихся только исходными данными.
let smallArray = [1,2,3,4];
let bigArray = [1,2,3,4,6,.....,43,34];
let namesArray = ['Emma', 'Olivia',..., 'Meilani'];
getLastItem(array) {
return array[array.length - 1];
}
... – это вызов функции самой себя, как правило, с другими аргументами.
Math.pow(2,3); //8
// pow(x, n) = x * pow(x, n - 1);
// pow(2, 3) = 2 * pow(2, 2)
function pow(x, n) {
return n !== 1 ? x * pow(x, n - 1) : x;
}
Любая рекурсия может быть переделана в цикл. Как правило, вариант с циклом будет эффективнее.
function pow(x, n) {
let result = x;
for (let i = 1; i < n; i++) {
result *= x;
}
return result;
}
Любой рекурсивный алгоритм может быть реализован с помощью итераций
При любом вложенном вызове JavaScript запоминает текущий контекст выполнения в специальной внутренней структуре данных – «стеке контекстов».
У каждого вызова функции есть свой «контекст выполнения» (execution context).
Контекст включает в себя не только переменные, но и место в коде, так что когда вложенный вызов завершится - можно будет легко вернуться назад.
К примеру: { x: 2, n: 3, строка 3 }
А зачем эта замена рекурсии?!
Размер стека вызовов в различных браузерах
Теперь очевидным решением будет:
function recursive (n) {
if(n <= 2) {
return 1;
} else {
return recursive(n - 1) + recursive(n - 2);
}
};
Решение без рекурсии...
let loopingFib = function(n) {
let a = 0, b = 1, f = 1;
for(let i = 2; i <= n; i++) {
f = a + b;
a = b;
b = f;
}
return f;
};
формула...
где:
где:
Решение c мемоизаций(?!)
let memo = [];
let fib = function(n) {
if (memo[n]) return memo[n];
let current = 0;
let next = 1;
for (let i = 0; i < n; i++) {
memo[i] = current;
[current, next] = [next, current + next]; //swap
}
return current;
};
Мемоизация — сохранение результатов выполнения функций для предотвращения повторных вычислений. Это один из способов оптимизации
Сравнение скорости работы алгоритма с мемоизацией и без
Для больших чисел n количество вызовов функции растёт очень быстро. Уже для n=50 это порядка 40 миллиардов вызовов.
Используя идею мемоизации, то есть кеширования промежуточного результата, можно добиться уменьшения количества вызовов для n=50 до всего лишь 99.
Мемоизация полезна, когда вы передаёте в функцию заранее известный набор аргументов и когда результат функции будет всегда одинаковым при одинаковых аргументах. Если же функция не даёт одинакового результата при тех же аргументах, то мемоизация будет бесполезна.
result = ++variable
result = --variable
result = variable++
result = variable--
/*************************/
variable = 1;
result1 = variable++;
result2 = ++variable;
variable //3
result1 //1
result2 //3
result1 = expression1 && expression2
result2 = expression1 || expression2
let a = {b: 3};
a.b > 2 && doSmth();
function foo(a) {
let variable = a || [1,2,3];
//do something
}
function foo(a = [1,2,3]) {
console.log(a);
//do something
}
typeof (2 === 11 % 3) === 'boolean' // 'string'...
/** Convert a decimal number to binary **/
var toBinary = function(decNum){
return parseInt(decNum, 10).toString(2);
}
/** Convert a binary number to decimal **/
var toDecimal = function(binary) {
return parseInt(binary, 2).toString(10);
}
function binToDec(number){
return number.split('').reverse().reduce(function(x, y, i){
return (y === '1') ? x + Math.pow(2, i) : x;
}, 0);
}
А если без parseInt...
Системы счисления
Попробуем сделать перевод из десятичной в двоичную
function decToBinary(decNumber) {
}
let stack = [];
let binString = "";
while(decNumber > 0) {
let rem = decNumber % 2;
stack.push(rem);
decNumber = Math.floor(decNumber / 2);
}
while(stack.length) {
binString = binString + stack.pop(); // binString.concat(stack.pop());
}
return binString;
Для оценки производительности алгоритмов можно использовать разные подходы. Самый бесхитростный - просто запустить каждый алгоритм на нескольких задачах и сравнить время исполнения.
Другой способ - математически оценить время исполнения подсчетом операций.
Если считать, что числа в таблице соответствуют микросекундам, то для задачи с n=1048576 элементами алгоритму с временем работы O(log n) потребуется 20 микросекунд, алгоритму со временем O(n) - 17 минут, а алгоритму с временем работы O( n*n ) - более 12 дней...
Наилучшей является оценка O(1)... В этом случае время вообще не зависит от n, т.е постоянно при любом количестве элементов.
let array = [1,2,3,4];
array[0]; //1
array.push(5);
array[4]; //5
Поиск максимального элемента в массиве.
let array = [1,2,3,4,5,6,7,8,9,10,12,0,100];
let max = array[0];
for (let i = 0; i < array.length; i++) {
if ( array[i] >= max ) {
max = array[i];
}
}
Предположим, что наш процессор способен выполнять как единые инструкции следующие операции:
2 операции
2 операции на инициализацию for
+ по 2 на итерацию цикла
Поиск максимального элемента в массиве.
if ( array[i] >= max ) { ...
Но тело if может запускаться, а может и нет, в зависимости от актуального значения из массива. Если произойдёт так, что array[ i ] > max, то у нас запустятся две дополнительные команды: поиск в массиве и присваивание
array2 = [ 4, 3, 2, 1 ];
array1 = [ 1, 2, 3, 4 ];
Когда мы анализируем алгоритмы, мы чаще всего рассматриваем наихудший сценарий.
Таким образом, в наихудшем случае в теле цикла из нашего кода запускается четыре инструкции, и мы имеем f( n ) = 4 + 2n + 4n = 6n + 4.
Поэтому первое, что мы сделаем,
это отбросим 4 и оставим только
f( n ) = 6n.
Мы отбрасываем те элементы функции, которые при росте n возрастают медленно, и оставляем только те, что растут сильно.
Второй вещью, на которую можно не обращать внимания, является множитель перед n. Так что наша функция превращается в f( n ) = n.
Что дальше
При оценке O() константы не учитываются.
При оценке за функцию берется количество операций, возрастающее быстрее всего.
Пример с улучшением кода
let array = [1,2,3,4,5,6,7,8,9,10,12,0,100];
let max = array[0];
for (let i = 0; i < array.length; i++) {
if ( array[i] >= max ) {
max = array[i];
}
}
let array = [1,2,3,4,5,6,7,8,9,10,12,0,100];
let max = array[0];
let length = array.length;
for (let i = 1; i < length; i++) {
if ( array[i] > max ) {
max = array[i];
}
}
Шпаргалка со сложностью алгоритмов
http://bigocheatsheet.com/
Цикл статей с подробным разбором способов оценки сложности алгоритмов
https://habrahabr.ru/post/196560/
ПОЛЕЗНЫЕ ССЫЛКИ
Доступ
O(1)
Удаление
O(1)
Вставка
O(1)
Поиск
O(n)
slice splice push shift unshift join reverse.....
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array
Глупая
Пузырьковая
Шейкарная
Слиянием
Быстрая
Пирамидальная сортировка
Выбором
Шелла
Сортировка прямыми включениями
Поразрядная
Блочная
.....
Виды сортировок:
«Так любой дурак сортировать умеет» — скажете Вы и будете абсолютно правы. Именно поэтому сортировку и прозвали «глупой».
Просматриваем массив слева-направо и по пути сравниваем соседей. Если мы встретим пару взаимно неотсортированных элементов, то меняем их местами и возвращаемся в самое начало.
O(n^3)
function stupidSort(arr) {
let i = 0;
let tmp;
while (i < arr.Length - 1) {
if (arr[i + 1] < arr[i]) {
tmp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = tmp;
i = 0;
} else {
i++;
}
}
return arr;
}
Принцип действий прост: обходим массив от начала до конца, попутно меняя местами неотсортированные соседние элементы. В результате первого прохода на последнее место «всплывёт» максимальный элемент. Теперь снова обходим неотсортированную часть массива (от первого элемента до предпоследнего) и меняем по пути неотсортированных соседей.
Продолжая в том же духе
(сортировка простыми обменами)
O(n^2)
function bubbleSort(array) {
return array;
}
for (let i = 0; i < length - 1; i++) {
for(let j = 1; j < length - i; j++) {
if(array[j-1] > array[j]) {
var temp = array[j-1];
array[j-1] = array[j];
array[j] = temp;
}
}
}
bubbleSort([5,90,2,3,5,7,1,4]); //[1, 2, 3, 4, 5, 5, 7, 90]
const length = array.length;
Пузырьковая сортировка + задвигать максимумы не только в конец, а ещё и в начало перебрасывать минимумы то у нас получается…
Начинается процесс как в «пузырьке»: "перекатываем" максимум в конец. После этого разворачиваемся на 180 градусов и идём в обратную сторону, при этом уже "перекатывая" в начало не максимум, а минимум. Отсортировав в массиве первый и последний элементы, снова повторяем...
Имеем:
Когда же конец:
заканчиваем процесс, оказавшись в середине списка.
O(n^2)
function shakerSort(arr) {
let swapped;
do {
for(let i = 0; i < arr.length - 2; i++) {
if(arr[i] > arr[i+1]) {
let tmp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = tmp;
swapped = true;
}
}
if(!swapped) {
break;
}
swapped = false;
for(let i = arr.length - 2; i > 0; i--) {
if(arr[i] > arr[i+1]) {
let tmp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = tmp;
swapped = true;
}
}
} while(swapped);
return arr;
}
O(N*LOG N)
https://www.youtube.com/watch?time_continue=229&v=XaqR3G_NVoo
function merge(left, right) { //Вспомогательная функция.
let result = [];
while (left.length > 0 && right.length > 0) {
if (left[0] < right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
return result.concat(left).concat(right);
}
function mergeSort(array) { //Функция сортировки слиянияем.
if (array.length < 2) {
return array;
}
let middle = Math.floor(array.length / 2);
let left = array.slice(0, middle);
let right = array.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function mergeSort(array){ // вспомогательную функцию нужно взять из прошлого примера
if (array.length < 2) {
return items;
}
let work = [], i, len;
for (i = 0, length = array.length; i < length; i++){
work.push([array[i]]);
}
work.push([]);
for (let lim = length; lim > 1; lim = Math.floor((lim + 1) / 2)){
for (var j = 0, k = 0; k < lim; j++, k+=2){
work[j] = merge(work[k], work[k+1]);
}
work[j] = [];
}
return work[0];
}
Без рекурсии, без перформанса.. :)
В целом, пример плохого mergeSort (опасно для здоровья)
http://me.dt.in.th/page/Quicksort/
1. Выбираем опорный элемент
2. Добиваемся того, чтобы элементы меньше опорного оказываются слева от опорного, а элементы больше опорного — справа.
3. К подмассивам слева и справа от опорного применяются первые два шага, если в этих подмассивах больше одного элемента.
function quickSort(array) {
if (array.length < 2) {
return array;
}
let a = [], b = [], p = array[0];
for (let i = 1; i < array.length; i++) {
if (array[i] < p) {
a[a.length] = array[i];
} else {
b[b.length] = array[i];
}
}
return quickSort(a).concat(p, quickSort(b));
}
O(N^2) в худшем случае
var fruit = ['cherries', 'apples', 'bananas'];
fruit.sort(); // ['apples', 'bananas', 'cherries'];
var scores = [1, 10, 21, 2];
scores.sort(); // [1, 10, 2, 21];
var things = ['word', 'Word', '1 Word', '2 Words'];
things.sort(); // ['1 Word', '2 Words', 'Word', 'word'];
function compareNumbers(a, b) {
return a - b;
}
var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
return a - b;
});
// [1, 2, 3, 4, 5]
var items = [
{ name: 'Edward', value: 21 },
{ name: 'Sharpe', value: 37 },
{ name: 'And', value: 45 },
{ name: 'The', value: -12 },
{ name: 'Magnetic', value: 13 },
{ name: 'Zeros', value: 37 }
];
function compare(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
// a === b
return 0;
}
// sort by value
items.sort(function (a, b) {
return a.value - b.value;
});
// sort by name
items.sort(function(a, b) {
var nameA = a.name.toUpperCase(); // ignore upper and lowercase
var nameB = b.name.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
План на сегодня
Доступ
O(n)
Удаление
O(1)
Вставка
O(1)
Поиск
O(n)
length add remove insertAfter traverse head tail numberOfValues
function Node(data) {
this.data = data;
this.next = null;
}
function SinglyLinkedList() {
this.head = null;
this.tail = null;
this.numberOfValues = 0;
}
SinglyLinkedList.prototype.length = function() {
return this.numberOfValues;
};
SinglyLinkedList.prototype.add = function(data) {
var node = new Node(data);
if(!this.head) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
this.numberOfValues++;
};
SinglyLinkedList.prototype.remove = function(data){
var previous = this.head;
var current = this.head;
while(current) {
if(current.data === data) {
if(current === this.head) {
this.head = this.head.next;
}
if(current === this.tail) {
this.tail = previous;
}
previous.next = current.next;
this.numberOfValues--;
} else {
previous = current;
}
current = current.next;
}
};
....insertAfter = function(data, afterElement) {
}
var list = new SinglyLinkedList();
list.add(1);
list.add(2);
list.add(3);
list.length(); //3
list.remove(2);
list.insertAfter(2, 1); //1,2,3
var node = new Node(data);
Доступ
O(n)
Удаление
O(1)
Вставка
O(1)
Поиск
O(n)
push(value) pop() peek()
function Stack() {
this.stack = [];
}
Stack.prototype.push = function(value) {
this.stack.push(value);
};
Stack.prototype.pop = function() {
return this.stack.pop();
};
Stack.prototype.peek = function() {
return this.stack[this.stack.length - 1];
};
var stack = new Stack();
stack.push(1);
stack.push(2); //1,2
stack.peek(); //2
stack.pop(); //1
Доступ
O(n)
Удаление
O(n)
Вставка
O(1)
Поиск
O(n)
enqueue, dequeue, peek..
function Queue() {
this.queue = [];
}
Queue.prototype.enqueue = function(value) {
this.queue.push(value);
};
Queue.prototype.dequeue = function() {
return this.queue.shift();
};
Queue.prototype.peek = function() {
return this.queue[0];
};
var queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.peek();
queue.dequeue();
Удаление
O(1)
Вставка
O(1)
Поиск
O(1)
calculateHash, add, search, remove..
В среднем
В худшем
O(n)
O(n)
O(n)
Коллизии
Парадокс дней рождения
Удаление
O(1)
Вставка
O(1)
Поиск
O(1)
http://www.ecma-international.org/ecma-262/6.0/index.html#sec-set-objects
В среднем
В худшем
O(n)
O(n)
O(n)
let map = new Map();
let obj = { key: 2 };
let arr = ['1', '2'];
map.set(0, 'val1');
map.set('1', 'val2');
map.set(obj, 'val3');
map.set(arr, 'val4');
map.get('1'); // 'val2'
map.get(obj); // 'val3'
map.size; // 4
map.delete(obj);
// необходимо передать итерируемый объект
let map = new Map([
['fruit1', 'apple'],
['fruit2', 'cherry'],
['fruit3', 'banana']
]);
for(let key of map.keys()) { // итерация по ключам
console.log(key);
}
for(let value of map.values()) { // итерация по значениям
console.log(value);
}
map.forEach( (value, key, map) => { // также есть метод forEach
console.log(`${key}: ${value}`);
});
let set = new Set();
let apple = { fruit: 'Яблоко' };
let cherry = { fruit: 'Вишня' };
let banana = { fruit: 'Банан' };
set.add(apple);
set.add(cherry);
set.add(banana);
// Добавляет только уникальные значения
set.add(banana);
set.size // 3
set.has(apple) // true
set.has('1') // false
set.delete(apple);
set.clear()
// Для инициализации необходимо в конструктор передать
// итерируемый объект
let set = new Set(['Яблоко', 'Вишня', 'Банан']);
// итерация по значениям (такой же эффект с set.keys()
for(let value of set.values()) {
console.log(value);
}
// также есть метод forEach
set.forEach( (value, valueAgain, set) => {
console.log(value);
});
let fruits = [
{fruit: 'apple'},
{fruit: 'cherry'},
{fruit: 'banana'}
];
let weakMap = new WeakMap();
weakMap.set(fruits[0], 'one');
weakMap.set(fruits[1], 'two');
weakMap.set(fruits[2], 'three');
// Нельзя в качестве ключей хранить примитивы
weakMap.set('orange', 'four'); // Error
weakMap.get(fruits[0]) // 'one'
fruits.shift();
weakMap.get(fruits[0]) // 'two'
ES6 структуры данных, которые не препятствуют удалению хранимых объектов сборщиком мусора, если данные объекты были удалены из другого места в коде и более не используются.
Имеют урезанный функционал: в качестве ключей могут быть только объекты, отсутствует свойство size, отсутствуют методы clear, forEach, keys, values и нельзя перебрать при помощи итератора, .
https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables
Виды деревьев
Доступ
O(n)
Удаление
Вставка
Поиск
O(n)
O(n)
O(n)
Доступ
O(LOGN)
Удаление
O(LOGN)
Вставка
O(LOGN)
Поиск
O(LOGN)
lodash
https://lodash.com/docs
Зачем очередной раз изобретать велосипед, когда до нас уже столько сделано....
Интересные задачи
https://www.interviewcake.com/all-questions/javascript
Для каждой из задач можно увидеть оптимальный путь решения (алгоритм)
Полезные ссылки
http://www.thatjsdude.com/interview/js1.html
https://khan4019.github.io/front-end-Interview-Questions/sort.html
CODEWARS ЗАДАЧИ: