[Data structure and algorithm] JavaScript implements hash table

1. Introduction to hash tables

1.1. Understand the hash table

Hash tables are usually implemented based onarrays, but they have more advantages over arrays:

  • Hash tables can provide very fastinsert-deletion-lookup operations;
  • No matter how much data there is, inserting and deleting values ​​only takes a very short time, that is, O(1) time level. In fact, it only requiresa few machine instructions to complete;
  • The hash table is faster thantree, and you can basically find the desired element instantly. But encoding is much simpler compared to trees.

Hash tables also have shortcomings:

  • The data in the hash table isout of order, so it cannot be traversed in a fixed way (such as from small to large) elements in it.
  • Normally, the keys in the hash table arenot allowed to be repeated. The same key cannot be placed to save different element.

What is a hash table?

  • Hash tables are not easy to understand, unlike arrays, linked lists, and trees, which can express their structure and principles in the form of graphics.
  • The structure of the hash table isarray, but its magic lies in the subscript value A transformation, we can call this transformationhash function, which can be obtained through the hash function HashCode.

Learn about hash tables through the following cases:

  • Case 1: The company wants to store the information of 1,000 people, and each job number corresponds to the information of an employee. If you use an array, it will be more troublesome to add and delete data; if you use a linked list, it will be more troublesome to obtain data. Is there a data structure that can convert an employee's name into its corresponding job number, and then search for the employee's complete information based on the job number? That's right, you can use the hash function of the hash table to achieve this.
  • Case 2: Storing contacts and corresponding phone numbers: When you want to find Zhang San’s number (for example), if you use an array: Since you don’t know the subscript value of Zhang San’s data object, it is very troublesome to find it. When using a linked list Just as troublesome. By using a hash table, the name Zhang San can be converted into its corresponding subscript value through the hash function, and then the search efficiency is very high through the subscript value.

That is to say: 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 /span>, establishes the corresponding relationship between strings and subscript values.

1.2. Hashing method

In order to convert the string into the corresponding subscript value, a coding system is needed. In order to facilitate understanding, we create such a coding system: For examplea is 1 and b is 2, c is 3, and so on, z is 26, and the space is 27 (regardless of capitalization).

With the coding system in place, there are many ways to convert letters into numbers:

  • Method 1: Add numbers. For examplecats is converted into a number: 3+1+20+19=43, then 43 is stored in the array as the subscript value of the cats word;

    However, this method will have such a problem: many words will be 43 after being converted into numbers in this way, such as was. In the arraya subscript value can onlystore one data, So this method is unreasonable.

  • Method 2: Multiplication of powers. The numbers we usually usegreater than 10 are multiplied by powers to express its uniqueness. For example: 6543=6 * 103 + 5 * 102 + 4 * 10 + 3; words can also be represented in this way: cats = 3 * 273 + 1 * 272 + 20 * 27 + 17 =60337;

    Although this method can ensure the uniqueness of characters, if it is a longer character (such as aaaaaaaaaa), the number represented will be very large, which requires a large-capacity array. However, there are many subscript values ​​pointing to invalid values. data (for example, there is no such word as zxcvvv), resulting in a waste of array space.

Summary of the two options:

  • The first solution (let the numbers be added and summed) producestoo few array subscripts;
  • The second solution (multiplying and summing the powers of 27) produces too many array subscripts;

Now we need acompression method to get the huge The integer range is packed intothe acceptable array range. This can be achieved through the remainder operation. Although the structure obtained by the remainder operation may also be repeated, it can be solved in other ways.

Some concepts of hash tables:

  • Hashing: Convertlarge numbers into The process of subscripting within the array range is called hashing;
  • Hash function:We usually convert words into Large numbers, hashlarge numbers The code implementation of a>;hash function is placed in a function, which is called
  • Hash table:Perform the entire Structure encapsulation, the result ishash table.

Issues that still need to be addressed:

  • The hashed subscript may still beduplicated. How to solve this problem? This situation is called conflict. Conflict is inevitable. We Conflicts can only beresolved.
1.3. Methods of resolving conflicts

Two common options for resolving conflicts:

  • Option 1:Landing method(Landing method );

As shown in the figure below, we perform the remainder operation on each number10, then the range of the remainder< a i=3>0~9 is used as the subscript value of the array. Moreover, the position corresponding to each subscript value in the array no longer stores a number, but an array composed of numbers that obtain the same remainder after a remainder operation. or linked list.

Insert image description here

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

