JavaScript Data Structure and Algorithm Study Notes (Part 1)

1. Realize the stack structure (Stack)

I. Introduction

1.1. Understand what is a data structure?

A data structure is the way in which data is stored and organized in a computer.

The main things to consider: insertion and lookup.

Common data structures:

Array (Aarray)

Stack

Linked List

Graph

Hash table (Hash)

Queue

Tree

Heap

1.2. What is an algorithm?

Algorithms:

A limited instruction set, the description of each instruction does not depend on the language;

Receive some input (in some cases no input is needed);

generate input;

must terminate after a finite number of steps;

Popular understanding: the method/step logic to solve the problem.

2. Stack structure (Stack)

2.1. Introduction

An array is a linear structure, and elements can be inserted and deleted at any position in the array. Stacks and queues are more common restricted linear structures. As shown below:

7d84b47a08d741559e8c1edef78e4260.png

The stack is characterized by first in last out, last in first out (LIFO: last in first out).

The stack structure in the program:

Function call stack: A(B(C(D()))): That is, A function calls B, B calls C, and C calls D; during the execution of A, A will be pushed onto the stack, and then when B is executed, B It is also pushed onto the stack, and functions C and D are also pushed onto the stack when they are executed. So the order of the current stack is: A->B->C->D (stack top); after the function D is executed, the stack will be popped and released, and the order of the pop-up stack is D->C->B->A;

Recursion: Why does recursion without a stop condition cause stack overflow? For example, function A is a recursive function, which keeps calling itself (because the function has not been executed yet, the function will not be popped off the stack), and keeps pushing the same function A onto the stack, eventually causing a stack overflow (Stack Overfloat).

Common operations on stacks:

  • push(element): add a new element to the top position of the stack;
  • pop(): Remove the element at the top of the stack and return the removed element at the same time;
  • peek(): returns the element at the top of the stack without making any changes to the stack (this method does not remove the element at the top of the stack, just returns it);
  • isEmpty(): Return true if there is no element in the stack, otherwise return false;
  • size(): returns the number of elements in the stack. This method is similar to the length property of an array;
  • toString(): Return the contents of the stack structure as a string.

2.2. Encapsulation stack class

// encapsulation stack class

    function Stack(){

      // properties on the stack

      this.items =[]

      // Stack related operations

      // 1.push(): Push elements onto the stack

      //Method 1 (not recommended): Add methods to objects, other objects cannot be reused

      // this.push = () => { }

      //Method 2 (recommended): Add methods to the Stack class, which can be reused by multiple objects

      Stack.prototype.push = function(element) {

        this.items.push(element)

      }

      // 2.pop(): Remove elements from the stack

      Stack.prototype.pop = () => {

        return this.items.pop()

      }

      // 3.peek(): Look at the top element of the stack

      Stack.prototype.peek = () => {

        return this.items[this.items.length - 1]

      }

      // 4.isEmpty(): Determine whether the stack is empty

      Stack.prototype.isEmpty = () => {

      // Not this.length (not the length of the Stack object, the Stack class has no length attribute), but the array items defined in the Stack class have the length attribute

        return this.items.length == 0 

      }

      // 5.size(): Get the number of elements in the stack

      Stack.prototype.size = () => {

        return this.items.length

      }

      // 6.toString(): output the data in the stack as a string

      Stack.prototype.toString = () => {

        //The desired output format: 20 10 12 8 7

        let resultString = ''

        for (let i of this.items){

          resultString += i + ' '

        }

        return resultString

      }

    }

Two, JavaScript realizes the queue structure (Queue)

1. Introduction to Queue

A queue is a limited linear table characterized by first in first out (FIFO: first in first out) .
 
The limitation is that it only allows delete operations at the front end of the table;
Insert operations at the back end (rear) of the table;
bad5549b89544fdfa9fbe99fec3bf332.png

 Implementation of the queue class:

The implementation of the queue is the same as that of the stack. There are two options:

Based on array implementation;

Implementation based on linked list;

Common operations on queues:

  • enqueue(element): Add one (or more) new items to the end of the queue;
  • dequeue(): Remove the first (that is, the front of the queue) item of the queue and return the removed element;
  • front(): Returns the first element in the queue - the first to be added and will be the first to be removed. The queue does not make any changes (the elements are not removed, and only the element information is returned, which is very similar to the peek method of the Stack class);
  • isEmpty(): If the queue does not contain any elements, return true, otherwise return false;
  • size(): returns the number of elements contained in the queue, similar to the length attribute of the array;
  • toString(): convert the content in the queue into a string form;

Two, encapsulation queue class

