Sports (stack)

A stack is a come-in, come-out data structure, such as a browser history

Array in JS already implements all functions of stack

Example: Check whether a palindrome string exists

let letters = [] / / stack
let word = 'racecar'
let rword = ' '

for(let i = 0; i < word.length; i++) {
  letters.push(word[i])
}
for(let i = 0; i < word.length; i++) {
  rword += letters.pop()
}
if (word === rword) {
  console.log(`${word}Is a palindrome string ')}Copy the code

Simple simulation stack implementation

class Stack {
  count = 0
  storage = {}
	// Push the data in
  push(value) {
    this.storage[this.count] = value;
    this.count++;
  }
	// Push out the last pushed data
  pop() {
    if (this.count === 0) {
      return undefined;
    }

    this.count--;
    const result = this.storage[this.count];
    delete this.storage[this.count]
    return result;
  }
  // The number of elements in the stack
  size() {
    return this.count;
  }
	// Look at the last element in the stack
  peek() {
    return this.storage[this.count - 1]; }}Copy the code

Testing:

let myStack = new Stack();
myStack.push(1);
myStack.push(2);
console.log(myStack.peek())
console.log(myStack.pop())
console.log(myStack.peek())
myStack.push('abcd')
console.log(myStack.size())
console.log(myStack.peek())
console.log(myStack.pop())
console.log(myStack.peek())
Copy the code

Set

Elements in a Set are never repeated and are not ordered

ES6 already supports Set natively

Simple simulation of the implementation of Set

class MySet { // Different from native Set
  collection = []

  has(element) {
    return this.collection.indexOf(element) ! = = -1
  }

  values() {
    return this.collection
  }

  add(element) {
    if (!this.has(element)) {
      this.collection.push(element);
      return true;
    }
    return false;
  }

  remove(element) {
    if (this.has(element)) {
      const index = this.collection.indexOf(element);
      this.collection.splice(index, 1);
      return true;
    }
    return false;
  }

  size() {
    return this.collection.length
  }

  // Return the union of 2 sets
  union(otherSet) {
    let unionSet = new MySet();
    let firstSet = this.values()
    let secondSet = otherSet.values()

    firstSet.forEach((e) = > unionSet.add(e))
    secondSet.forEach((e) = > unionSet.add(e))

    return unionSet
  }

  // Returns the intersection of two sets
  intersection(otherSet) {
    let intersectionSet = new MySet();
    let firstSet = this.values()

    firstSet.forEach((e) = > {
      if (otherSet.has(e)) {
        intersectionSet.add(e)
      }
    })

    return intersectionSet
  }

  // Return a new Set whose elements are in the current Set but not in the otherSet
  difference(otherSet) {
    let differenceSet = new MySet();
    let firstSet = this.values()

    firstSet.forEach((e) = > {
      if(! otherSet.has(e)) { differenceSet.add(e) } })return differenceSet
  }

  // Check whether the current Set is a subset of otherSet
  subset(otherSet) {
    let firstSet = this.values()

    return firstSet.every(value= > {
      return otherSet.has(value)
    })
  }
}
Copy the code

test

let setA = new MySet();
let setB = new MySet();
setA.add('a')
setB.add('b')
setB.add('c')
setB.add('a')
setB.add('d')
console.log(setA.subset(setB))
console.log(setA.intersection(setB).values())
Copy the code

The Queue (Queue)

First in, first out, like waiting in line

JS can also implement queues with arrays, but if you want to be more precise, you have to implement it yourself

class Queue {
  collection = []

  print() {
    console.log(this.collection)
  }
  // Push the element to the end of the queue
  enqueue(element) {
    this.collection.push(element)
  }
  // Push the first element out of the queue and return the pushed element
  dequeue() {
    return this.collection.shift()
  }

  front() {
    return this.collection[0]}size() {
    return this.collection.length
  }

  isEmpty() {
    return this.collection === 0}}Copy the code

test

let queue = new Queue()
queue.enqueue('a')
queue.enqueue('b')
queue.enqueue('c')
queue.print()
queue.dequeue()
queue.front()
queue.print()
Copy the code

PriorityQueue

In addition to passing in elements, we also need to pass in the corresponding priority:

If all elements have the same priority, it is the same as a normal queue; But if not, the higher-priority element is inserted at the beginning of the queue

class PriorityQueue extends Queue {
  enqueue(element) {
    const { collection } = this;

    if (this.isEmpty()) {
      collection.push(element)
    } else {
      let added = false;
      for(let i = 0; i < collection.length; i++) {
        if (element[i] < collection[i][1]) {
          collection.splice(i, 0, element)
          added = true;
          break; }}if(! added) { collection.push(element) } } }dequeue() {
    const value = this.collection.shift()
    return value[0]}}Copy the code

test

const pq = new PriorityQueue();
pq.enqueue(['bbb'.2])
pq.enqueue(['ccc'.3])
pq.enqueue(['aaa'.1])
pq.print()
pq.dequeue()
pq.front()
pq.print()
Copy the code

Binary Search Tree

The data stored in A tree looks like A tree in nature. The following is A graphical representation of the tree, in which the circle storing data is called the node, the topmost node A is called the root node, and the leaf node refers to the end of the tree, and there must be no child nodes.

There are many types of trees, and we’ll introduce one of them: binary search trees.

In theory, a tree can have any number of branches. For example, C node in the figure above has F, G and H child nodes, while a binary tree has only two branches. The following figure is a binary tree, where each node has only two branches:

Binary tree is an ordered data structure, each left child node must be smaller than the parent node, and each right child node must be larger than the parent node (generally not equal to). Because of this feature, the search times can always be halved, so the time complexity of this algorithm’s insert/delete (essentially search) is: O(logn), which is much better than linear complexity (traversal), but slower than hash.

Let’s look at the implementation of JS:

class Node {
  constructor(data, left = null, right = null) {
    this.data = data;
    this.left = left;
    this.right = right; }}class BST {
  constructor() {
    this.root = null
  }

  add(data) {
    const node = this.root

    if (node === null) {
      this.root = new Node(data)
      return
    } else {
      const searchTree = (node) = > {
        if (data < node.data) {
          if (node.left === null) {
            node.left = new Node(data)
            return
          } else {
            return searchTree(node.left)
          }
        } else if (data > node.data) {
          if (node.right === null) {
            node.right = new Node(data)
            return
          } else {
            return searchTree(node.right)
          }
        } else {
          return null; }}return searchTree(node)
    }
  }

  findMin() {
    let current = this.root;
    while(current.left ! = =null) {
      current = current.left;
    }
    return current.data
  }

  findMax() {
    let current = this.root;
    while(current.right ! = =null) {
      current = current.right;
    }
    return current.data
  }

  find(data) {
    let current = this.root;

    while(current.data ! == data) {if (data < current.data) {
        current = current.left;
      } else {
        current = current.right;
      }
      if (current === null) {
        return null; }}return current
  }
  // Whether to include the data
  isPresent(data) {
    let current = this.root;

    while(current) {
      if (data === current.data) {
        return true;
      }
      if (data < current.data) {
        current = current.left
      } else {
        current = current.right
      }
    }

    return false;
  }

  remove(data) {
    const removeNode = (node, data) = > {
      if (node === null) {
        return null;
      }
      if (data === node.data) {
        // There are no child nodes
        if (node.left === null && node.right === null) {
          return null
        }
        // There is no left child
        if (node.left === null) {
          return node.right
        }
        // No right child node
        if (node.right === null) {
          return node.left
        }
        // Both left and right child nodes exist
        // If you want to remove 3 nodes, replace it with the left-most node (4) of the right node of 3
        let tempNode = node.right;
        while(tempNode.left ! = =null) {
          tempNode = tempNode.left;
        }
        node.data = tempNode.data;
        node.right = removeNode(node.right, tempNode.data);
        return node;
      } else if(data < node.data) {
        node.left = removeNode(node.left, data)
        return node;
      } else {
        node.right = removeNode(node.right, data)
        returnnode; }}// The root node and the data to delete are passed in
    this.root = removeNode(this.root, data)
  }
}
Copy the code

test

const bst = new BST ()
bst.add(4)
bst.add(2)
bst.add(6)
bst.add(1)
bst.add(3)
bst.add(5)
bst.add(7)
bst.remove(4)
console.log(bst.findMin())
console.log(bst.findMax())
bst.remove(7)
console.log(bst.findMax())
console.log(bst.isPresent(4))
Copy the code

Traversal and height of binary search trees

The height of BST is the distance between the root node and a leaf node. For example, in the figure below, the height with respect to 9, 9 is 0, while the height of 4 and 17 nodes is 1, 5, 7, and 20 is 3.

There must be a minimum height and a maximum height for any BST, if the maximum height – minimum height is not greater than 1, then THE BST is balanced.

As shown in the figure above, the minimum height of this BST is the height 1 from the root node to the first (without two child nodes) leaf node 17, and the maximum height is the height from the root node to any bottommost (without child nodes) leaf node. Obviously the maximum height of this BST is 3, so the tree is unbalanced. The reason this tree is unbalanced is that 17 is missing a left node.

If we add a 10 node (the left node of 17), then the minimum height is 2 and the maximum height is 3, the tree is balanced.

Why tree balance? Because the query efficiency of the balanced tree is higher, the expansion is not done here

We can implement a BST that automatically balances itself when adding or deleting nodes, which will improve the efficiency of BST queries

JS implement binary search tree traversal and height:

class THBST extends BST {
  // Whether it is balanced
  isBalanced() {
    return this.findMinHeight() >= this.findMaxHeight() - 1
  }
  // Minimum height
  findMinHeight(node = this.root) {
    if (node === null) {
      return -1
    }
    // Left and right must return -1 at the end
    let left = this.findMinHeight(node.left)
    let right = this.findMinHeight(node.right)
    If the minimum height of the left tree is small, then the minimum height of the whole tree is the minimum height of the left tree +1
    if (left < right) {
      return left + 1
    } else { Otherwise, the minimum height of the whole tree is the minimum height of the right tree +1
      return right + 1}}// Maximum height
  findMaxHeight(node = this.root) {
    if (node === null) {
      return -1
    }

    let left = this.findMaxHeight(node.left)
    let right = this.findMaxHeight(node.right)

    if (left > right) {
      return left + 1
    } else {
      return right + 1}}// Sequential traversal: start from the left-most node (in order of size) to the right-most node
  inOrder() {
    if (this.root === null) {
      return null;
    } else {
      let result = [];
      function traverseInOrder(node) {
        node.left && traverseInOrder(node.left)
        result.push(node.data)
        node.right && traverseInOrder(node.right)
      }
      traverseInOrder(this.root)
      returnresult; }}// Start from the root node, depth first traversal (first traversal the root node, then extend to the leaf node)
  preOrder() {
    if (this.root === null) {
      return null;
    } else {
      let result = [];
      function traversePreOrder(node) {
        result.push(node.data)
        node.left && traversePreOrder(node.left)
        node.right && traversePreOrder(node.right)
      }
      traversePreOrder(this.root)
      returnresult; }}// The root node is traversed from the left to the left (first to the left, then to the right)
  postOrder() {
    if (this.root === null) {
      return null;
    } else {
      let result = [];
      function traversePostOrder(node) {
        node.left && traversePostOrder(node.left)
        node.right && traversePostOrder(node.right)
        result.push(node.data)
      }
      traversePostOrder(this.root)
      returnresult; }}// Level traversal: breadth-first search, traversal the elements of this level before looking for the next level
  levelOrder() {
    let result = []
    let queue = []

    if (this.root ! = =null) {
      queue.push(this.root)
      while(queue.length > 0) {
        let node = queue.shift()
        result.push(node.data)

        if(node.left ! = =null) {
          queue.push(node.left)
        }
        if(node.right ! = =null) {
          queue.push(node.right)
        }
      }
      return result;
    } else {
      return null; }}}Copy the code

Construct the data and test it as shown below:

const thbst = new THBST()
thbst.add(9)
thbst.add(4)
thbst.add(17)
thbst.add(3)
thbst.add(6)
thbst.add(21)
thbst.add(5)
thbst.add(7)
thbst.add(20)

console.log(thbst.findMinHeight())
console.log(thbst.findMaxHeight())
console.log(thbst.isBalanced())
thbst.add(10)
console.log(thbst.findMinHeight())
console.log(thbst.findMaxHeight())
console.log(thbst.isBalanced())

console.log(thbst.inOrder()) // [3, 4, 5, 6, 7, 9, 10, 17, 20, 21]
console.log(thbst.preOrder()) // [9, 4, 3, 6, 5, 7, 17, 10, 21, 20]
console.log(thbst.postOrder()) // [3, 5, 7, 6, 4, 10, 20, 21, 17, 9]
console.log(thbst.levelOrder()) // [9, 4, 17, 3, 6, 10, 21, 5, 7, 20]
Copy the code

Hash Tables

Hash table is a data structure used to implement associative array or key-value pair mapping. Because of its high efficiency, it is often used to implement data structures such as Map and Object.

The average complexity of each (find, insert, delete) operation is independent of the number of elements in the hash table, and the time complexity is O (1), so it is very fast.

Most development languages have this functionality natively. For example, JavaScript object is an implementation of the hash table. Here are the details:

// Generate an index
function hash(str, max) {
  let hash = 0
  for(let i = 0; i < str.length; i++) {
    hash += str.charCodeAt(i)
  }
  return hash % max;
}

class HashTable {
  constructor(limit) {
    this.storage = []
    this.limit = limit
  }

  print() {
    console.log(this.storage)
  }

  add(key, value) {
    const { storage } = this
    // Call the hash function to get the index in the hash table that stores the value
    let index = hash(key, this.limit)

    if (storage[index] === undefined) {
      storage[index] = [ [key, value] ]
    } else {
      let inserted = false;
      for(let i = 0; i < storage[index][length]; i++) {
        if (storage[index][i][0] === key) { / / update
          storage[index][i][1] = value
          inserted = true}}if (inserted === false) { / / add
        storage[index].push([ key, value ])
      }
    }
  }

  remove(key) {
    const { storage } = this
    let index = hash(key, this.limit)
    // There may be more than one
    if (storage[index].length === 1 && storage[index][0] [0] === key) {
      delete storage[index]
    } else {
      for (let i = 0; i < storage[index].length; i++) {
        if (storage[index][i][0] === key) {
          delete storage[index][i]
        }
      }
    }
  }
  // Find data
  lookup(key) {
    const { storage } = this
    let index = hash(key, this.limit)

    if (storage[index] === undefined) {
      return undefined
    } else {
      for (let i = 0; i < storage[index].length; i++) {
        if (storage[index][i][0] === key) {
          return storage[index][i][1]}}}}}Copy the code

test

let ht = new HashTable(10)
ht.add('aaa'.'111')
ht.add('bbb'.'222')
ht.add('ccc'.'333')
ht.add('ddd'.'444')
console.log(ht.lookup('ccc')) / / 333
ht.print()
Copy the code

Linked List

reference

You can think of each node of a linked list as storing a piece of data. Each node of a linked list has two parts: the data part and the address part.

Linked lists have different forms, mainly divided into three types: one-way linked list, two-way linked list, circular linked list. Here is a one-way linked list:

Unidirectional linked lists versus arrays

To find the performance

A lookup operation for a one-way linked list usually looks like this:

  1. Enter the primary node, start to compare the value of the node, if different through the pointer to the next node
  2. Repeat until the same value is found, or the node’s pointer points to NULL

Linked lists have the same search performance as arrays with O(n) time complexity.

Insert and delete performance

The biggest difference between a linked list and an array lies in the performance advantage of deletion and insertion. Because a linked list is a discontinuous memory, there is no need to carry out a large area of member displacement during insertion and deletion like an array. For example, a new node C is inserted between nodes A and B.

  1. A breaks the pointer to B and points it to C
  2. Node C points to node B. End

This insert operation only needs to move the pointer, and the time complexity of insert and delete is only O(1).

Read performance

Linked lists also have the disadvantage of being far less efficient than arrays, which are read efficiently because they are contiguous memory and can be quickly located by addressing formulas, whereas linked lists, which are discontiguous memory, must be traversed node by node by pointer.

For example, if we want to read the third member of an array, we only need ARR [2] to get the member quickly, whereas linked lists need to be accessed from the head node and read from the next node through the pointer.

JS implementation of single linked list

class Node {
  constructor(element) {
    this.element = element
    this.next = null; }}class LinkedList {
  length = 0;
  head = null;

  size() {
    return this.length;
  }
  // Return the first element of the list
  head() {
    return this.head;
  }
  // Add an element to the end of the list
  add(element) {
    const node = new Node(element)
    if (this.head === null) {
      this.head = node
    } else {
      let currentNode = this.head;
      while(currentNode.next) {
        currentNode = currentNode.next;
      }
      currentNode.next = node;
    }
    this.length++
  }

  remove(element) {
    let currentNode = this.head
    let prevNode;

    if (currentNode.element === element) {
      this.head = currentNode.next
    } else {
      while(currentNode.element ! == element) { prevNode = currentNode currentNode = currentNode.next } prevNode.next = currentNode.next }this.length--; // This is not very rigorous
  }

  isEmpty() {
    return this.length === 0
  }

  indexOf(element) {
    let currentNode = this.head;
    let index = -1;

    while (currentNode) {
      index++;
      if (currentNode.element === element) {
        return index;
      }
      currentNode = currentNode.next;
    }

    return -1;
  }
  // Get the element corresponding to the index
  elementAt(index) {
    let currentNode = this.head;
    let count = 0;

    while (count < index) {
      count++;
      currentNode = currentNode.next;
    }

    return currentNode.element
  }
  // Add an element at a specific location in the list
  addAt(index, element) {
    let node = new Node(element)
    let currentNode = this.head;
    let prevNode;
    let currentIndex = 0;

    if (index > this.length) {
      return false;
    }

    if (index === 0) {
      node.next = currentNode;
      this.head = node;
    } else {
      while (currentIndex < index) {
        currentIndex++;
        prevNode = currentNode
        currentNode = currentNode.next
      }
      node.next = currentNode
      prevNode.next = node;
    }
    this.length++;
  }
  // This is similar to the addAt method
  removeAt(index) {
    let currentNode = this.head;
    let prevNode;
    let currentIndex = 0;

    if (index < 0 || index >= this.length) {
      return null;
    }

    if (index === 0) {
      this.head = currentIndex.next;
    } else {
      while (currentIndex < index) {
        currentIndex++;
        prevNode = currentNode
        currentNode = currentNode.next
      }
      prevNode.next = currentNode.next;
    }
    this.length--;
    return currentNode.element
  }
}
Copy the code

test

const list = new LinkedList();
list.add('aaa')
list.add('bbb')
list.add('ccc')
list.add('ddd')
list.add('eee')
console.log(list.size()) / / 5
console.log(list.removeAt(3)) // ddd
console.log(list.size()) / / 4
Copy the code

Trie(Dictionary tree)

Application scenarios

Also called a prefix tree, it is a branch of the tree and a data structure used to store associated data.

Trie also has branches, and one branch can store a single word, so the trie structure is often used to store words/strings, for example, to check if a word/string exists in a data set.

In trie, each node represents a letter/character, as shown below:

The star indicates that this position is the end node of a word, that is, to the position of the star mark, there is a complete word

JS code implementation:

class Node {
  constructor() {
    / / as shown above, the keys to the root Node, it is a new Map ([' b ', the new Node ()], [' d ', the new Node ()], [' s', the new Node ()])
    this.keys = new Map(a);this.end = false;
  }
  setEnd() {
    this.end = true;
  }
  isEnd() {
    return this.end; }}class Trie {
  constructor() {
    this.root = new Node()
  }
  add(str, node = this.root) {
    if (str.length === 0) {
      node.setEnd()
      return;
    } else if(! node.keys.has(str[0])) {
      node.keys.set(str[0].new Node())
      return this.add(str.substr(1), node.keys.get(str[0))}else {
      return this.add(str.substr(1), node.keys.get(str[0))}}// Whether word is in Trie
  isWord(word) {
    let node = this.root;

    while (word.length > 1) { / / is greater than 1
      if(! node.keys.has(word[0]) {return false;
      } else {
        node = node.keys.get(word[0])
        word = word.substr(1)}}// While is greater than 1
    return (node.keys.has(word) && node.keys.get(word).isEnd()) ? true : false;
  }
  // Print out all the words (branch)
  print() {
    let words = []
    let search = (node, str) = > {
      if(node.keys.size ! = =0) {
        for (let letter of node.keys.keys()) {
          search(node.keys.get(letter), str.concat(letter))
        }
        if (node.isEnd()) {
          words.push(str)
        }
      } else {
        str.length > 0 ? words.push(str): undefined;
        return;
      }
    }
    search(this.root, ' ')
    return words.length > 0 ? words : null}}Copy the code

test

const myTrie = new Trie();
myTrie.add('ball')
myTrie.add('bat')
myTrie.add('doll')
myTrie.add('dork')
myTrie.add('do')
myTrie.add('dorm')
myTrie.add('send')
myTrie.add('sense')
console.log(myTrie.isWord('doll')) // true
console.log(myTrie.isWord('dor')) // false
console.log(myTrie.isWord('dora')) // false
console.log(myTrie.print()) // ['ball', 'bat', 'doll', 'dork', 'dorm', 'do', 'send', 'sense']
Copy the code

Graphs (FIG.)

Application scenarios

There are many ways to store graphs: adjacency matrix, adjacency list, cross linked list, multiple adjacency list and so on.

The two most commonly used: adjacency matrix and adjacency list, here to explain the adjacency list.

Adjacency list structure:

  1. Store vertices in the graph —-> array/object

  2. Edge pointing relation —-> linked list/array

The diagram below:

The code implementation of the collar table:

class Graph {
  constructor(points) {
    / / points
    this.points = points
    // Number of edges
    this.sideNum = 0;
    // Each point corresponds to a list/array
    this.adjArray = Array.from({ length: points.length }, () = >[])}addPoint(id, point) {
    let index1 = this.points.findIndex(v= > v === id)
    this.adjArray[index1].push(point)

    /* For undirected graphs, suppose A and B are joined: if you want to add only one connection between A and B(a-b or b-a), you need the following statement, but if you add two connections (a-b and b-a), you do not need the following statement */
    // let index2 = this.points.findIndex(v => v === point)
    // this.adjArray[index2].push(id) 

    this.sideNum++
  }
  addPoints(id, points) {
    points.forEach(item= > {
      this.addPoint(id, item)
    })
  }

  getPoints(id) {
    let index = this.points.findIndex(v= > v === id)
    return this.adjArray[index]
  }
  // Prints formatted data
  print() {
    let str = ' '

    this.points.forEach((item, index) = > {
      str += item + '- > [' + this.adjArray[index].join(', ') + '] \n'
    })

    return str
  }
  getMap(points) {
    let arr = points.reduce((arr, item) = > {
      return arr.concat(this.getPoints(item))
    }, [])

    return arr.reduce((map, item) = > {
      if(! map[item]) { map[item] =1
      } else {
        map[item]++
      }
      return map
    }, {})
  }
  / / intersection
  getUnions(points) {
    let map = this.getMap(points)
    let len = points.length

    return Object.keys(map).filter(key= > map[key] >= len)
  }
  / / and set
  getCollect(points) {
    let map = this.getMap(points)
    return Object.keys(map).filter(key= > map[key])
  }
}
Copy the code

Test, to achieve the following structure:

const demo = new Graph(['v0'.'v1'.'v2'.'v3'.'v4'])

/ / register
demo.addPoints('v0'['v2'.'v3']);
demo.addPoints('v1'['v3'.'v4']);
demo.addPoints('v2'['v0'.'v3'.'v4']);
demo.addPoints('v3'['v0'.'v1'.'v2']);
demo.addPoints('v4'['v1'.'v2']);

// -----------------------------------
// console.log(demo.getPoints('v0'))
// console.log(demo.getPoints('v1'))
// console.log(demo.getPoints('v2'))
// console.log(demo.getPoints('v3'))
// console.log(demo.getPoints('v4'))

console.log(demo.print())
Copy the code

If you get something, give it a thumbs up