Summary:The way to resolve conflicts with the chain address method is toeach array unit store different Instead of a single data, it is a chain . This chain is often The data structure used is array or linked list, and the search efficiency of the two data structures is equivalent (because the chain generally does not have too many elements).

  • Option 2:Open land method;

The main way the open address method works islooking for blank cellsto placeconflicts Data items of a>.

Insert image description here

According to the different ways of detecting the position of blank cells, it can be divided into three methods:

  • Linear detection
  • secondary detection
  • rehash
1.4. How to find blank cells
Linear detection

Entry at 13:00

  • The subscript value index=3 obtained after hashing (modulo 10), but the data 33 has been placed at this location. Linear detection starts fromindex position +1 and searches for the appropriate position one by one backwards to place 13. The so-called suitable position refers to the empty position of . For example, the position of index=4 in the picture above is the suitable position. .

This time is 13 o'clock

  • First, 13 is hashed to obtain index=3. If the data stored at index=3 is the same as the data 13 that needs to be queried, it will be returned directly;
  • When is not the same, the search is linear, starting from the index+1 position and searching for data 13 one by one;
  • The entire hash table will not be traversed during the query. As long as the empty position is found, it will stop because there will be no jump when inserting 13 Pass the empty position to insert another position.

Our office is closed at 13:00

  • The deletion operation is similar to the above two situations, but it should be noted that when deleting a data item, cannot subscript the position. Content is set to null, otherwise it will affect other subsequent query operations , because the search will stop as soon as it encounters a null position.
  • NormallyWhen deleting a data item at a location, we canspecially handle it a> (for example, set to -1), so that when you encounter -1 during search, you will know to continue searching.

Problems with linear detection:

  • Linear detection has a serious problem, which isaggregation;
  • If no element has been inserted into the hash table, 23, 24, 25, 26, 27 are inserted, which means that the positions with subscript values ​​3, 4, 5, 6, and 7 are placed. Data, this kind of a series of filled units is called aggregation; a>
  • Aggregation will affect the performance of the hash table, whether it is insertion/query/deletion;
  • For example, when inserting 13, you will find that consecutive units 3 to 7 are not allowed to insert data, and you need to go through this situation multiple times during the insertion process. The secondary detection method can solve this problem.

Insert image description here

secondary detection

As mentioned aboveProblems with linear detection:

  • If the previous data wascontinuously inserted, then the newly inserted data may need to be detected Very long distance;

    Secondary detection is carried out on the basis of linear detectionOptimization:

  • Linear detection: We can think of it as detection with a step size of 1, for example, from below Starting from the table value x, then linear detection is based on the subscript values: x+1, x+2, x+3, etc.;

  • Second detection: The step size is optimized, for example, starting from the subscript value x: x+12, x+22, x+33. In this way, can detect a relatively long distance at once, avoiding 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 data with different step sizes. This kind of aggregation (although the probability of this situation is smaller than that of linear detection) will also affect performance.
rehash

The best solution for finding blank cells in open addressing isrehashing:

  • The step size of the secondary detection is fixed: 1, 4, 9, 16 and so on;
  • Now a method is needed: generate a keyword (data) dependent detection sequence instead of each keyword detection step They all look the same;
  • In this way, different keywords are mapped to the same array index , you can also usedifferent detection sequences;
  • The method of re-hashing is to use another hash function for the keyword, Do 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:

  • and have different first hash functions, otherwise the hashed result will still be the original position;
  • cannot be output as 0, otherwise each detection will be an endless loop of standing still;

Excellent hash function:

  • stepSize = constant - (key % constant)
  • where constant isprime number, and is less than the capacity of the array;
  • For example: stepSize = 5 - (key % 5), which meets the requirements, and the result cannot be 0;

Hash efficiency

Insertion and search operations are very efficient in hash tables.

  • If there are noconflicts, then the efficiency will be higher;
  • Ifconflict, the access time depends on the subsequent probe length;
  • The average detection length and the average access time depend onthe filling factor. As the filling factor becomes larger, the detection length will become longer. Comes longer and longer.

Understanding the conceptLoading factor:

  • The loading factor represents the data items already included in the current hash table and the entire hash table Ratio of length;
  • Loading factor = total data items / hash table length;
  • The loading factor of the open address method is maximum1, because only empty cells can be placed element;
  • The loading factor of the chain address method can be greater than 1, because the zipper method can be infinite if you want to Extend;
1.5. Comparison of performance of different detection methods
  • Linear detection:

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 filling factor depends on the balance between storage efficiency and speed. As the filling factor becomes smaller, storage efficiency decreases and speed increases.