// Based on array encapsulation queue class

    function Queue() {

    // Attributes

      this.items = []

    // method

    // 1.enqueue(): Add elements to the queue

    Queue.prototype.enqueue = element => {

      this.items.push(element)

    }

    // 2.dequeue(): delete the front element from the queue

    Queue.prototype.dequeue = () => {

      return this.items.shift()

    }

    // 3.front(): View the front-end elements

    Queue.prototype.front = () => {

      return this.items[0]

    }

    // 4.isEmpty: Check if the queue is empty

    Queue.prototype.isEmpty = () => {

      return this.items.length == 0;

    }

    // 5.size(): View the number of elements in the queue

    Queue.prototype.size = () => {

      return this.items.length

    }

    // 6.toString(): Output the elements in the queue as strings

    Queue.prototype.toString = () => {

      let resultString = ''

        for (let i of this.items){

          resultString += i + ' '

        }

        return resultString

      }

    }

    // Queue application: interview questions: drumming and passing flowers
    let passGame = (nameList, num) => {       //1. Create a queue structure       let queue = new Queue()

      //2. Add everyone to the queue one by one
      // This is the for loop writing method of ES6, i is equivalent to nameList[i]
      for(let i of nameList){         queue.enqueue(i)       }

      // 3. Start counting
     while(queue.size() > 1){//There is only one person left in the queue and stop counting
      //When it is not num, re-join the end of the queue
      //When it is num, add it Delete from the queue
      // The people before 3.1.num numbers are put back into the end of the queue (add those deleted from the front of the queue to the end of the queue)
      for(let i = 0; i<num-1; i++ ){         queue.enqueue( queue.dequeue())       }       // 3.2.num corresponds to this person, delete it directly from the queue       /*         The idea is this, because the queue does not have an array-like subscript value and cannot directly fetch a certain element, so use, put The num-1 elements in front of num are deleted first and then added to the end of the queue, so that the numth element is placed at the front of the queue, and can be deleted directly by using the dequeue method */ queue.dequeue       (       )      }







      //4. Get the remaining person
      console.log(queue.size()); //104
      let endName = queue.front()
      console.log('The final remaining person:' + endName); // 106    
      
      return nameList. indexOf(endName);
    }

    //5. Test drum pass
    let names = ['lily', 'lucy', 'Tom', 'Lilei', 'Tony']
    console.log(passGame(names, 3)); //113

The form of the push method of the array in the array, stack and queue:

122c9be18ea1438ba1c2a546d6bc4382.png

3. JavaScript implements collections and dictionaries

1. Collection structure

1.1. Introduction

A common implementation of collections is a hash table , which is encapsulated here using the Object class of JavaScript.
 
A collection is usually composed of an unordered set of elements that cannot be repeated.
 
  • Elements in sets often referred to in mathematics can be repeated, but elements in sets in computers cannot be repeated.
Collections are special arrays :
 
  • The special thing is that the elements inside have no order and cannot be repeated .
  • No order means that it cannot be accessed by subscript value , and no duplication means that there will only be one copy of the same object in the collection .

Implement the collection class :
 
  • The Set class in ES6 is a collection class. Here we repackage a Set class to understand the underlying implementation of the collection.
  • The key in the Object class in JavaScript is a collection, which can be used to encapsulate the collection class Set.
 
Collection common operations :
 
  • add(value): add a new item to the collection;
  • remove(value): remove a value from the collection;
  • has(value): returns true if the value is in the collection, otherwise returns false;
  • clear(): remove all items in the collection;
  • size(): Returns the number of elements contained in the collection, similar to the length property of the array;
  • values(): returns an array containing all the values ​​in the collection;
  • Other methods...

1.2. Code implementation

//encapsulation collection class

    function Set() {

      //Attributes

      this.items = {}

      //method

      //One.has method

      Set.prototype.has = value => {

        return this.items.hasOwnProperty(value)

      }

      //two.add method

      Set.prototype.add = value => {

        // Check if the element is already contained in the set

        if (this.has(value)) {

          return false

        }

        // add the element to the collection

        this.items[value] = value//Indicates that the attribute key and value are both value

        return true//indicates that the addition is successful

      }

      //3.remove method

      Set.prototype.remove = (value) => {

        //1. Determine whether the element is contained in the collection

        if (!this.has(value)) {

          return false

        }

        //2. Remove the element from the attribute

        delete this.items[value]

        return true

      }

      //four.clear method

      Set.prototype.clear = () => {

        //The original object has no reference point and will be automatically recycled

        this.items = {}

      }

      //5.size method

      Set.prototype.size = () => {

        return Object.keys(this.items).length

      }

      // get all the values ​​in the collection

      //Six.values ​​method

      Set.prototype.values = function() {

        return Object.keys(this.items)

      }

    }

1.3. Operations between collections

  • Union : Given two collections, returns a new collection containing all the elements in the two collections;
  • Intersection : Given two sets, return a new set containing elements common to both sets;
  • Difference : Given two sets, returns a new set containing all elements that exist in the first set and do not exist in the second set;
  • Subset : Verify whether a given set is a subset of another set;

d33a4b56e8064e2293d49f0cb4c7bb90.png

 Implementation of union :

Implementation idea: Create set C to represent the union of set A and set B, first add all elements in set A to set C, and then traverse set B, if it is an element that set C does not have, add it to set C middle.

Set.prototype.union = otherSet => {

      // this: collection object A

      // otherSet: collection object B

      //1. Create a new collection

      let unionSet = new Set()

      //2. Add all elements in the A collection to the new collection

      let values = this.values()

      // for(let i of values){

      // unionSet.add(i)

      // }

      for(let i = 0;i < values.length;i++){

        unionSet.add(values[i])

      }

      //3. Take out the elements in the B collection and judge whether it needs to be added to the new collection

      values = otherSet.values()

      // for(let i of values){

      // //Since the add method of the collection has judged the repeated elements, it can be added directly here

      // unionSet.add(i)

      // }

      for(let i = 0;i < values.length;i++){

        unionSet.add(values[i])

      }

      return unionSet

    }

Implementation of intersection :

Implementation idea: traverse collection A, and when the obtained element also exists in collection B, add the element to another collection C.

Set.prototype.intersection = otherSet => {

      // this: collection A

      // otherSet: Set B

      //1. Create a new collection

      let intersectionSet = new Set()

      //2. Take an element from A, judge whether it exists in set B at the same time, and put it into a new set if it is

      let values = this.values()

      for(let i =0 ; i < values.length; i++){

        let item = values[i]

        if (otherSet.has(item)) {

          intersectionSet.add(item)

        }

      }

      return intersectionSet

    }

Implementation of difference set :

Implementation idea: traverse collection A, and when the obtained element does not exist in collection B, add the element to another collection C.

Set.prototype.diffrence = otherSet => {

        //this: collection A

        //otherSet: Set B

        //1. Create a new collection

        var diffrenceSet = new Set()

        //2. Take out each element in the A collection, judge whether it exists in B at the same time, add it to the new collection if it does not exist

        var values = this.values()

        for(var i = 0;i < values.length; i++){

          var item = values[i]

          if (!otherSet.has(item)) {

            diffrenceSet.add(item)

          }

        }

        return diffrenceSet

      }

Subset implementation :

Implementation idea: Traversing set A, when one of the obtained elements does not exist in set B, it means that set A is not a subset of set B, and returns false.

Set.prototype.subset = otherSet => {

        //this: collection A

        //otherSet: Set B

        //Traverse all the elements in collection A, if you find that the elements in collection A do not exist in collection B, then return false, if you traverse the entire collection A without returning false, return true

        let values = this.values()

        for(let i = 0; i < values.length; i++){

          let item = values[i]

          if(!otherSet.has(item)){

            return false

          }

        }

        return true

      }

2. Dictionary structure

2.1. Introduction

Features of the dictionary:
 
  • The dictionary stores key-value pairs, and its main feature is one-to-one correspondence;
  • In the dictionary, keys cannot be repeated and are unordered , while Values ​​can be repeated .
The relationship between dictionaries and maps :
 
  • Some programming languages ​​call this mapping relationship a dictionary , such as Dictonary in Swift and dict in Python;
  • In some programming languages, this mapping relationship is called Map , such as HashMap&TreeMap in Java, etc.;

Common operations of dictionary class:

  • set(key,value): Add new elements to the dictionary.
  • remove(key): Remove the data value corresponding to the key value from the dictionary by using the key value.
  • has(key): If a key value exists in this dictionary, it returns true, otherwise it returns false.
  • get(key): Find a specific value through the key value and return it.
  • clear(): Delete all elements in this dictionary.
  • size(): Returns the number of elements contained in the dictionary. Similar to the length property of an array.
  • keys(): Return all the key names contained in the dictionary as an array.
  • values(): Returns all the values ​​contained in the dictionary as an array.

2.2. Package dictionary

The dictionary class can be implemented based on the object structure in JavaScript.
 
//Encapsulate dictionary class
function Dictionary(){
  //dictionary attributes
  this.items = {}
 
  //Dictionary operation method
  //1. Add key-value pairs to the dictionary
  Dictionary.prototype.set = function(key, value){
    this.items[key] = value
  }
 
  //2. Determine whether there is a key in the dictionary
  Dictionary.prototype.has = function(key){
    return this.items.hasOwnProperty(key)
  }
 
  //Three. Remove elements from the dictionary
  Dictionary.prototype.remove = function(key){
    //1. Determine whether the key exists in the dictionary
    if(!this.has(key)) return false
 
    //2. Delete the key from the dictionary
    delete this.items[key]
    return true
  }
 
  //4. Get the value according to the key
  Dictionary.prototype.get = function(key){
    return this.has(key) ? this.items[key] : undefined
  }
 
  //5. Get all keys
  Dictionary.prototype.keys = function(){
    return Object.keys(this.items)
  }
 
  //6.size method
  Dictionary.prototype.keys = function(){
    return this.keys().length
  }
 
  //Seven.clear method
  Dictionary.prototype.clear = function(){
    this.items = {}
  }
}

Fourth, JavaScript implements a hash table

1. Introduction to Hash Table

1.1. Know the hash table

Hash tables are usually implemented based on arrays , but they have more advantages over arrays:
 
  • Hash tables can provide very fast insert-delete-find operations ;
  • No matter how much data there is, inserting and deleting values ​​only takes a very short time, that is, O(1) time class. In fact, it only takes a few machine instructions to do it;
  • The speed of the hash table is faster than that of the tree , and the desired element can be found almost instantly. But it's much simpler to encode than a tree .
Hash tables also have disadvantages:
 
  • The data in the hash table is out of order , so the elements in it cannot be traversed in a fixed way (such as from small to large).
  • Usually, the key in the hash table is not allowed to be repeated , and the same key cannot be placed to store different elements.
What is a hash table?
 
  • Hash tables are not easy to understand. Unlike arrays, linked lists, and trees, their structures and principles can be represented graphically.
  • The structure of the hash table is an array , but its magic lies in a transformation of the subscript value . This transformation can be called a hash function , and the HashCode can be obtained through the hash function .

The hash table is finally implemented based on data, but the hash table can convert the string into the corresponding subscript value through the hash function, and establish the corresponding relationship between the string and the subscript value .

1.2. Hash method

In order to convert a string into a corresponding subscript value, a coding system is required . With the coding system, there are many ways to convert letters into numbers.
 

Some concepts of hash table :

  • Hashing : The process of converting large numbers into subscripts within the range of the array is called hashing;
  • Hash function : We usually convert words into large numbers , and put the code implementation of hashing large numbers in a function, which is called a hash function;
  • Hash table : Encapsulate the entire structure of the array into which the final data is inserted , and the result is a hash table.

Issues that still need to be resolved:

  • Subscripts after hashing may still be repeated . How to solve this problem? This situation is called conflict , conflict is inevitable , we can only resolve conflict.

1.3. Methods for resolving conflicts

  • Option 1: chain address method (zipper method) ;
As shown in the figure below, we take the remainder of 10 for each number , and the remainder ranges from 0 to 9 as the subscript value of the array. Moreover, the location corresponding to each subscript value of the array is no longer a number, but an array or linked list composed of numbers that obtain the same remainder after the remainder operation .
 
46002defa2254402828fb7c32c2b38d6.png

 

In this way, the entire array or linked list can be obtained according to the subscript value, and then continue to search in the array or linked list. Moreover, there are generally not too many conflicting elements.

Summary : The method of chain address method to resolve conflicts is that each array unit stores no single data , but a chain . The data structure commonly used in this chain is an array or a linked list , and the efficiency of searching for the two data structures is equivalent ( Because the elements of the chain are generally not too many).

  • Solution 2: open address method ;

The main way the open address method works is to find empty cells to place conflicting data items.

fb28712788124c81a396d6e7f21e919a.png

According to the different ways of detecting the position of blank cells, it can be divided into three methods: linear detection, secondary detection, and rehashing

1.4. Ways to find blank cells

linear detection
When inserting 13 :
 
  • After hashing (modulo 10), the subscript value index=3 is obtained, but data 33 has already been placed in this position. The linear detection is to start from the index position + 1 to find a suitable position to place 13 one by one . The so-called suitable position refers to the empty position . For example, the position of index=4 in the above figure is the suitable position.
When querying for 13:
 
  • First, 13 is hashed to get index=3. If the data stored at index=3 is the same as the data 13 to be queried, it will be returned directly;
  • If they are not the same, then search linearly, starting from the index+1 position to search for data 13 one by one;
  • The entire hash table will not be traversed during the query process, as long as the query finds an empty position, it will stop , because when inserting 13, the empty position will not be skipped to insert other positions.
When removing 13:
 
  • The deletion operation is similar to the above two cases, but it should be noted that when deleting a data item, the content of the subscript of the position cannot be set to null , otherwise it will affect other subsequent query operations , because once a null position is encountered will stop searching.
  • Usually, when deleting a data item at a location, we can perform special processing on it (for example, set it to -1), so that when we encounter -1 during the search, we know to continue the search.
Problems with linear probing:
 
  • There is a serious problem in linear detection, which is aggregation ;
  • For example, when no element has been inserted into the hash table, insert 23, 24, 25, 26, and 27, which means that the positions with subscript values ​​of 3, 4, 5, 6, and 7 are all placed with data. The filling unit is called aggregation ;
  • Aggregation will affect the performance of the hash table , whether it is insertion/query/deletion;
  • For example, when you insert 13, you will find that the continuous units 3~7 are not allowed to insert data, and you need to experience this situation many times during the insertion process. The quadratic probing method can solve this problem.

9d44f1f87aa044318c8ad97a06366244.png

 secondary detection

The problems with the linear detection mentioned above :

  • If the previous data is inserted continuously , then a newly inserted data may need to detect a long distance ;
  • The secondary detection is optimized on the basis of linear detection :
  • Linear detection : We can regard it as a detection with a step size of 1. For example, starting from the value x in the table below, then the linear detection is to detect in sequence according to the subscript values: x+1, x+2, x+3, etc.;
  • Secondary detection : The step size is optimized, for example, starting from the subscript value x: x+12, x+22, x+33. In this way, a relatively long distance is detected at one time , which avoids the impact of data aggregation.

Problems with secondary detection :

  • When inserting a group of data with large data distribution, such as: 13-163-63-3-213, this situation will cause a kind of aggregation with different step sizes (although the probability of this situation is higher than that of linear detection Aggregation should be small), which will also affect performance.

rehashing

The best solution for finding blank cells in open addressing is rehashing:

  • The step size of the secondary detection is fixed: 1, 4, 9, 16 and so on;
  • Now we need a method: generate a detection sequence that depends on keywords (data) , rather than the detection step of each keyword is the same;
  • In this way, different keywords can use different detection sequences even if they map to the same array subscript ;
  • The method of rehashing is: use another hash function for the keyword, perform hashing again , and use the result of this hashing as the step size of the keyword ;

The second hashing needs to meet the following two points:

  • Different from the first hash function , otherwise the result after hashing is still in the original position;
  • The output cannot be 0 , otherwise each detection will be an endless loop of standing still;

Excellent hash function:

  • stepSize = constant - (key % constant)
  • Where constant is a prime number and is less than the capacity of the array;
  • For example: stepSize = 5 - (key % 5), meet the requirements, and the result cannot be 0;

Efficiency of hashing

Performing insertion and search operations in a hash table is very efficient.

  • If there are no conflicts , then the efficiency will be higher;
  • If a conflict occurs , the access time depends on the subsequent probe length;
  • The average probe length and average access time depend on the fill factor , and as the fill factor becomes larger, the probe length will become longer and longer.

Understanding the concept fill factor:

  • The filling factor represents the ratio of the data items already contained in the current hash table to the length of the entire hash table ;
  • Fill factor = total data items / hash table length ;
  • The filling factor of the open address method is at most 1 , because only blank cells can be filled with elements;
  • The filling factor of the chain address method can be greater than 1 , because as long as you want, the zipper method can be extended indefinitely;

1.5. Performance comparison of different detection methods

  • Linear probing :
It can be seen that as the filling factor increases, the average detection length increases exponentially, and the performance is poor. In practice, the best fill factor depends on the balance between storage efficiency and speed. As the fill factor becomes smaller, the storage efficiency decreases while the speed increases.
 
cf11bfc68b914be5ad25bea561139bce.png

 

  • Performance of secondary probing and rehashing :

Quadratic probing is comparable to rehashing, and they perform slightly better than linear probing. It can be seen from the figure below that as the filling factor increases, the average detection length increases exponentially, and the number of detections required also increases exponentially, and the performance is not high.

2d5156209c6146ae85499912f6caf9f9.png

  • The performance of the chain address method :

It can be seen that as the filling factor increases, the average detection length increases linearly, which is relatively gentle. There are many chain address methods used in development. For example, the chain address method is used in HashMap in Java.

76e9da5218dd43e8af5197a2dda4b258.png

 1.6. Excellent hash function

The advantage of the hash table is its speed, so the hash function cannot use complex algorithms that consume high performance. One way to improve speed is to minimize multiplications and divisions in the hash function .
 
A high-performance hash function should have the following two advantages:
 
  • fast calculation ;
  • uniform distribution ;
fast calculation
Horner's rule : In China, Horner's rule is also called Qin Jiushao's algorithm. The specific algorithm is:
5061e86d9df64f7cbc8be2a82fe5ca5f.png

 

When calculating the value of a polynomial, first calculate the value of the first-degree polynomial in the innermost bracket, and then calculate the value of the first-degree polynomial layer by layer from the inside to the outside. This algorithm converts the value of polynomial f(x) of degree n into the value of polynomial of degree n.

Before transformation :

  • Multiplication times: n(n+1)/2 times;
  • Number of additions: n times;

After transformation :

  • Number of multiplications: n times;
  • Number of additions: n times;

If you use big O to represent the time complexity, it is directly reduced from O(N^2) before the transformation to O(N).

Evenly distributed

In order to ensure that the data is evenly distributed in the hash table , when we need to use constants , try to use prime numbers ; for example: the length of the hash table, the base number of the power of N, etc.

HashMap in Java uses the chain address method, and the formula for hashing is: index = HashCode (key) & (Length-1)

That is to convert the data into binary and perform the AND operation instead of the remainder operation. In this way, the computer directly operates binary data, which is more efficient. However, JavaScript will have problems when performing AND operations called big data, so the remainder operation is still used when using JavaScript to implement hashing.

2. Preliminary package hash table

Common operations for hash tables are:
 
  • put (key, value): insert or modify operation;
  • get(key): Get the element at a specific position in the hash table;
  • remove(key): delete the element at a specific position in the hash table;
  • isEmpty(): If the hash table does not contain any elements, return trun, if the length of the hash table is greater than 0, return false;
  • size(): returns the number of elements contained in the hash table;
  • resize(value): expand the hash table;

2.1. Simple implementation of hash function

First, use Horner's rule to calculate the value of hashCode, and implement hashing through the remainder operation. Here, simply specify the size of the array.

//Design the hash function

    //1. Convert the string to a relatively large number: hashCede

    //2. Compress the large number hasCode into the range (size) of the array

    function hashFunc(str, size){

      //1. Define the hashCode variable

      let hashCode = 0

      //2. Horner's rule, calculate the value of hashCode

      //cats -> Unicode encoding

      for(let i = 0 ;i < str.length; i++){

        // str.charCodeAt(i)//Get the unicode encoding corresponding to a character

        hashCode = 37 * hashCode + str.charCodeAt(i)

      }

      //3. Remainder operation

      let index = hashCode % size

      return index

    }

2.2. Create a hash table

The array structure model of the encapsulated hash table:
886a3196ebc747f6a66b168389f14f59.png

First create the hash table class HashTable, and add the necessary attributes and the hash function implemented above, and then implement other methods.

//Encapsulate the hash table class

    function HashTable() {

      //Attributes

      this.storage = []

      this.count = 0//Calculate the number of stored elements

      //Filling factor: when loadFactor > 0.75, capacity expansion is required; when loadFactor < 0.25, capacity needs to be reduced

      this.limit = 7//initial length

      //method

      //hash function

      HashTable.prototype.hashFunc = function(str, size){

      //1. Define the hashCode variable

      let hashCode = 0

      //2. Horner's rule, calculate the value of hashCode

      //cats -> Unicode encoding

      for(let i = 0 ;i < str.length; i++){

        // str.charCodeAt(i)//Get the unicode encoding corresponding to a character

        hashCode = 37 * hashCode + str.charCodeAt(i)

      }

      //3. Remainder operation

      let index = hashCode % size

      return index

    }

2.3.put(key,value)

The insertion and modification operations of the hash table are the same function: because when the user passes in a <key, value>, if the key does not exist, then it is an insertion operation, and if the key already exists, then it is a modification operate.
9ae02cb1245841d59a132321c32a019d.png

Implementation ideas :

  • First, get the index value index according to the key, the purpose is to insert the data into the corresponding location of the storage;
  • Then, take out the bucket according to the index value. If the bucket does not exist, create the bucket first, and then place it at the position of the index value;
  • Then, judge whether to add or modify the original value. If there is already a value, modify the value; if not, perform subsequent operations.
  • Finally, perform the new data operation.

Code implementation :

//insert & modify operations

    HashTable.prototype.put = function (key, value){

      //1. Obtain the corresponding index according to the key

      let index = this.hashFunc(key, this.limit)

      //2. Take out the corresponding bucket according to the index

      let bucket = this.storage[index]

      //3. Determine whether the bucket is null

      if (bucket == null) {

        bucket = []

        this.storage[index] = bucket

      }

      //4. Determine whether to modify data

      for (let i = 0; i < bucket.length; i++) {

        let tuple = bucket[i];

        if (tuple[0] == key) {

          tuple[1] = value

          return//No return value

        }

      }

      //5. Add operation

      bucket.push([key, value])

      this.count += 1

    }

2.4.get(key)

Implementation ideas :
 
  • First, obtain its corresponding index value index in the storage through the hash function according to the key;
  • Then, get the corresponding bucket according to the index value;
  • Next, judge whether the acquired bucket is null, if it is null, directly return null;
  • Then, linearly traverse whether each key in the bucket is equal to the incoming key. If equal, return the corresponding value directly;
  • Finally, after traversing the bucket, if the corresponding key is still not found, just return null.

//get operation

    HashTable.prototype.get = function(key){

      //1. Obtain the corresponding index according to the key

      let index = this.hashFunc(key, this.limit)

      //2. Obtain the corresponding bucket according to the index

      let bucket = this.storage[index]

      //3. Determine whether the bucket is equal to null

      if (bucket == null) {

        return null

      }

      //4. If there is a bucket, then perform a linear search

      for (let i = 0; i < bucket.length; i++) {

        let tuple = bucket[i];

        if (tuple[0] == key) {//tuple[0] stores key, tuple[1] stores value

          return tuple[1]

        }

      }

      //5. Still not found, then return null

      return null

    }

2.5.remove(key)

Implementation ideas :
 
  • First, obtain its corresponding index value index in the storage through the hash function according to the key;
  • Then, get the corresponding bucket according to the index value;
  • Next, judge whether the acquired bucket is null, if it is null, directly return null;
  • Then, search the bucket linearly, find the corresponding data, and delete it;
  • Finally, still not found, return null;

Code:

//delete operation

    HashTable.prototype.remove = function(key){

      //1. Obtain the corresponding index according to the key

      let index = this.hashFunc(key, this.limit)

      //2. Obtain the corresponding bucket according to the index

      let bucket = this.storage[index]

      //3. Determine whether the bucket is null

      if (bucket == null) {

        return null

      }

      //4. If there is a bucket, then perform a linear search and delete it

      for (let i = 0; i < bucket.length; i++) {

        let tuple = bucket[i]

        if (tuple[0] == key) {

          bucket.splice(i,1)

          this.count -= 1 

          return tuple[1]

        }

    }

      //5. Still not found, return null

      return null

    }

2.6. Implementation of other methods

Other methods include: isEmpty(), size() :
 
Code:
/ / Determine whether the hash table is null
  HashTable.prototype.isEmpty = function(){
    return this.count == 0
  }
 
  // Get the number of elements in the hash table
  HashTable.prototype.size = function(){
    return this.count
  }

3. Expansion of the hash table

3.1. Expansion and compression
Why do you need to expand?
 
  • We used an array with a length of 7 in the hash table earlier. Since the chain address method is used , the load factor (loadFactor) can be greater than 1, so this hash table can insert new data without limit.
  • However, as the amount of data increases , the bucket array (linked list) corresponding to each index in the storage will become longer and longer, which will reduce the efficiency of the hash table
When is capacity expansion required?
 
  • The common situation is to expand the capacity when loadFactor > 0.75 ;
How to expand?
 
  • Simple expansion can be doubled directly (about prime numbers, discussed later);
  • After expansion, all data items must be modified synchronously;

Implementation ideas :

  • First, define a variable, such as oldStorage pointing to the original storage;
  • Then, create a new array with a larger capacity and let this.storage point to it;
  • Finally, take out each data in each bucket in oldStorage and add them to the new array pointed to by this.storage in turn;

88c3f09bc1a441958b835911de6281c9.png

 //Hash table expansion

  HashTable.prototype.resize = function(newLimit){

    //1. Save the old storage array content

    let oldStorage = this.storage

    //2. Reset all properties

    this.storage = []

    this.count = 0

    this.limit = newLimit

    //3. Traverse all buckets in oldStorage

    for (let i = 0; i < oldStorage.length; i++) {

      //3.1. Take out the corresponding bucket

      const bucket = oldStorage[i];

      //3.2. Determine whether the bucket is null

      if (bucket == null) {

        continue

      }      

      //3.3. If there is data in the bucket, take out the data and insert it again

      for (let j = 0; j < bucket.length; j++) {

        const tuple = bucket[j];

        this.put(tuple[0], tuple[1])//key and value of inserted data

      }

    }

  }

The resize method of the hash table defined above can not only realize the expansion of the hash table, but also realize the compression of the capacity of the hash table.

Loading factor = data in the hash table / length of the hash table , that is, loadFactor = count / HashTable.length.

  • Usually, when the filling factor laodFactor > 0.75 , the hash table is expanded. Add the following code to the add method (push method) in the hash table:

//Determine whether expansion operation is required

      if(this.count > this.limit * 0.75){

        this.resize(this.limit * 2)

      }

  • When the filling factor laodFactor < 0.25 , the hash table capacity is compressed. Add the following code to the delete method (remove method) in the hash table:

//Reduce capacity

    if (this.limit > 7 && this.count < this.limit * 0.25) {

      this.resize(Math.floor(this.limit / 2))

    }

3.2. Choose a prime number as capacity

The capacity of the expanded hash table is a prime number
 
Implementation ideas :
 
After 2 times expansion, call isPrime in a loop to judge whether the obtained capacity is a prime number, if not , then +1 until it is.
 
Code implementation :
 
  • Step 1: First, you need to add the isPrime method for judging the prime number and the getPrime method for obtaining the prime number to the HashTable class:

/ / Determine whether the incoming num is a prime number

  HashTable.prototype.isPrime = function(num){

      if (num <= 1) {

        return false

      }

      //1. Get the square root of num: Math.sqrt(num)

      //2. Loop judgment

      for(var i = 2; i<= Math.sqrt(num); i++ ){

        if(num % i == 0){

          return false;

        }

      }

        return true;

    }

    // method to get prime number

    HashTable.prototype.getPrime = function(num){

       //7*2=14,+1=15,+1=16,+1=17(prime number)

      while (!this.isPrime(num)) {

        num++

      }

      return num

    }

  • Step 2: Modify the operations related to array expansion in the put method of adding elements and the remove method of deleting elements:

Add the following code to the put method:

//Determine whether expansion operation is required

      if(this.count > this.limit * 0.75){

        let newSize = this.limit * 2

        let newPrime = this.getPrime(newSize)

        this.resize(newPrime)

      }

Add the following code to the remove method:

//Reduce capacity

          if (this.limit > 7 && this.count < this.limit * 0.25) {

            let newSize = Math.floor(this.limit / 2)

            let newPrime = this.getPrime(newSize)

            this.resize(newPrime)

          }

Fourth, the complete implementation of the hash table

//Encapsulate the hash table class

    function HashTable() {

      //Attributes

      this.storage = []

      this.count = 0//Calculate the number of stored elements

      //Filling factor: when loadFactor > 0.75, capacity expansion is required; when loadFactor < 0.25, capacity needs to be reduced

      this.limit = 7//initial length

      //method

      //hash function

      HashTable.prototype.hashFunc = function(str, size){

      //1. Define the hashCode variable

      let hashCode = 0

      //2. Horner's rule, calculate the value of hashCode

      //cats -> Unicode encoding

      for(let i = 0 ;i < str.length; i++){

        // str.charCodeAt(i)//Get the unicode encoding corresponding to a character

        hashCode = 37 * hashCode + str.charCodeAt(i)

      }

      //3. Remainder operation

      let index = hashCode % size

      return index

    }

 

    //1. Insert & modify operation

    HashTable.prototype.put = function (key, value){

      //1. Obtain the corresponding index according to the key

      let index = this.hashFunc(key, this.limit)

      //2. Take out the corresponding bucket according to the index

      let bucket = this.storage[index]

      //3. Determine whether the bucket is null

      if (bucket == null) {

        bucket = []

        this.storage[index] = bucket

      }

      //4. Determine whether to modify data

      for (let i = 0; i < bucket.length; i++) {

        let tuple = bucket[i];

        if (tuple[0] == key) {

          tuple[1] = value

          return//No return value

        }

      }

      //5. Add operation

      bucket.push([key, value])

      this.count += 1

      //6. Determine whether expansion operation is required

      if(this.count > this.limit * 0.75){

        let newSize = this.limit * 2

        let newPrime = this.getPrime(newSize)

        this.resize(newPrime)

      }

    }

    //2. Get operation

    HashTable.prototype.get = function(key){

      //1. Obtain the corresponding index according to the key

      let index = this.hashFunc(key, this.limit)

      //2. Obtain the corresponding bucket according to the index

      let bucket = this.storage[index]

      //3. Determine whether the bucket is equal to null

      if (bucket == null) {

        return null

      }

      //4. If there is a bucket, then perform a linear search

      for (let i = 0; i < bucket.length; i++) {

        let tuple = bucket[i];

        if (tuple[0] == key) {//tuple[0] stores key, tuple[1] stores value

          return tuple[1]

        }

      }

      //5. Still not found, then return null

      return null

    }

    //3. Delete operation

    HashTable.prototype.remove = function(key){

      //1. Obtain the corresponding index according to the key

      let index = this.hashFunc(key, this.limit)

      //2. Obtain the corresponding bucket according to the index

      let bucket = this.storage[index]

      //3. Determine whether the bucket is null

      if (bucket == null) {

        return null

      }

      //4. If there is a bucket, then perform a linear search and delete it

      for (let i = 0; i < bucket.length; i++) {

        let tuple = bucket[i]

        if (tuple[0] == key) {

          bucket.splice(i,1)

          this.count -= 1 

          return tuple[1]

          //6. Reduce capacity

          if (this.limit > 7 && this.count < this.limit * 0.25) {

            let newSize = Math.floor(this.limit / 2)

            let newPrime = this.getPrime(newSize)

            this.resize(newPrime)

          }

        }

    }

      //5. Still not found, return null

      return null

    }

  /*------------------Other methods--------------------*/

  / / Determine whether the hash table is null

  HashTable.prototype.isEmpty = function(){

    return this.count == 0

  }

  // Get the number of elements in the hash table

  HashTable.prototype.size = function(){

    return this.count

  }

  //Hash table expansion

  HashTable.prototype.resize = function(newLimit){

    //1. Save the old storage array content

    let oldStorage = this.storage

 

    //2. Reset all properties

    this.storage = []

    this.count = 0

    this.limit = newLimit

    //3. Traverse all buckets in oldStorage

    for (let i = 0; i < oldStorage.length; i++) {

      //3.1. Take out the corresponding bucket

      const bucket = oldStorage[i];

      //3.2. Determine whether the bucket is null

      if (bucket == null) {

        continue

      }      

      //3.3. If there is data in the bucket, take out the data and insert it again

      for (let j = 0; j < bucket.length; j++) {

        const tuple = bucket[j];

        this.put(tuple[0], tuple[1])//key and value of inserted data

      }

    }

  }

  / / Determine whether the incoming num is a prime number

  HashTable.prototype.isPrime = function(num){

      if (num <= 1) {

        return false

      }

      //1. Get the square root of num: Math.sqrt(num)

      //2. Loop judgment

      for(var i = 2; i<= Math.sqrt(num); i++ ){

        if(num % i == 0){

          return false;

        }

      }

        return true;

    }

    // method to get prime number

    HashTable.prototype.getPrime = function(num){

       //7*2=14,+1=15,+1=16,+1=17(prime number)

      while (!this.isPrime(num)) {

        num++

      }

      return num

    }

 }

Five, JavaScript implements one-way linked list

1. Introduction to one-way linked list

Linked lists, like arrays, can be used to store a series of elements, but the implementation mechanisms of linked lists and arrays are completely different. Each element of the linked list consists of a node that stores the element itself and a reference (some languages ​​call it a pointer or connection) pointing to the next element.

259f815052744e8dbfc6366a607a272f.jpg

37206d282ec14c27b2aeea4d53859218.png 

  • The head attribute points to the first node of the linked list;
  • The last node in the linked list points to null;
  • When there is no node in the linked list, head directly points to null;

Disadvantages of arrays:

  • The creation of an array usually needs to apply for a continuous memory space (a whole block of memory), and the size is fixed. Therefore, when the original array cannot meet the capacity requirements , it needs to be expanded (generally, apply for a larger array, such as 2 times, and then copy the elements in the original array).
  • Inserting data at the beginning or middle of an array is expensive and requires a large number of element shifts.

Advantages of linked list:

  • The elements in the linked list do not need to be a continuous space in the memory , and the memory of the computer can be fully utilized to realize flexible dynamic memory management .
  • The linked list does not have to determine the size when it is created , and the size can be extended indefinitely .
  • When the linked list inserts and deletes data, the time complexity can reach O(1), which is much more efficient than the array.

Disadvantages of linked list:

  • When the linked list accesses any element at any position, it needs to be accessed from the beginning (the first element cannot be skipped to access any element).
  • Elements cannot be directly accessed through the subscript value, and need to be accessed one by one from the beginning until the corresponding element is found.
  • While it is easy to get to the next node, it is difficult to go back to the previous node .

Common operations in linked lists:

  • append(element): Add a new item to the end of the linked list;
  • insert(position, element): Insert a new item into a specific position of the linked list;
  • get(position): Get the element at the corresponding position;
  • indexOf(element): Returns the index of the element in the linked list. If there is no element in the linked list, return -1;
  • update(position, element): modify an element at a certain position;
  • removeAt(position): Remove an item from a specific position in the linked list;
  • remove(element): remove an item from the linked list;
  • isEmpty(): If the linked list does not contain any elements, return trun, if the length of the linked list is greater than 0, return false;
  • size(): Returns the number of elements contained in the linked list, similar to the length attribute of the array;
  • toString(): Since the linked list item uses the Node class, it is necessary to rewrite the default toString method inherited from the JavaScript object, so that it only outputs the value of the element;

2. Encapsulate the one-way linked list class

2.0. Create a one-way linked list class

First create the one-way linked list class Linklist, and add basic attributes, and then implement the common methods of one-way linked list:

// Encapsulate linked list class

    function LinkList(){

      // Encapsulate an inner class: node class

      function Node(data){

        this.data = data;

        this.next = null;

      }

      // Attributes

      // The attribute head points to the first node of the linked list

      this.head = null;

      this.length = 0;

      // 1. Implement the append method

      LinkList.prototype.append = data => {

        //1. Create a new node

        let newNode = new Node(data)

        //2. Add a new node

        //Case 1: When there is only one node

        if(this.length == 0){

          this.head = newNode

        //Case 2: The number of nodes is greater than 1, add a new node at the end of the linked list  

        }else {              

          //Let the variable current point to the first node

          let current = this.head

          //When current.next (the next node is not empty) is not empty, keep looping until current points to the last node

          while (current.next){

            current = current.next

          }

          // The next of the last node points to the new node

          current.next = newNode

        }

        //3. length+1 after adding new nodes

        this.length += 1

      }

// Two. Implement the toString method

      LinkList.prototype.toString = () => {

        // 1. Define variables

        let current = this.head

        let listString = ""

 

        // 2. Loop to get nodes one by one

        while(current){ 

          listString += current.data + " "

          current = current.next//Don't forget to make current point to the next node after splicing the data of a node

        }

        return listString

      }

 

      // 3. Implement the insert method

      LinkList.prototype.insert = (position, data) => {

      //Understand the meaning of positionon: position=0 means that the new boundary point will become the first node after insertion, position=2 means that the new boundary point will become the third node after insertion

        //1. Make an out-of-bounds judgment on the position: it is required that the incoming position cannot be negative and cannot exceed the length of the LinkList

        if(position < 0 || position > this.length){

          return false

        }

        //2. Create newNode based on data

        let newNode = new Node(data)

 

        //3. Insert a new node

        //Case 1: insert position position=0

        if(position == 0){

          // Make the new node point to the first node

          newNode.next = this.head

          // Let head point to the new node

          this.head = newNode

        //Case 2: Insertion position position>0 (this case includes position=length)

        } else{

          let index = 0

          let previous = null

          let current = this.head

          //Step 1: Use the while loop to make the variable current point to the next node of the position position (note the writing of the while loop)

          while(index++ < position){

          //Step 2: Before current points to the next node, let previous point to the node currently pointed to by current

            previous = current

            current = current.next

          }

          // Step 3: Make newNode point to the next node of the position through the variable current (the current has already pointed to the next node of the position position at this time)

          newNode.next = current

          //Step 4: Use the variable previous to make the previous node at position point to newNode

          previous.next = newNode

          

  //We can't directly operate the nodes in the linked list, but we can point to these nodes through variables to indirectly operate the nodes;

        }

        //4. Length+1 is required after the new node is inserted

        this.length += 1;

        return true

      }

//Four. Implement the get method

      LinkList.prototype.get = (position) => {

        //1. Out of bounds judgment

        // When position = length, get null so 0 =< position < length

        if(position < 0 || position >= this.length){

          return null

        }

        //2. Get the data of the next node at the specified positon position

        // Also use a variable to indirectly manipulate nodes

        let current = this.head

        let index = 0

        while(index++ < position){

          current = current.next

        }

        return current.data

      }

 

      //5. Implement the indexOf method

      LinkList.prototype.indexOf = data => {

        //1. Define variables

        let current = this.head

        let index = 0

 

        //2. Start searching: as long as current does not point to null, it will keep looping

        while(current){

          if(current.data == data){

            return index

          }

          current = current.next

          index += 1

        } 

        //3. After traversing the linked list, if it is not found, return -1

        return -1

      }

//6. Implement the update method

      LinkList.prototype.update = (position, newData) => {

        //1. Out of bounds judgment

        //Because the modified node cannot be null, position cannot be equal to length

        if(position < 0 || position >= this.length){

          return false

        }

        //2. Find the correct node

        let current = this.head

        let index = 0

        while(index++ < position){

          current = current.next

        }

        //3. Change the data of the next node at position to newData

        current.data = newData

        //Return true to indicate the modification is successful

        return true

      }

 

      // Seven. Implement the removeAt method

      LinkList.prototype.removeAt = position => {

        //1. Out of bounds judgment

        if (position < 0 || position >= this.length) {

          return null

        }

        //2. Delete element

        //Case 1: When position = 0 (delete the first node)

        let current = this.head

        if (position ==0 ) {

        //Case 2: position > 0

          this.head = this.head.next

        }else{

          let index = 0

          let previous = null

          while (index++ < position) {

            previous = current

            current = current.next

          }

          //After the loop ends, current points to the node after position, and previous points to the node before current

          //Make the next of the previous node point to the next of the current

          previous.next = current.next

        }

        //3,length-1

        this.length -= 1

        //Return the data of the deleted node, for which current is defined at the top

        return current.data

      }

/*-------------Implementation of other methods--------------*/

      //8. Implement the remove method

      LinkList.prototype.remove = (data) => {

        //1. Get the position of data in the list

        let position = this.indexOf(data)

        //2. According to the location information, delete the node

        return this.removeAt(position)

      }

      //9. Implement the isEmpty method

      LinkList.prototype.isEmpty = () => {

        return this.length == 0

      }

      //10. Implement the size method

      LinkList.prototype.size = () => {

        return this.length

      }

    }

Six, JavaScript realizes the doubly linked list

1. Introduction to doubly linked list

Doubly linked list: It can be traversed from the beginning to the end , and from the end to the head . That is to say, the process of linked list connection is bidirectional, and its realization principle is: a node has both forward-connection reference and backward-connection reference .

Disadvantages of doubly linked list:

  • Every time a node is inserted or deleted, four references need to be processed instead of two, which will be more difficult to implement;
  • Compared with the one-way linked list, it occupies a larger memory space;
  • However, these disadvantages are trivial compared to the convenience of doubly linked lists.

The structure of the doubly linked list:

5c0078c63c794216b5148cb1082140a4.png

  • The doubly linked list not only has a head pointer pointing to the first node, but also has a tail pointer pointing to the last node;
  • Each node consists of three parts: item stores data, prev points to the previous node, and next points to the next node;
  • The prev of the first node of the doubly linked list points to null ;
  • The next of the last node of the doubly linked list points to null ;

Common operations (methods) for doubly linked lists:

  • append(element): Add a new item to the end of the linked list;
  • inset(position, element): Insert a new item into a specific position of the linked list;
  • get(element): Get the element at the corresponding position;
  • indexOf(element): Returns the index of the element in the linked list, if there is no element in the linked list, it returns -1;
  • update(position, element): modify an element at a certain position;
  • removeAt(position): Remove an item from a specific position in the linked list;
  • isEmpty(): If the linked list does not contain any elements, return trun, if the length of the linked list is greater than 0, return false;
  • size(): Returns the number of elements contained in the linked list, similar to the length attribute of the array;
  • toString(): Since the linked list item uses the Node class, it is necessary to rewrite the default toString method inherited from the JavaScript object, so that it only outputs the value of the element;
  • forwardString(): returns the string form of forward traversal nodes;
  • backwordString(): returns the string form of the node traversed in reverse;

2. Encapsulating the doubly linked list class

2.0. Create a doubly linked list class

First create a doubly linked list class DoubleLinklist, and add basic attributes, and then implement the common method of doubly linked list:

//Encapsulate the doubly linked list class

    function DoubleLinklist(){

      //Encapsulate inner class: node class

      function Node(data){

        this.data = data

        this.prev = null

        this.next = null

      }

      //Attributes

      this.head = null

      this.tail ==null

      this.length = 0

      }

2.1.append(element)

Code:

//append method

      DoubleLinklist.prototype.append = data => {

        //1. Create a new node based on data

        let newNode = new Node(data)

        //2. Add node

        //Case 1: The first node is added

        if (this.length == 0) {

          this.tail = newNode

          this.head = newNode 

        //Case 2: Adding is not the first node

        }else {

          newNode.prev = this.tail

          this.tail.next = newNode

          this.tail = newNode

        }

        //3.length+1

        this.length += 1

      }

2.2.toString() summary

Code:
 

      //Convert the linked list into a string format
      //one.toString method
      DoubleLinklist.prototype.toString = () => {         return this.backwardString()       }

      //2.forwardString method
      DoubleLinklist.prototype.forwardString = () => {         //1. Define variable         let current =this.tail         let resultString = ""


        //2. Traverse forward sequentially to get each node
        while (current) {           resultString += current.data + "--"           current = current.prev          }         return resultString       }




      //Three. backwardString method
      DoubleLinklist.prototype.backwardString = () => {         //1. Define variable         let current = this.head         let resultString = ""


        //2. Traverse backwards one by one to get each node
        while (current) {           resultString += current.data + "--"           current = current.next         }         return resultString       }




2.3.insert(position,element)

Code:
 

//insert method
      DoubleLinklist.prototype.insert = (position, data) => {         //1. Cross-border judgment         if (position < 0 || position > this.length) return false

        //2. Create a new node based on data
        let newNode = new Node(data)

        //3. Insert a new node
        //The original linked list is empty
          //Case 1: The inserted newNode is the first node

       if (this.length == 0) {           this.head = newNode           this.tail = newNode         //The original linked list is not empty         }else {



8c171c279fb247169fa15c7e86478777.png

  //情况2:position == 0
          if (position == 0) {
            this.head.prev = newNode
            newNode.next = this.head
            this.head = newNode

63be32e3cb7d42bd85b9a48cb59bd32c.png

    //情况3:position == this.length 
          } else if(position == this.length){
            this.tail.next = newNode
            newNode.prev = this.tail
            this.tail = newNode

32a4903b933d4f75a44d1f99dbd17091.png

 //Case 4: 0 < position < this.length
          }else{             let current = this.head             let index = 0             while(index++ < position){               current = current.next             }             //Modify the node variable before and after the position of pos to point to             newNode. next = current             newNode.prev = current.prev             current.prev.next = newNode             current.prev = newNode           }         }         //4.length+1         this.length += 1         return true//Return true to indicate successful insertion       }















c7d4d2009723462f840d20fe134c9678.png

 2.4.get(position)

Code:

      //get method
      DoubleLinklist.prototype.get = position => {         //1. Out of bounds judgment         if (position < 0 || position >= this.length) {//position cannot be equal to length return null when         getting           elements



        //2. Get element
        let current = null
        let index = 0
        //this.length / 2 > position: traverse from the beginning
        if ((this.length / 2) > position) {           current = this.head           while(index++ < position ){           current = current.next         }         //this.length / 2 =< position: traverse from the tail         }else{           current = this.tail           index = this.length - 1           while(index-- > position){           current = current .prev         }         }         return current.data       }













Detailed process:
 
Define two variables current and index, obtain the current node and the corresponding index value index through while loop traversal according to the previous idea, until you find a node after the position that needs to be obtained, at this time index = pos =x, and then return current Just .data.
 
If the number of nodes in the linked list is large, this search method is not efficient, and the improvement method is:
 
Be sure to use this.length to obtain the number of nodes in the linked list, otherwise an error will be reported.
  • When this.length / 2 > position: start traversing from the head (head);
  • When this.length / 2 < position: start traversing from the tail (tail);

2e3ecee0e4d547b0ac4340f9b6c7adcd.png

2.5.indexOf(element)

Code:
 
//indexOf method
      DoubleLinklist.prototype.indexOf = data => {
        //1. Define variables
        let current = this.head
        let index = 0
 
        //2. Traverse the linked list to find the same node as data
        while(current){
          if (current.data == data) {
            return index
          }
          current = current.next
          index += 1
        }
        return -1
      }

2.7.update(position,element)

Code:
 
//update method
      DoubleLinklist.prototype.update = (position, newData) => {
        //1. Out of bounds judgment
        if (position < 0 || position >= this.length) {
          return false
        }
 
        //2. Find the correct node
        let current = this.head
        let index = 0
        //this.length / 2 > position: traverse from the beginning
        if (this.length / 2 > position) {
          while(index++ < position){
          current = current.next
        }
        //this.length / 2 =< position: traverse from the end
        }else{
          current = this.tail
          index = this.length - 1
          while (index -- > position) {
            current = current.prev
          }
        }
 
        //3. Modify the data of the found node
        current.data = newData
        return true//indicates successful modification
      }

2.8.removeAt(position)

Code:

//removeAt method

  DoubleLinklist.prototype.removeAt = position => {

        //1. Out of bounds judgment

        if (position < 0 || position >= this.length) {

          return null

        }

        //2. Delete node

        //When length == 1 in the linked list

        //Case 1: There is only one node in the linked list

        let current = this.head//Defined at the top to facilitate the return of current.data in the following situations

        if (this.length == 1) {

          this.head = null

          this.tail = null

        //When length > 1 in the linked list

        } else{

          //case 2: delete the first node

          if (position == 0) {

            this.head.next.prev = null

            this.head = this.head.next

          //case 3: delete the last node

          }else if(position == this.length - 1){

            current = this.tail//In this case, return the last node that was deleted

            this.tail.prev.next = null

            this.tail = this.tail.prev

          }else{

          //Case 4: Delete the node in the middle of the linked list

            let index = 0

            while(index++ < position){

              current = current.next

            }

            current.next.prev = current.prev

            current.prev.next = current.next

          }

        }

        //3.length -= 1

        this.length -= 1

        return current.data//return the data of the deleted node

      }

Detailed process:

There are several situations when a node is deleted:

When the length of the linked list = 1:

  • Case 1: Delete all nodes in the linked list: just make the head and tail of the linked list point to null.

265e28d4e76e4429852c2e06186ad0f9.png

 When the length of the linked list > 1:

  • Case 2: Delete the first node in the linked list:

      Pass: this.head.next.prev = null, change to 1;

     Pass: this.head = this.head.next, change to point to 2;

    Although Node1 has references to other nodes, but no references to Node1, Node1 will be automatically recycled.

b13915df571a475bafdb0075cdffda48.png

  • Case 3: Delete the last node in the linked list:

      Pass: this.tail.prev.next = null, modify to point to 1;

      Pass: this.tail = this.tail.prev, modify to point to 2;

19b81316e7ab45c48dccb9851bf8e90a.png

  •  Case 4: Delete the node in the middle of the linked list:
    find the node to be deleted through the while loop, such as position = x, then the node to be deleted is Node(x+1), as shown in the following figure:

d8341875ece043018d792e60c9cd8353.png

Pass: current.next.prev = current.prev, modify to point to 1;

Pass: current.prev.next = current.next, modify to point to 2;

In this way, there is no reference pointing to Node(x+1) (current is pointing to Node(x+1), but current is a temporary variable, which will be destroyed after the method is executed), and then Node(x+1) will be automatically delete.

d8ed82ed720e49a3a810b7fa5137092c.png

 2.9. Other methods

Other methods include: remove(element), isEmpty(), size(), getHead(), getTail()
 
Code:

  //8.remove method

  DoubleLinklist.prototype.remove = data => {

    //1. Get the subscript value according to data

    let index = this.indexOf(data)

    //2. Delete the node at the corresponding position according to the index

    return this.removeAt(index)

  }

  //nine.isEmpty method

  DoubleLinklist.prototype.isEmpty = () => {

    return this.length == 0

  }

  //10.size method

  DoubleLinklist.prototype.size = () => {

    return this.length

  }

  //11.getHead method: Get the first element of the linked list

  DoubleLinklist.prototype.getHead = () => {

    return this.head.data

  }

  //12.getTail method: Get the last element of the linked list

  DoubleLinklist.prototype.getTail = () => {

    return this.tail.data

  }

3. Summary of linked list structure

A singly linked list has two attributes head and next, and a doubly linked list has four attributes head, tail, next, and prev . Handling their pointers is equivalent to connecting them correctly, thus forming a chain, which is the realization of a simple linked list.

3.1. Notes

  • In the linked list, current = current.next can be seen from left to right, as current --> current.next, that is, current points to the next node of current.
  • The principle of deleting a node: as long as there is no reference to the object, no matter whether the object has references to other objects, the object will be recycled (deleted).
  • Any position in the parameter must be judged out of bounds.

3.2. Addition, deletion, modification and query of linked list

Take the doubly linked list as an example: adding, deleting, modifying and checking the linked list is nothing more than obtaining the corresponding node in the linked list and changing the direction of the two variables prev and next .
  • Situation 1: You only need two variables of head and tail to obtain the variables that need to be operated (here refers to being able to obtain them easily, of course you want to obtain them through head.next.next... or tail.prev.prev... The desired node is also ok), in this case the length of the linked list is length: 0 <= length <= 2.
  • Situation 2: When you cannot rely on tail and head to obtain the variables that need to be operated, you can use the while loop to traverse to find the nodes that need to be operated:

3.3. Modify the linked list reference point

The pointing of the newNode reference should be modified first, and then other references should be modified
  • Case 1: When the node that needs to be operated can be obtained through head and tail references, finally change the pointer of the head or tail variable.
  • Case 2: When using current to obtain the node that needs to be operated, change the pointing of curren.next or current.prev at last.

9adb78c1350f416ebacde6dc55f93698.png

 3.4. Traversing the linked list

Accumulate two traversal ideas
 
  • Get the next node and index value at the specified position = x position:

After the loop ends index = position = x, the variable current points to Node(x+1), and the value of the variable index is the index value x of Node(x+1).

  • Traverse all nodes in the linked list:
The loop stops when current.next = null, and current points to the last node of the linked list.
 

 

 

Guess you like

Origin blog.csdn.net/m0_65835778/article/details/126460034