1. Introduction to JavaScript
2. ECMAScript and TypeScript overview
2.2.7 object oriented programming using classes
class Book { constructor(title, pages, isbn) { this.title = title this.pages = pages this.isbn = isbn } printTitle() { console.log(this.title) } } let book = new Book('title', 'pag') book.title // title book.pages // pag
1. Succession
About super keyword:
ES6 requires that the constructor of a subclass must execute the super function once, otherwise an error will be reported.
The keyword super can be used as either a function or an object. In these two cases, its usage is completely different;
-
Use as a function
The super method must be called in the constructor, because the subclass does not have its own this object, but inherits and processes the this object of the parent class, and super represents the constructor of the parent class. Although super represents the constructor of parent class A, it returns an instance of subclass B, that is, this inside super refers to B. therefore, super() is equivalent to a.prototype constructor. call(this, props)
-
Use as object
In normal methods, point to the prototype object of the parent class; In a static method, point to the parent class;
class ITBook extends Book { constructor(title, pages, isbn, technology) { super(title, pages, isbn) this.technology = technology } printTechnology () { console.log(this.technology) } } let jsBook = new ITBook('study js algorithm', '210', '123456', 'JavaScript') jsBook.title // 'learning js algorithm' jsBook.printTechnology // 'JavaScript'
2. Use attribute accessor
class Person { constructor(name) { this._name = name } get name () { return this._name } set name(value) { this._name = value } }
2.2.8 power operator
const area = 3.14 * r * r const area = 3.14 * Math.pow(r, 2) const area = 3.14 * (r ** 2) // The above three methods are the same
3. Array
shift pop
unshift push
3.5 add or delete elements anywhere
number.splice(5, 3) // Starting with index 5, three elements of length 3 are deleted number.splice(5, 0, 2, 3, 4) // Starting with index 5, insert 2,3,4 number.splice(5, 3, 2, 3, 4) // Starting from index 5, three elements of length 3 are deleted and three elements of 2, 3 and 4 are inserted at the same time
3.6 2D and multidimensional arrays
Matrix (two-dimensional array, or array of arrays)
// To see the output of the matrix, you can create a general function function printMatrix(myMatrix) { for (let i = 0; i < myMatrix.length; i++) { for (let j = 0l j < myMatrix[i].length; j++) { console.log(myMatrix[i][j]) } } }
3.7 array method reference of JavaScript
3.7.1 array merging
const zero = 0; const positiveNumbers = [1, 2, 3]; const negativeNumbers = [-3, -2, -1]; let numbers = negativeNumbers.concat(zero, positiveNumbers) // [-3, -2, -1, 0, 1, 2, 3]
3.7.2 iterator function
4. Stack
4.2 stack data structure
An ordered set of last in, first out elements, newly added or to be deleted, are stored at the same end of the stack, called the top of the stack, and the other end is called the bottom of the stack;
4.2.1 create an array based stack
// Use array class Stack { constructor() { this.items = [] } push(item) { this.item.push(item) } pop() { return this.items.pop() } peek() { return this.items[this.item.length - 1] } isEmpty() { return this.items.length === 0 } size() { return this.items.length } clear() { this.items = [] } }
4.3 create a Stack class based on JavaScript objects
// Use object class ObjStack { constructor() { this.count = 0 this.items = {} } push(item) { this.items[this.count] = item this.count++ } pop() { if (this.isEmpty()) { return undefined } this.count-- const result = this.items[this.count] delete this.items[this.count] return result } peek() { if (this.isEmpty()) { return undefined } return this.items[this.count - 1] } clear() { this.items = {} this.count = 0 } toString() { if (this.isEmpty()) { return '' } let str = `${this.items[0]}` for (let i = 1; i < this.count; i++) { str = `${str},${this.items[i]}` } return str } }
4.4 protecting internal elements of data structure
Although prototype based classes can save memory space and are better than function based classes in expansion, they cannot declare private properties or methods, which will make the data structure very dangerous, because the data structure can be changed by modifying properties or methods;
4.4.1 underline naming convention
Some developers like to use underline naming convention in js to mark an attribute as private;
However, this is only an agreement and cannot protect data. It can only rely on the common sense of developers;
4.4.2 implementing classes with Symbol
const _items = Symbol('stackItems') class Stack { constructor() { this[_items] = [] } }
This method creates a fake private property because object Getownpropertysymbols method can obtain all Symbols properties declared in the class;
const stack = new Stack() stack.push(5) stack.push(8) let objectSymbols = Object.getOwnPropertySymbols(stack) stack[objectSymbols[0]].push(1) console.log(stack) // 5,8,1
As you can see from the above code, you can access stack[objectSymbols[0]]_ items, and_ items is an array, which can be used for any array operation;
4.4.3 implementing classes with WeakMap
const items = new WeakMap() class Stack { constructor() { items.set(this, []) } push(item) { const s = item.get(this) s.push(item) } pop() { const s = item.get(this) const r = s.pop() return r } }
items is the real private attribute in the Stack class; Using this method, the readability of the code is not strong, and the private attributes cannot be inherited when extending the class;
4.4.4 ECMAScript attribute proposal
class Stack { #count = 0 #items = 0 }
TS provides a private modifier for class attributes and methods. Then, the modifier is only useful at compile time. After the code is transferred, the attributes are also exposed;
A proposal to add private attributes to js classes; Use # as a prefix to declare private attributes;
4.5 solving problems with stack
4.5.1 from decimal to binary
function decimalToBinary(decNumber) { const remStack = new ObjStack() let number = decNumber let rem; let binaryString = '' while (number > 0) { rem = Math.floor(number % 2) remStack.push(rem) number = Math.floor(number / 2) } while (!remStack.isEmpty()) { binaryString += remStack.pop().toString } return binaryString }
4.5.2 stack containing min
class MinStack { constructor () { // stackA is used to store data this.stackA = [] this.countA = 0 // Stackbis used to store data in descending order this.stackB = [] this.countB = 0 } push (item) { this.stackA[this.countA++] = item if (this.countB === 0 || item < this.min()) { this.stackB[this.countB++] = item } } min () { return this.stackB[this.countB - 1] } top () { return this.stackA[this.countA - 1] } pop () { if (this.top() === this.min()) { delete this.stackB[--this.countB] } delete this.stackA[--this.countA] } }
4.5.3 leetCode739. Daily temperature
// Input the daily temperature and output the temperature of that day. How many days will it rise // Input [73,74,75,71,69,72,76,73] // Output [1, 1, 4, 2, 1, 1, 0, 0] // Daily temperature problem var dailyTemperatures = function(T) { const stack = new Stack() const arr = new Array(T.length).fill(0) for (let i = 0; i < T.length; i++) { let temp = T[i] while(stack.size() && temp > T[stack.peek()]) { let index = stack.pop() arr[index] = i - index } stack.push(i) } return arr } console.log(dailyTemperatures([73,74,75,71,69,72,76,73]))
5. Queue and double ended queue
A queue is an ordered set of items that follow the first in first out principle; Add a new element at the end of the queue and remove the element from the top. The newly added element must be at the end of the queue;
5.1 queue data structure
Because we want to remove the element from the head, we need a variable to track the head element
For the length of the queue, it becomes the distance from lowestCount to count;
// queue class Queue { constructor() { this.count = 0 // Total number of queues this.lowestCount = 0 // Trace the sequence of the first element this.items = {} } // Add element to queue tail enqueue(element) { this.item[this.count] = element this.count++ } // Remove header element from queue dequeue() { if (this.isEmpty()) { return undefined } const result = this.items[this.lowestCount] delete this.item[this.lowestCount] this.lowestCount++ return result } // Return header element peek() { if (this.isEmpty()) { return undefined } return this.items[this.lowestCount] } isEmpty() { return this.size() === 0 } size() { return this.count - this.lowestCount } // Empty queue clear() { this.items = {} this.count = 0 this.lowestCount = 0 } toString() { if (this.isEmpty()) { return '' } let str = this.items[this.lowestCount] for (let i = this.lowestCount + 1; i < this.count; i++) { str +=',' + this.items[i] } return str // return Object.value(this.items).join(',') } }
5.2 double ended queue data structure
A double ended queue is a special queue that allows us to add and remove elements from both the front end and the back end;
Because the double ended queue abides by the principles of first in first out and last in first out at the same time, it can be said that it is a data structure combining queue and stack;
For the length of the double ended queue, it becomes the distance from lowestCount to count;
Because both the front end and the back end will be changed, pointers are required at the head and tail of the double ended queue;
Here you just need to focus on the logic of the addFront method
There are three situations:
-
The double ended queue is empty;
Directly call the addBack method
-
Elements have been removed from the front end of the double ended queue, when lowestcount > 0;
lowestCount needs to be subtracted by 1 and a new element is added at lowestCount
-
No element has been removed from the front end, when lowestCount === 0;
All elements need to move backward one position to make room for lowestCount (0 at this time) to add new elements;
// Double ended queue class Deque { constructor() { this.count = 0 // Serial number at the end of the queue this.lowestCount = 0 // Trace the sequence of the first element this.items = {} } /* * New addFront addBack removeFront removeBack peekFront peekBack method */ // Adds an element to the front end of a two ended queue addFront(element) { if (this.isEmpty()) { this.addBack(element) // An element has been removed from the front end } else if (this.lowestCount > 0) { this.lowestCount-- this.items[this.lowestCount] = element // No elements were removed } else { // All elements are moved back one bit to make room for the first position for (let i = this.count; i > 0; i--) { this.items[i] = this.items[i - 1] } this.count++ this.lowestCount = 0 // This step can be considered not because it is 0 this.item[0] = element } } // Same as enqueue method addBack(element) { this.item[this.count] = element this.count++ } // Same as dequeue method removeFront() { if (this.isEmpty()) { return undefined } const result = this.items[this.lowestCount] delete this.item[this.lowestCount] this.lowestCount++ return result } // The same as the pop method in the Stack class removeFront() { if (this.isEmpty()) { return undefined } this.count-- const result = this.items[this.count] delete this.items[this.count] return result } // Same as peek method peekFront() { if (this.isEmpty()) { return undefined } return this.items[this.lowestCount] } // Same as peek method in Stack class peekBack() { if (this.isEmpty()) { return undefined } return this.items[this.count - 1] } }
5.2.1 leetCode59. Maximum number of queues
// Please define a queue and implement the function max_value gets the maximum value in the queue var MaxQueue = function () { // Store queue data this.queue = {} // Double ended queue maintenance maximum this.deque = {} // Prepare queue related data this.countQ = this.countD = this.headQ = this.headD = 0 } MaxQueue.prototype.push_back = function(value) { this.queue[this.countQ++] = value // Detects whether data can be added to a dual ended queue // If the added value is greater than the end of the queue value, the end of the queue will be out of the queue and the new value will be in the queue // ? What if you just leave the maximum value? // no way!! If deleted in this way, the queue will become null // At this time, it is necessary to ensure that the double ended queue is a monotonically decreasing queue, and the head of the queue is the maximum value // 10 9 11 // 10 // if (!this.isEmptyDeque()) { // this.deque[0] = value // } // if (value > this.deque[0]) { // this.deque[0] = value // } while (!this.isEmptyDeque() && value > this.deque[this.countD - 1]) { delete this.deque[--this.countD] } this.deque[this.countD++] = value } MaxQueue.prototype.pop_front = function() { if (this.isEmptyQueue()) { return -1 } // Compare the first value of deque and queue. If they are the same, deque will be out of the queue, otherwise deque will not operate if (this.queue[this.headQ] === this.deque[this.headD]) { delete this.deque[this.headD++] } const frontData = this.queue[this.headQ] delete this.queue[this.headQ++] return frontData } MaxQueue.prototype.max_value = function () { return this.deque[this.headD] } MaxQueue.prototype.isEmptyDeque = function () { return !(this.countD - this.headD) } MaxQueue.prototype.isEmptyQueue = function () { return !(this.countQ - this.headQ) }
Summary:
Double ended queues can be maintained as monotonically increasing or decreasing; In this way, the team leader is the minimum or maximum value;
For special cases, if the deleted number is just the head of the queue, you need to remove the head of the queue;
5.2.2 leetCode59. sliding window
// sliding window var maxSlidingWindow = function(nums, k) { if (k <= 1) { return nums } const result = [] const deque = [] deque.push(nums[0]) let i = 1 for (; i < k; i++) { while(deque.length && nums[i] > deque[deque.length - 1]) { deque.pop() } // It means that in fact, every number should join the team, but it depends on whether the number at the end of the team can get out of the team deque.push(nums[i]) } result.push(deque[0]) // Traverse subsequent data const len = nums.length for (; i < len; i++) { while(deque.length && nums[i] > deque[deque.length - 1]) { deque.pop() } deque.push(nums[i]) // The most critical step // When the maximum value is outside the window, remove the head of the team // Detects whether the current maximum value is outside the window if (deque[0] === nums[i - k]) { deque.shift() } result.push(deque[0]) } return result }
Summary:
The first k-length queue and k-nums Queues of length need to be traversed separately
Directly check the maximum value at the front k end
When querying at the back len end, pay attention to special cases. If the length of the queue is greater than K (when the maximum value is outside the window, ensure that the length of the queue cannot be greater than k), take the value at the head of the queue out of the queue and continue to enter the subsequent values (note here that when the value of the queue is greater than k, deque [0] = = nums [I - k] must be true)
5.3 practice - circular queue and palindrome check
5.4 summary
- Learned to add and remove elements through enqueue method and dequeue method and follow the first in first out principle
- You learned about the data structure of double ended queues, how to add elements to the front and back ends of double ended queues, and how to remove elements from the front and back ends of double ended queues
- Loop queue and palindrome check
6. Linked list
A linked list stores an ordered set of elements, but unlike an array, the elements in the linked list are not placed continuously in memory. Each element is composed of a node storing the element itself and a reference to the next element (also known as pointer and link);
If you want to access an element in the middle of the linked list, you need to iterate the linked list from the starting point to the required element;
6.1 one way linked list
push method
To add an element to the end of the linked list, we must find the last element. Remember, we only have the reference of the first element! Therefore, we need to access the list circularly until the last item is found. Therefore, we need a variable pointing to the current item in the linked list;
removeAt method
The current variable always refers to the current element of the looped list. We also need a reference to the previous element of the current element;
// Linked list class LinkedList { constructor(equalsFn = defaultEquals) { this.count = 0 // Number of elements in the linked list this.head = undefined // Reference to the first element this.equalsFn = equalsFn // Compare elements for equality } // Add a new element to the tail push(element) { const node = new Node(element) let current; if (this.head == null) { this.head = node } else { // It is well explained that if you want to access an element in the middle of the linked list, you need to iterate the linked list from the starting point to the required element; current = this.head // The last element was found. The next of the last element is null while (current.next != null) { current = current.next } current.next = node } this.count++ } // Inserts a new element into the specified location insert(element, index) { if(index >= 0 && index <= this.count) { const node = new Node(element) let current = this.head // Add in the first position if (index === 0) { node.next = current this.head = node } else { // Add in the middle let previous = this.getElementAt(index - 1) current = previous.next node.next = current previous.next = node } } return false } // Remove an element remove(element) { const index = this.indexOf(element) return this.removeAt(index) } // Returns the index of the element in the linked list indexOf(element) { let current = this.head for (let i = 0; i < this.count && current != null; i++) { if (this.equalsFn(element, current.element)) { return i } current = current.next } return -1 } // Removes the element at the specified location removeAt(index) { // Check out of bounds if (index >= 0 && index < this.count) { let previous; // Previous item let current = this.head // Current item // The first item may be removed if (index === 0) { this.head = current.next } else { // Is one of them for (let i = 0; i < index; i++) { // When looping once, previous is head, current is the first item, and current Next is the second item previous = current // head current = current.next // Is the second item } // Removes a previous item by connecting it to the next item in the current item, skipping the current item previous.next = current.next } this.count-- // Returns the deleted element return current.element } return undefined } // Refactoring the removeAt method using getElementAt newRemoveAt(index) { // Check out of bounds if (index >= 0 && index < this.count) { let previous; // Previous item let current = this.head // Current item // The first item may be removed if (index === 0) { this.head = current.next } else { previous = this.getElementAt(index - 1) current = previous.next previous.next = current.next } this.count-- // Returns the deleted element return current.element } return undefined } // Pull out the method and iterate over the linked list until the target location index returns the current element // Returns the element at a specific location. If it does not exist, it returns undefined getElementAt(index) { if (index >= 0 && index < this.count) { let current = this.head for (let i = 0; i < index && node != null; i++) { current = current.next } return current } return undefined } isEmpty() { return this.size() === 0 } size() { return this.count } toString() { if (this.isEmpty()) { return '' } let current = this.head.next let str = this.head.element for (let i = 1; i < this.count && current != null; i++) { str += ',' + current.element current = current.next } return str } getHead() { return this.head } } // Helper class Node class Node { constructor(element) { this.element = element this.next = null } } function defaultEquals (a, b) { return a === b } const demo = new LinkedList() demo.push(3) console.log(demo) // LinkedList {count: 1, head: Node, equalsFn: ƒ}count: 1equalsFn: ƒ defaultEquals(a, b)head: Node {element: 3, next: null}[[Prototype]]: Object
6.2 bidirectional linked list
The difference between a two-way linked list and a one-way linked list is that the links in a two-way linked list are two-way. One chain goes down one element and one chain goes forward one element;
Bidirectional linked list provides two iterative methods: from beginning to end and from end to end;
In a one-way linked list, if you miss the element you want to find during the iteration, you need to go back to the starting point and restart the iteration; This is an advantage of two-way linked list;
// Bidirectional linked list class DoublyLinkedList extends LinkedList { constructor(equalsFn = defaultEquals) { super(equalsFn) this.tail = null } // An algorithm for inserting a new element anywhere insert(element, index) { // Within the legal scope if (index >= 0 && index <= this.count) { const node = new DoublyNode(element) let current = this.head // If you add in the header if (index === 0) { // Empty linked list if (current == null) { this.head = node this.tail = node } else { node.next = current current.prev = node this.head = node } // Add in the last item } else if (index === this.count) { current = this.tail current.next = node node.prev = current this.tail = node // Add a new item in the middle } else { let previous = this.getElementAt(index - 1) current = this.getElementAt(index) previous.next = node node.prev = previous node.next = current current.prev = node } this.count++ return true } return false } // Deletes the element at the specified location removeAt(index) { // Safety range if (index >= 0 && index < this.count) { let current = this.head // Deleted is the header if (index = 0) { this.head = current.next // If there is only one item if (this.count == 1) { this.tail = undefined // If there are multiple } else { this.head.prev = undefined } // The tail is deleted } else if (index === this.count - 1) { current = this.tail previous = current.prev previous.next = undefined // The middle element is deleted } else { current = this.getElementAt(index) let previous = current.prev previous.next = current.next current.next.prev = previous // The prev of the next element is the previous element } this.count-- return current.element } return undefined } } // Helper class DoublyNode extends Node { constructor(element, next, prev) { super(element, next) this.prev = prev } }
6.3 circular linked list
A circular linked list can have only one-way references like a linked list, or two-way references like a two-way linked list;
The only difference between a circular linked list and a linked list is that the pointer (tail.next) of the last element to the next element does not refer to undefined, but points to the first element (this.head)
// Circular linked list -- unidirectional linked list class CircularLinkedList extends LinkedList { constructor(equalsFn = defaultEquals) { super(equalsFn) } // Insert a new element anywhere insert(element, index) { // boundary condition if (index > 0 && index <= this.count) { const node = new Node(element) let current = this.head // Insert in head if (index === 0) { if (current == null) { this.head = node node.next = this.head } else { node.next = current current = this.getElementAt(this.size()) this.head = node current.next = node } } else { const previous = this.getElementAt(index) current = previous.next node.next = current previous.next = node } this.count++ return true } return false } // Remove element anywhere removeAt(index) { if (index > 0 && index < this.count) { let current = this.head if (index === 0) { if (this.size() === 1) { this.head = undefined } else { current = this.getElementAt(this.size()) current.next = this.head.next current = this.head } } else { const previous = this.getElementAt(index - 1) current = previous.next previous.next = current.next } this.count-- return current.element } return undefined } }
*6.4 with sequence table
Ordered linked list is a linked list structure in which values keep elements in order;
In addition to using the sorting algorithm, you can also insert elements into the correct position to ensure the order of the linked list;
// Ordered linked list const Compare = { LESS_THAN: -1, BIGGER_THAN: 1 } function defaultCompare(a, b) { if (a === b) { return 0 } // If a is smaller, return - 1. If a is larger, return 1 return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN } class SortedLinkedList extends LinkedList { constructor(equalsFn = defaultEquals, compareFn = defaultCompare) { super(equalsFn) this.compareFn = compareFn } insert(element, index = 0) { if (this.isEmpty()) { return super.insert(element, 0) } // Obtain appropriate sequence points const pos = this.getIndexNextSortedElement(element) return super.insert(element, pos) } getIndexNextSortedElement(element) { let current = this.head let i = 0 for (; i < this.size() && current; i++) { const comp = this.compareFn(element, current.element) if (comp === Compare.LESS_THAN) { return i } current = current.next } return i } }
6.5 using linked list to create other data structures - stack
// Implementation stack class StackLinkedList { constructor() { // Use a two-way linked list this.items = new DoublyLinkedList() } push(element) { this.items.push(element) } pop() { if(this.isEmpty()) { return undefined } else { return this.items.removeAt(this.size() - 1) } } }
The reason for using a two-way linked list is that for the stack, we will add elements to the tail and remove elements from the tail of the linked list;
The bidirectional linked list has a reference to the last element of the list, which can be obtained without iterating over the elements of the whole linked list;
The two-way linked list can directly obtain the head and tail elements and reduce the process consumption. Its time complexity is the same as that of the original Stack implementation, which is O(1);
Save a tail element:
// Return tail element peek() { if(this.items.isEmpty()) { return undefined } else { return this.items.getElementAt(this.size() - 1).element } } isEmpty() { return this.items.isEmpty() } size() { return this.items.size() } clear() { return this.items.clear() } toString() { return this.items.toString() }
6.6 chain related algorithm problems
6.6.1 leetCode206 reverse linked list
Reverse the linked list recursively
The first newHead is assigned to head. Note the reference data type of Js;
follow-up
node.next.next = node
node.next = null
The value of newHead is modified
// Reverse linked list var reverseList = function(node) { // let prev = null // let current = head // while(current) { // const next = current.next // current.next = prev // //Every time I go down // prev = current // current = next // } if (node === null || node.next === null) { return node } const newHead = reverseList(node.next) // The node that can execute here for the first time is the penultimate node node.next.next = node node.next = null return newHead }
6.6.2 leetCode02.08 loop detection
6.7 summary
- The most important advantage of the linked list data structure over the array is that you can easily add and remove elements without moving the elements in the linked list; Therefore, when you need to add and remove many elements, the best choice is a linked list rather than an array;
- Learned that if you use an internal linked list to store elements to create a stack, instead of using arrays and objects;
- Understand the benefits of reusing operations available in other data structures rather than rewriting all logical code;
7. Assembly
A set consists of a set of unordered and unique (i.e. non repeatable) items; The data structure uses the same mathematical concepts as finite sets;
7.1 creating collection classes
Use object prototype. hasOwnproperty. The purpose of call () is to prevent the loss of the hasOwnproperty method on the object;
// aggregate class Set { constructor() { this.items = {} } has(element) { // return element in this.items return Object.prototype.hasOwnProperty.call(this.items, element) } add(element) { if (!this.has(element)) { this.items[element] = element return true } return false } delete(element) { if (this.has(element)) { delete this.items[element] return true } return false } clear() { this.items = {} } size() { return Object.keys(this.items).length } // Optimized version of size sizeLegacy() { let count = 0; for(let key in this.items) { // At this point, the key must exist in this In items, consider removing the has judgment if (this.has(key)) { count++ } } return count } values() { return Object.values(this.items) } // Improved values method valuesLegacy() { let values = [] for(let key in this.items) { // At this point, the key must exist in this In items, consider removing the has judgment if (this.has(key)) { values.push(key) } } return values } }
7.2 set operation
We can calculate the set as follows:
Union: a new set containing all elements in two sets;
Intersection: a new set containing elements common to two sets;
Difference set: a new set containing all elements that exist in the first set and do not exist with the second set; (in addition to the elements of the given set, the set of remaining elements)
Subset: verify whether a given set is a subset of another set;
7.2.1 Union
union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union union Union;
Cram both sets into a new set;
// Union union(otherSet) { // Define a new collection const unionSet = new Set() this.values.forEach(item => unionSet.add(item)) otherSet.values().forEach(item => unionSet.add(item)) return unionSet }
7.2.2 intersection
Iterate a set, judge whether another set contains the elements of the iterated set, and if so, put them into a new set;
At this time, the set we iterate over should be a set with fewer elements, so we should judge the set with fewer elements first;
// intersection intersection(otherSet) { const intersectionSet = new Set() const values = this.values() for (let i = 0; i < values.length; i++) { if (otherSet.has(values[i])) { intersectionSet.add(values[i]) } } return intersectionSet } // Improved intersection nextIntersection(otherSet) { const intersectionSet = new Set() const values = this.values() const otherValues = otherSet.values() // Suppose the new collection has fewer elements let biggerSet = values; let smallSet = otherValues; // The new set has more elements than the meta set. Exchange variables if (values.length < otherValues.length) { smallSet = values biggerSet = otherValues } // Iterate over a collection with fewer elements for (let i = 0; i < smallSet.length; i++) { if (biggerSet.includes(smallSet[i])) { intersectionSet.add(smallSet[i]) } } return intersectionSet }
7.2.3 difference set
In addition to the elements of the given set, the set of remaining elements;
// Difference set difference(otherSet) { const differenceSet = new Set() this.values.forEach(item => { if (!otherSet.has(item)) { differenceSet.add(item) } }) return differenceSet }
7.2.4 subset
Determining whether the set is a subset of a given set;
The use of every method is considered for performance. When one returns false, the iteration will stop;
// subset isSubsetOf(otherSet) { // If the elements of the current set are more than those of the given set, it is certainly not a subset if (this.size() > otherSet.size) { return false } // Whether a record is a subset variable. By default, it is a subset of a given set let isSubset = true // for (let i = 0; i < this.size(); i++) { // if(!otherSet.has(this.values()[i])) { // isSubset = false // } // } this.values().every(item => { if (!otherSet.has(item)) { isSubset = false return false } return true }) return isSubset }
7.3 ECMAScript2015 - Set class
The native Set class is somewhat different from our Set;
The values method of set returns Iterator, and size is an attribute of set;
7.3.1 analog set operation
Analog union operation
const union = (setA, setB) => { const unionAb = new Set() setA.forEach(value => unionAb.add(value)) setB.forEach(value => unionAb.add(value)) return unionAb }
Simulated intersection operation
const intersection = (setA, setB) => { const intersectionSet = new Set() setA.forEach(item => { if (setB.has(item)) { intersectionSet.add(value) } }) return intersectionSet }
Analog difference set operation
const difference = (setA, setB) => { const differentSet = new Set() setA.forEach(item => { if (!setB.has(item)) { differentSet.add(item) } }) return differentSet }
7.3.2 using extension operators
// Union new Set([...setA, ...setB]) // intersection new Set([...setA].filter(item => setB.has(item))) // Difference set new Set([...setA].filter(item => !setB.has(item)))
7.4 summary
In advance, we introduced a set class similar to the set class defined in ECMAScript2015, and also introduced some uncommon methods of set data structure, such as union, intersection, difference and subset;
8. Dictionary and hash table
In this chapter, we continue to learn the data structure of using dictionary and hash table to store unique values (non repeated values);
In a set, we are interested in each value itself and regard it as the main element;
In the dictionary, we store data in the form of [key, value] pairs;
The same is true in the hash table, but each key in the dictionary can only have one value;
8.1 dictionary
A dictionary is very similar to a set. A set stores elements in the form of [value, value], and a dictionary stores elements in the form of [key, value];
Dictionaries are also called maps, symbol tables, or associative arrays;
// Dictionaries and hash tables class Dictionary { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn this.table = {} } // Check if a key exists in the dictionary hasKey(key) { return this.table[this.toStrFn(key)] != null } // Like adding a new element to a dictionary set(key, value) { // Both key and value are legal if(key != null && value != null) { this.table[this.toStrFn(key)] = new ValuePair(key, value) return true } return false } // Remove a value from the dictionary remove(key) { if (this.hasKey(key)) { delete this.table[this.toStrFn(key)] return true } return false } // Retrieve a value from the dictionary get(key) { // if (this.hasKey(key)) { // return this.table[this.toStrFn(key)] // } // return undefined const valuePair = this.table[this.toStrFn(key)] return valuePair == null ? undefined : valuePair.value } // Returns all valuePair objects in the dictionary as an array keyValues() { // return Object.values(this.table) const valuePairs = [] // At this time, k is already the string processed by toStrFn for (const k in this.table) { if (this.hasKey(k)) { valuePairs.push(this.table[k]) } } return valuePairs } // Returns all the original key names in the class that are used to identify the value keys() { // return this.keyValues.map(valuePair => valuePair.key) const keys = [] const valuePairs = this.keyValues() for (let i = 0; i < valuePairs.length; i++) { keys.push(valuePairs[i].key) } return key } // Return all values values() { return this.keyValues.map(valuePair => valuePair.value) } // Use forEach to iterate over each key value pair in the dictionary forEach(callback) { const valuePairs = this.keyValues() for (let i = 0; i < valuePairs.length; i++) { const valuePair = valuePairs[i] const result = callback(valuePair.key, valuePair.value) // return false is to jump out of the loop if (result === false) { break } } } size() { return Object.keys(this.table).length } isEmpty() { return this.size() === 0 } clear() { this.table = {} } toString() { if(this.isEmpty()) { return '' } const valuePairs = this.keyValues() let str = `${valuePairs[0].toString()}` for (let i = 1; i < valuePairs.length; i++) { str = `${str},${valuePairs[i].toString()}` } return str } } // The value stored in the dictionary contains the key and value attributes class ValuePair { constructor(key, value) { this.key = key this.value = value } toString() { return `[#${this.key}: ${this.value}]` } } // The default function is to convert key to string function defaultToString(item) { if (item === null) { return 'NULL' } else if (item === undefined) { return 'UNDEFINED' } else if (typeof item === 'string' || item instanceof String) { // If it is a string, it returns itself return `${item}` } return item.toString() } const demo = new Dictionary() demo.set(111, 123) demo.set(222, 123)
8.2 hash table
8.2.1 create hash table
class HashTable { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn this.table = {} } // Hash function loseloseHashCode(key) { if (typeof key === 'number') { return key } const tableKey = this.toStrFn(key) let hash = 0 for (let i = 0; i < tableKey.length; i++) { hash += tableKey.charCodeAt(i) } return hash % 37 } // Get hashCode value hashCode (key) { return this.loseloseHashCode(key) } //Add keys and values to the hash table put(key, value) { if (key != null && value != null) { const position = this.hashCode(key) this.table[position] = new ValuePair(key, value) return true } } // Gets a value from the hash table get(key) { const valuePair = this.table[this.hashCode(key)] return valuePair == null ? undefined : valuePairs.value } // Removes a value from the hash table remove(key) { const hashCode = this.hashCode(key) if (this.table[hashCode]) { delete this.table[hash] return true } return false } }
8.2.3 Hash list and hash set
The difference between a hash set and a Hash list is that instead of adding key value pairs, only values are inserted without keys;
8.2.4 handling conflicts in hash tables
Sometimes, some keys have the same hash value;
When different values correspond to the same position in the hash table, we call it conflict;
Have the same hashCode and multiple different elements;
The methods to deal with conflicts are: link separation, linear exploration and double hash;
8.2.4.1 separate links
The separate link method includes creating a linked list for each position of the hash table and storing the elements in it; It is the simplest way to resolve conflicts, but additional storage space is required in addition to the HashTable instance;
// The method of separating links is used to solve the conflict class HashTableSeparateChaining { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn this.table = {} } put (key, value) { // Ensure that both key and value exist if (key != null && value != null) { const position = this.hashCode(key) if (this.table[position] == null) { this.table[position] = new LinkedList() } this.table[position].push(new ValuePair(key, value)) return true } return false } get (key) { const position = this.hashCode(key) const LinkedList = this.table[position] if (LinkedList != null && !LinkedList.isEmpty()) { let current = LinkedList.getHead() while(current != null) { if (current.element.key === key) { return current.element.value } current = current.next } } return undefined } remove(key) { const position = this.hashCode(key) const LinkedList = this.table[position] if (LinkedList != null && !LinkedList.isEmpty()) { let current = LinkedList.getHead() while(current != null) { if (current.element.key === key) { LinkedList.remove(current.element) if (LinkedList.isEmpty()) { delete this.table[position] } return true } current = current.next } } return false } }
8.2.4.1 linear detection
Store elements directly in a table rather than in a separate data structure;
We search the hashCode location in the table. If not, we add the value to the correct location. If occupied, we iterate the hash table until we find a free location;
- When we remove a key value pair from the hash table, it is not enough to remove only the elements of the position implemented by the data structure before this chapter; If we only remove elements, we may find an empty location when looking for other elements with the same hashCode, which will lead to problems in the algorithm;
There are two techniques for linear exploration:
The first method is soft deletion;
The second is to check whether it is necessary to move one or more elements to the previous position;
// Override put method put (key, value) { // Ensure that both key and value exist if (key != null && value != null) { const position = this.hashCode(key) if (this.table[position] == null) { this.table[position] = new LinkedList() } else { // Key point: when there are elements in the current hashCode, continue iterative search until the next null location let index = position + 1 while(this.table[index] != null) { index++ } // Null location found this.table[index] = new ValuePair(key, value) } this.table[position].push(new ValuePair(key, value)) return true } return false }
// Override get method get(key) { const position = this.hasCode(key) let current = this.table[position] // Current hashCode is not empty if (current != null) { // The key corresponding to the current hashCode position is exactly the key to be found if (current.key == key) { return current.value } else { let index = position + 1 // Start the iteration from the current hasCode position. If the current position is null, it returns undefined while(this.table[index].key != key && this.table[index] != null) { index++ } // If the current position is not null and the key values are equal if (this.table[index] != null && this.table[index].key === key) { return this.table[index].value } } } return undefined }
// Override delete method remove(key) { const position = this.hasCode(key) let current = this.table[position] // Current hashCode is not empty if (current != null) { // The key corresponding to the current hashCode position is exactly the key to be found if (current.key == key) { delete this.table[position] this.verifyRemoveSideEffect(key, position) return true } else { let index = position + 1 // Start the iteration from the current hasCode position. If the current position is null, it returns undefined while(this.table[index].key != key && this.table[index] != null) { index++ } // If the current position is not null and the key values are equal if (this.table[index] != null && this.table[index].key === key) { delete this.table[index] this.verifyRemoveSideEffect(key, index) return true } } } return false } // The hasCode location of the incoming key and the deleted key verifyRemoveSideEffect(key, removePosition) { // Initial hashCode position corresponding to the current key const hash = this.hashCode(key) // The next position of the hashCode corresponding to the current key const index = removePosition + 1 // The safe location cannot be reached until the iteration location is empty while(this.table[index] != null) { // The initial hasCode value corresponding to the key value of the iterated position const posHash = this.hashCode(this.table[index].key) if (posHash <= hash || posHash <= removePosition) { this.table[removePosition] = this.table[index] delete this.table[index] removePosition = index } index++ } }
8.3 ES2015Map
A Map is essentially a collection of key value pairs;
Unlike the Map and Dictionary classes, the values and keys methods both return iterators instead of arrays of values or keys;
Another difference is that our previous size method returns the number of values stored in the dictionary, while the Map class of ES2015 has a size attribute;
8.4 ES2015 WeakMap and WeakSet classes
Weakening methods belonging to Set and Map;
-
The WeakSet or WeakMap class has no entries, keys and values methods;
-
Only objects can be used as keys;
-
These two classes are created mainly for performance. The objects referenced by the key name of WeakMap are weak references, that is, the garbage collection mechanism does not take this reference into account. Therefore, as long as other references of the referenced object are cleared, the garbage collection mechanism will free the memory occupied by the object. That is, once it is no longer needed, the key name object and the corresponding key value pair in WeakMap will disappear automatically without manually deleting the reference;
8.5 summary
Learned the relevant knowledge of the dictionary, and learned how to add, remove and obtain elements and other methods;
Also learned the differences between dictionaries and collections;
Learned hash calculation, how to create a hash data structure, how to add, remove and obtain elements, and how to create hash functions;
Learned to use different methods to solve the conflict problem in hash table;
9. Recursion
ECMAScript2015 tail call Optimization:
If the last operation in the function is to call the function, which means that the function has been basically completed, it does not need to create a new frame stack when calling the last function, but can reuse the existing function frame stack, which is not only fast, but also saves memory;
10. Trees
We learned that the first non sequential data structure is hash table. Next, we will learn another non sequential data structure tree, which is very useful for storing data that needs to be searched quickly;
10.2 related terms
Root node, internal node, external node, subtree;
Binary tree complete binary tree full binary tree
10.3 binary tree and binary search tree
A node in a binary tree can only have two child nodes at most: one is the left child node and the other is the right child node;
Binary search tree is a kind of binary tree, but you only store smaller values in the left node (than the parent node) and larger values in the right node (than the parent node);
// Binary search tree // node class TreeNode { constructor(key) { this.key = key this.left = null this.right = null } } class BinarySearchTree { // The comparison function here is similar to the ordered linked list constructor(compareFn = defaultCompare) { this.compareFn = compareFn this.root = null } // Insert a new key into the tree insert(key) { if (this.root == null) { this.root = new TreeNode(key) } else { this.insertNode(this.root, key) } } insertNode(node, key) { // New key is better than node When I was younger if (this.compareFn(key, node.key) === Compare.LESS_THAN) { // When the smaller tree is empty if (node.left == null) { node.left = new TreeNode(key) } else { // When the smaller tree is not empty, check it again this.insertNode(node.left, key) } } else { if (node.right == null) { node.right = new TreeNode(key) } else { this.insertNode(node.right, key) } } } }
10.4 tree traversal
The process of accessing each node of the tree and performing some operation on them;
10.4.1 middle order traversal
Middle order traversal is a way to access all nodes of the BST in the order of more than one row, that is, access all nodes in the order from the smallest to the largest;
Left subtree = root node = right subtree
// Medium order traversal inOrderTraverse(callback) { this.inOrderTraverseNode(this.root, callback) } inOrderTraverseNode(node, callback) { if (node != null) { this.inOrderTraverseNode(node.left, callback) callback(node.key) this.inOrderTraverseNode(node.right, callback) } }
10.4.2 preorder traversal
Preorder traversal accesses each node with priority over the descendant nodes. One application of preorder traversal is to print a structured document;
Root node = left subtree = right subtree
// Preorder traversal preOrderTraverse(callback) { this.preOrderTraverseNode(this.root, callback) } preOrderTraverseNode(node, callback) { if (node != null) { callback(node.key) this.preOrderTraverseNode(node.left, callback) this.preOrderTraverseNode(node.right, callback) } }
10.4.2 post order traversal
Postorder traversal is to access the descendants of the node first, and then the node itself. One application of postorder traversal is to calculate the space occupied by all files in a directory and its letters;
Left subtree = number of right words = root node
// Postorder traversal postOrderTraverse(callback) { this.postOrderTraverseNode(this.root, callback) } postOrderTraverseNode(node, callback) { if (node != null) { callback(node.key) this.postOrderTraverseNode(node.left, callback) this.postOrderTraverseNode(node.right, callback) } }
10.5 values in the search tree
10.5.1 search for maximum and minimum values
The minimum value is on the left and the maximum value is on the right;
For finding the minimum value, always follow the left side of the tree; For finding the maximum value, always follow the right side of the tree;
// Search for maximum and minimum values min() { return this.minNode(this.root) } minNode(node) { let current = node while(current != null && current.left != null) { current = node.left } return current } max() { return this.maxNode(this.root) } maxNode(node) { let current = node while(current != null && current.right != null) { current = node.right } return current }
10.5.2 search for a specific value
true is returned when found, false is returned when not found
// Search for a specific value search(key) { this.searchNode(this.root, key) } searchNode(node, key) { if (node == null) { return false } // Small, small if (this.compareFn(key, node.key) === Compare.LESS_THAN) { return this.searchNode(node.left, key) } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) { // Big big return this.searchNode(node.right, ket) } else { // It's him return true } }
10.5.3 removing a node
To remove a node, first find the node;
root is assigned the return value of the removeNode method;
The removeNode method returns the tree structure after the specified node is deleted;
// remove remove(key) { this.root = this.removeNode(this.root, key) } removeNode(node, key) { if(node === null) { return null } if (this.compareFn(key, node.key) === Compare.LESS_THAN) { node.left = this.removeNode(node.left, key) return node } else if (this.compareFn(key, node.key === Compare.BIGGER_THAN)) { node.right = this.removeNode(node.right, key) return node } else { // The first case has no child nodes if (node.left == null && node.right == null) { node = null return node } // In the second case, there is only a single child node if (node.left == null) { node = node.right return node } else if (node.right == null) { node = node.left return node } // The third case has two child nodes // You need to find the smallest node in the right subtree // Then use the smallest node to update the value of this node. At this time, the node has been removed // Remove the smallest node on the right // Returns an updated node reference to its parent node const aux = this.minNode(node.right) node.key = aux.key node.right = this.removeNode(node.right, aux.key) return node } }
10.6 self balancing tree
There is a problem with BST: depending on the number of nodes you add, one edge of the tree may be very deep, that is, one branch of the tree will have many layers, while other branches have only a few layers;
This will cause some performance problems, so there is a tree called AVL tree; AVL tree is a self balanced binary search tree, which means that the height difference between the left and right subtrees of any node is at most 1;