Insert image description here

  • Performance of secondary probing and rehashing:

Quadratic probing and rehashing perform equally well, and their performance is slightly better than linear probing. As can be seen from the figure below, as the filling factor increases, the average detection length increases exponentially, the number of detections required also increases exponentially, and the performance is not high.

Insert image description here

  • Performance of chain address method:

It can be seen that as the filling factor increases, the average detection length increases linearly and relatively gently. The chain address method is often used in development. For example, the HashMap in Java uses the chain address method.

Insert image description here

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;
Quick calculation

Horner's law: In China, Horner's law is also calledQin Jiushao algorithm. The specific algorithm is:

Insert image description here

When finding the value of a polynomial, first calculate the value of the linear polynomial in the innermost bracket, and then calculate the value of the linear polynomial layer by layer from the inside out. This algorithm converts the value of n degree polynomial f(x) into the value of n degree polynomials.

before change

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

After transformation:

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

If big O is used to represent the time complexity, it is directly reduced from O(N2) before 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 of Nth power, etc.

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

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

2. Preliminary encapsulation of hash table

Common operations on 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 hash table length 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, Horner's rule is used to calculate the value of hashCode, and hashing is implemented through the remainder operation. Here, the size of the array is simply specified.

    //设计哈希函数
    //1.将字符串转成比较大的数字:hashCede
    //2.将大的数字hasCode压缩到数组范围(大小)之内
    function hashFunc(str, size){
    
    
      //1.定义hashCode变量
      let hashCode = 0

      //2.霍纳法则,计算hashCode的值
      //cats -> Unicode编码
      for(let i = 0 ;i < str.length; i++){
    
    
        // str.charCodeAt(i)//获取某个字符对应的unicode编码
        hashCode = 37 * hashCode + str.charCodeAt(i)
      }

      //3.取余操作
      let index = hashCode % size
      return index
    }

Test code:

    //测试哈希函数
    console.log(hashFunc('123', 7));
    console.log(hashFunc('NBA', 7));
    console.log(hashFunc('CBA', 7));
    console.log(hashFunc('CMF', 7));

Test Results:

Insert image description here

2.2. Create a hash table

Array structure model that encapsulates hash tables:

Insert image description here

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

    //封装哈希表类
    function HashTable() {
    
    
      //属性
      this.storage = []
      this.count = 0//计算已经存储的元素个数
      //装填因子:loadFactor > 0.75时需要扩容;loadFactor < 0.25时需要减少容量
      this.limit = 7//初始长度

      //方法
      //哈希函数
      HashTable.prototype.hashFunc = function(str, size){
    
    
      //1.定义hashCode变量
      let hashCode = 0

      //2.霍纳法则,计算hashCode的值
      //cats -> Unicode编码
      for(let i = 0 ;i < str.length; i++){
    
    
        // str.charCodeAt(i)//获取某个字符对应的unicode编码
        hashCode = 37 * hashCode + str.charCodeAt(i)
      }

      //3.取余操作
      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 originally exist, then it is an insertion operation; if the key already exists, then it is Modify operations.

Insert image description here

Implementation ideas:

  • First, obtain the index value index according to the key, with the purpose of inserting the data into the corresponding location of the storage;
  • Then, take out the bucket based on the index value. If the bucket does not exist, create the bucket first and then place it at the index value;
  • Next, determine 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 operation of adding new data.

Code:

    //插入&修改操作
    HashTable.prototype.put = function (key, value){
    
    
      //1.根据key获取对应的index
      let index = this.hashFunc(key, this.limit)

      //2.根据index取出对应的bucket
      let bucket = this.storage[index]

      //3.判断该bucket是否为null
      if (bucket == null) {
    
    
        bucket = []
        this.storage[index] = bucket
      }

      //4.判断是否是修改数据
      for (let i = 0; i < bucket.length; i++) {
    
    
        let tuple = bucket[i];
        if (tuple[0] == key) {
    
    
          tuple[1] = value
          return//不用返回值
        }
      }

      //5.进行添加操作
      bucket.push([key, value])
      this.count += 1
    }

Test code:

    //测试哈希表
    //1.创建哈希表
    let ht = new HashTable()

    //2.插入数据
    ht.put('class1','Tom')
    ht.put('class2','Mary')
    ht.put('class3','Gogo')
    ht.put('class4','Tony')
    ht.put('class4', 'Vibi')
    console.log(ht);

Test Results:

Insert image description here

2.4.get(key)

realistic path

  • First, obtain its corresponding index value in the storage based on the key through the hash function;
  • Then, obtain the corresponding bucket based on the index value;
  • Then, determine whether the obtained bucket is null. If it is null, return null directly;
  • 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.
   //获取操作
    HashTable.prototype.get = function(key){
    
    
      //1.根据key获取对应的index
      let index = this.hashFunc(key, this.limit)

      //2.根据index获取对应的bucket
      let bucket = this.storage[index]

      //3.判断bucket是否等于null
      if (bucket == null) {
    
    
        return null
      }

      //4.有bucket,那么就进行线性查找
      for (let i = 0; i < bucket.length; i++) {
    
    
        let tuple = bucket[i];
        if (tuple[0] == key) {
    
    //tuple[0]存储key,tuple[1]存储value
          return tuple[1]
        }
      }

      //5.依然没有找到,那么返回null
      return null
    }

Test code:

    //测试哈希表
    //1.创建哈希表
    let ht = new HashTable()
    
	//2.插入数据
    ht.put('class1','Tom')
    ht.put('class2','Mary')
    ht.put('class3','Gogo')
    ht.put('class4','Tony')
    
    //3.获取数据
    console.log(ht.get('class3'));
    console.log(ht.get('class2'));
    console.log(ht.get('class1'));

Test Results:

Insert image description here

2.5.remove(key)

realistic path

  • First, obtain its corresponding index value in the storage based on the key through the hash function;
  • Then, obtain the corresponding bucket based on the index value;
  • Then, determine whether the obtained bucket is null. If it is null, return null directly;
  • Then, linearly search the bucket to find the corresponding data and delete it;
  • Finally, if it is still not found, null is returned;

Code:

   //删除操作
    HashTable.prototype.remove = function(key){
    
    
      //1.根据key获取对应的index
      let index = this.hashFunc(key, this.limit)

      //2.根据index获取对应的bucket
      let bucket = this.storage[index]

      //3.判断bucket是否为null
      if (bucket == null) {
    
    
        return null
      }

      //4.有bucket,那么就进行线性查找并删除
      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.依然没有找到,返回null
      return null
    }

Test code:

    //测试哈希表
    //1.创建哈希表
    let ht = new HashTable()
    
	//2.插入数据
    ht.put('class1','Tom')
    ht.put('class2','Mary')
    ht.put('class3','Gogo')
    ht.put('class4','Tony')
    
    //3.删除数据
    console.log( ht.remove('class2'));
    console.log(ht.get('class2'));

Test Results:

Insert image description here

2.6. Implementation of other methods

Other methods include:isEmpty(), size():

Code:

//判断哈希表是否为null
  HashTable.prototype.isEmpty = function(){
    
    
    return this.count == 0
  }

  //获取哈希表中元素的个数
  HashTable.prototype.size = function(){
    
    
    return this.count
  }

Test code:

    //测试哈希表
    //1.创建哈希表
    let ht = new HashTable()

    //2.插入数据
    ht.put('class1','Tom')
    ht.put('class2','Mary')
    ht.put('class3','Gogo')
    ht.put('class4','Tony')
    
    //3.测试isEmpty()
    console.log(ht.isEmpty());
    //4.测试isEmpty()
    console.log(ht.size());
    console.log(ht);

Test Results:

Insert image description here

3. Expansion of Hash Table

3.1. Expansion and compression

Why is expansion needed?

  • We used an array of length 7 in the hash table. Since the **chain address method is used, the filling The factor (loadFactor) can be greater than 1, so this hash table can insert new data without limit.
  • However, asthe amount of data increases, the bucket array (linked list) corresponding to each index in the storage will become longer and longer. , which will result in a reduction in the efficiency of the hash table

Under what circumstances is expansion needed?

  • A common situation is to expand the capacity whenloadFactor > 0.75;

How to expand?

  • Simple expansion can be directly expandedtwice (about prime numbers, discussed later);
  • After expansionalldata items must be modifiedsynchronously;

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 from each bucket in oldStorage and add it to the new array pointed to by this.storage in turn;

Insert image description here

Code:

  //哈希表扩容
  HashTable.prototype.resize = function(newLimit){
    
    
    //1.保存旧的storage数组内容
    let oldStorage = this.storage

    //2.重置所有的属性
    this.storage = []
    this.count = 0
    this.limit = newLimit

    //3.遍历oldStorage中所有的bucket
    for (let i = 0; i < oldStorage.length; i++) {
    
    
      //3.1.取出对应的bucket
      const bucket = oldStorage[i];

      //3.2.判断bucket是否为null
      if (bucket == null) {
    
    
        continue
      }      

      //3.3.bucket中有数据,就取出数据重新插入
      for (let j = 0; j < bucket.length; j++) {
    
    
        const tuple = bucket[j];
        this.put(tuple[0], tuple[1])//插入数据的key和value
      }
    }
  }

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

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

  • Normally, when the loading factor laodFactor > 0.75, the hash table is expanded. Add the following code to the add method (push method) in the hash table to determine whether the expansion function needs to be called for expansion:
     //判断是否需要扩容操作
      if(this.count > this.limit * 0.75){
    
    
        this.resize(this.limit * 2)
      }
  • When the loading factor laodFactor < 0.25 is used to compress the hash table capacity. Add the following code to the delete method (remove method) in the hash table to determine whether the expansion function needs to be called for compression:
    //缩小容量
    if (this.limit > 7 && this.count < this.limit * 0.25) {
    
    
      this.resize(Math.floor(this.limit / 2))
    }
3.2. Choose prime numbers as capacity

Determination of prime numbers

First, let’s review how to determine prime numbers:

Note that 1 is not a prime number

  • Method 1: Targeting the characteristics of prime numbers: they can only be divisible by 1 and num, but not by 2 ~ (num-1). Traverse 2 ~ (num-1) .
    function isPrime(num){
    
    
      if(num <= 1 ){
    
    
        return false
      } 
      for(let i = 2; i <= num - 1; i++){
    
    
        if(num % i ==0){
    
    
          return false
        }
      }
        return true
    }

Although this method can realize the judgment of prime numbers, it is not efficient.

  • Method 2: Just traverse the square root of 2 ~ num.
   function isPrime(num){
    
    
      if (num <= 1) {
    
    
        return false
      }
      //1.获取num的平方根:Math.sqrt(num)
      //2.循环判断
      for(var i = 2; i<= Math.sqrt(num); i++ ){
    
    
        if(num % i == 0){
    
    
          return false;
        }
      }
        return true;
    }

The capacity of the hash table after expansion is a prime number

Implementation ideas:

After 2 times the capacity expansion, it is determined whether the obtained capacity is a prime number by calling isPrime in a loop. If not, +1 will be added until it is. For example, the original length: 7, the length after 2 times expansion is 14, 14 is not a prime number, 14 + 1 = 15 is not a prime number, 15 + 1 = 16 is not a prime number, 16 + 1 = 17 is a prime number, stop the loop, and get the prime number 17 .

Code:

  • Step 1: First, you need to add the isPrime method to determine the prime number and the getPrime method to get the prime number to the HashTable class:
  //判断传入的num是否质数
  HashTable.prototype.isPrime = function(num){
    
    
      if (num <= 1) {
    
    
        return false
      }
      //1.获取num的平方根:Math.sqrt(num)
      //2.循环判断
      for(var i = 2; i<= Math.sqrt(num); i++ ){
    
    
        if(num % i == 0){
    
    
          return false;
        }
      }
        return true;
    }

    //获取质数的方法
    HashTable.prototype.getPrime = function(num){
    
    
       //7*2=14,+1=15,+1=16,+1=17(质数)
      while (!this.isPrime(num)) {
    
    
        num++
      }
      return num
    }
  • Step 2: Modify the operations related to array expansion in the put method for adding elements and the remove method for deleting elements:

Add the following code in the put method:

      //判断是否需要扩容操作
      if(this.count > this.limit * 0.75){
    
    
        let newSize = this.limit * 2
        let newPrime = this.getPrime(newSize)
        this.resize(newPrime)
      }

Add the following code in the remove method:

          //缩小容量
          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)
          }

Test code:

    let ht = new HashTable()

    ht.put('class1','Tom')
    ht.put('class2','Mary')
    ht.put('class3','Gogo')
    ht.put('class4','Tony')
    ht.put('class5','5')
    ht.put('class6','6')
    ht.put('class7','7')
    ht.put('class8','8')
    ht.put('class9','9')
    ht.put('class10','10')
    console.log(ht.size());//10
    console.log(ht.limit);//17

Test Results:

Insert image description here

4. Complete implementation of hash table

	//封装哈希表类
    function HashTable() {
    
    
      //属性
      this.storage = []
      this.count = 0//计算已经存储的元素个数
      //装填因子:loadFactor > 0.75时需要扩容;loadFactor < 0.25时需要减少容量
      this.limit = 7//初始长度

      //方法
      //哈希函数
      HashTable.prototype.hashFunc = function(str, size){
    
    
      //1.定义hashCode变量
      let hashCode = 0

      //2.霍纳法则,计算hashCode的值
      //cats -> Unicode编码
      for(let i = 0 ;i < str.length; i++){
    
    
        // str.charCodeAt(i)//获取某个字符对应的unicode编码
        hashCode = 37 * hashCode + str.charCodeAt(i)
      }

      //3.取余操作
      let index = hashCode % size
      return index
    }

    //一.插入&修改操作
    HashTable.prototype.put = function (key, value){
    
    
      //1.根据key获取对应的index
      let index = this.hashFunc(key, this.limit)

      //2.根据index取出对应的bucket
      let bucket = this.storage[index]

      //3.判断该bucket是否为null
      if (bucket == null) {
    
    
        bucket = []
        this.storage[index] = bucket
      }

      //4.判断是否是修改数据
      for (let i = 0; i < bucket.length; i++) {
    
    
        let tuple = bucket[i];
        if (tuple[0] == key) {
    
    
          tuple[1] = value
          return//不用返回值
        }
      }

      //5.进行添加操作
      bucket.push([key, value])
      this.count += 1

      //6.判断是否需要扩容操作
      if(this.count > this.limit * 0.75){
    
    
        let newSize = this.limit * 2
        let newPrime = this.getPrime(newSize)
        this.resize(newPrime)
      }
    }

    //二.获取操作
    HashTable.prototype.get = function(key){
    
    
      //1.根据key获取对应的index
      let index = this.hashFunc(key, this.limit)

      //2.根据index获取对应的bucket
      let bucket = this.storage[index]

      //3.判断bucket是否等于null
      if (bucket == null) {
    
    
        return null
      }

      //4.有bucket,那么就进行线性查找
      for (let i = 0; i < bucket.length; i++) {
    
    
        let tuple = bucket[i];
        if (tuple[0] == key) {
    
    //tuple[0]存储key,tuple[1]存储value
          return tuple[1]
        }
      }

      //5.依然没有找到,那么返回null
      return null
    }

    //三.删除操作
    HashTable.prototype.remove = function(key){
    
    
      //1.根据key获取对应的index
      let index = this.hashFunc(key, this.limit)

      //2.根据index获取对应的bucket
      let bucket = this.storage[index]

      //3.判断bucket是否为null
      if (bucket == null) {
    
    
        return null
      }

      //4.有bucket,那么就进行线性查找并删除
      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.缩小容量
          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.依然没有找到,返回null
      return null
    }

  /*------------------其他方法--------------------*/
  //判断哈希表是否为null
  HashTable.prototype.isEmpty = function(){
    
    
    return this.count == 0
  }

  //获取哈希表中元素的个数
  HashTable.prototype.size = function(){
    
    
    return this.count
  }


  //哈希表扩容
  HashTable.prototype.resize = function(newLimit){
    
    
    //1.保存旧的storage数组内容
    let oldStorage = this.storage

    //2.重置所有的属性
    this.storage = []
    this.count = 0
    this.limit = newLimit

    //3.遍历oldStorage中所有的bucket
    for (let i = 0; i < oldStorage.length; i++) {
    
    
      //3.1.取出对应的bucket
      const bucket = oldStorage[i];

      //3.2.判断bucket是否为null
      if (bucket == null) {
    
    
        continue
      }      

      //3.3.bucket中有数据,就取出数据重新插入
      for (let j = 0; j < bucket.length; j++) {
    
    
        const tuple = bucket[j];
        this.put(tuple[0], tuple[1])//插入数据的key和value
      }
    }
  }

  //判断传入的num是否质数
  HashTable.prototype.isPrime = function(num){
    
    
      if (num <= 1) {
    
    
        return false
      }
      //1.获取num的平方根:Math.sqrt(num)
      //2.循环判断
      for(var i = 2; i<= Math.sqrt(num); i++ ){
    
    
        if(num % i == 0){
    
    
          return false;
        }
      }
        return true;
    }

    //获取质数的方法
    HashTable.prototype.getPrime = function(num){
    
    
       //7*2=14,+1=15,+1=16,+1=17(质数)
      while (!this.isPrime(num)) {
    
    
        num++
      }
      return num
    }
 }

Guess you like

Origin blog.csdn.net/weixin_46862327/article/details/134176813