学习数据结构中的Hashing

70 阅读6分钟

数据结构中的哈希

散列是指使用一种算法将任何长度的输入转换为固定大小的字符串或数字的过程。在哈希算法中,其原理是使用一个哈希函数,将一个给定的键转换为一个较小的数字,并将这个小数字作为一个叫做哈希表的表的索引。

数据结构中的散列

我们使用哈希函数为输入生成一个哈希值,然后用生成的哈希值作为键在哈希表中存储元素。

Hash Table

哈希表。散列表是一个键值对的集合。当需要快速搜索或插入一个元素时,就会用到它。

散列函数中的操作。

  • Insert- T[ h(key)] = value;
    • 它计算哈希值,将其作为密钥,并将其值存储在哈希表中。
  • 删除- T[ h(key) ] = NULL。
    • 它计算哈希值,为该键重置存储在哈希表中的值。
  • 搜索- 返回T[ h(key)]。
    • 它计算哈希值,找到并返回该键在哈希表中存储的值。

哈希碰撞。当两个或更多的输入被映射到哈希表中使用的相同键时。例如:h("John") == h( "joe")

碰撞不能完全避免,但可以通过使用一个 "好的 "哈希函数和更大的表尺寸来最小化。

如果表的大小是一个质数,那么发生哈希碰撞的机会就会减少。

如何选择一个哈希函数

  • 一个有效的哈希函数应该被建立起来,使添加的项目的索引值在表中被平均分配。
  • 应该建立一个有效的碰撞解决技术,为一个键生成一个备用索引,这个键的哈希索引对应于哈希表中先前插入的位置。
  • 我们必须选择一种计算速度快的哈希算法。

一个好的哈希函数的特点

  • 均匀分布。用于分布在整个构建的表中。
  • 快速。散列的生成应该非常快,而且不应该产生任何相当大的开销。

碰撞哈希技术

  1. 开放式散列(分离式链路)。这是最常用的碰撞散列技术,使用Lined List实现。当任何两个或更多的元素在同一位置发生碰撞时,这些元素被链成一个单一的链接的列表,称为链。在此,我们将链接列表中的所有元素都链到同一槽位上进行散列。

让我们考虑一个简单的哈希函数的例子。

h(key) = key%table size

在一个大小为7的哈希表中

h(27) = 27%7 = 6

h(130) = 130%7 = 4

Hash Map Example

如果我们插入一个新的元素(18,"Saleema"),这也将进入第四个索引。

h(18) = 18%7 = 4

Hash Map Example;

对于单独的链,最坏的情况是所有的键都会得到相同的哈希值,并且会被插入到同一个链表中。我们可以通过使用一个好的散列函数来避免这种情况。

  1. 闭合哈希(开放寻址):在这种情况下,我们在哈希表中找到 "下一个 "空缺的桶,并将值存储在该桶中。

    1. 线性探查:我们线性地去找每一个下一个桶,看看它是否是空的。

      rehash(key) = (n+1)%tablesize

    2. 二次探查:我们前往第1,4,9...个桶,检查它们是否空着。

      rehash(key) = (n+ k<sup>2</sup> ) % tablesize

  2. 双重哈希:在这里,我们将哈希函数产生的密钥放到第二个哈希函数中。

    h2(key) != 0 and h2 != h1

负载系数:这是一个测量哈希表在其容量增加之前可能变得多满的方法。

哈希表的负载系数,T,被定义为。

  • N = T中的元素数 - 当前大小
  • M = T的大小 - 表的大小
  • e = N/M - 负载系数

一般来说,如果负载因子大于0.5,我们就会增加桶阵列的大小,并再次重新洗练所有的键值对。

Hashing是如何获得O(1)复杂度的?

鉴于上面的例子,人们会想,如果有几个项目映射到同一个地方,散列怎么可能是O(1)呢...

这个问题的解决方案是直接的。我们使用负载因子来确保每个块,例如,(在一个单独的链式策略中的链接列表),平均存储比负载因子少的最大数量的元素。另外,在实践中,这个负载因子是恒定的(一般是10或20)。因此,在10个或20个元素中搜索变得恒定。

如果一个区块中的平均项目数超过了负载因子,那么这些元素就会以更大的哈希表尺寸重新洗牌。

重新洗牌

当负载因子变得 "太高 "时(由阈值指定),碰撞将变得更加普遍,因此重洗是解决这个问题的办法。

  • 我们增加哈希表的大小,通常是将表的大小增加一倍。
  • 所有现有的项目都必须重新插入到新的加倍大小的哈希表中。

现在让我们深入研究一下代码。我将用代码实现我们到目前为止所学到的一切。

#include<iostream>
using namespace std;

class node{
    public:
    string name;
    int value;
    node* next;
    node(string key,int data){
        name=key;
        value=data;
        next=NULL;
    }
};

class hashmap{
    node** arr;
    int ts;
    int cs;

    int hashfn(string key){
        int ans=0;
        int mul=1;
        for(int i=0; key[i]!='\0';i++){
            ans =  (ans + ((key[i]/ts)*(mul%ts))%ts);
            mul *= 37;
            mul %=ts;
        }
        ans = ans %ts;
        return ans;
    }

    void reHash(){
        node** oldarr=arr;
        int oldts=ts;
        arr= new node*[2*ts];
        ts *= 2;
        cs=0;

        for(int i=0;i<ts;i++){
            arr[i]=NULL;
        }

        //insert in new table
        for(int i=0;i<oldts;i++){
            node* head = oldarr[i];
            while(head){
                insert(head->name,head->value);
                head=head->next;
            }
        }
        delete []oldarr;


    }

    public:
    hashmap(int s=7){
        arr = new node*[s];
        ts=s;
        cs=0;
        for(int i=0;i<s;i++){
            arr[i]=NULL;
        }

    }

    void insert(string key, int data){
        int i=hashfn(key);
        node* n=new node(key,data);
        n->next=arr[i];
        arr[i]=n;
        cs++;

        if(cs/(1.0*ts)  > 0.6){
            reHash();
        }

    }

    node* search(string key){
        int i=hashfn(key);
        node*head= arr[i];
        while(head){
            if(head->name==key){
               return head;
                break;
            }
            head=head->next;
        }
        if(head==NULL){
            cout<<"not exist";
        }
        return NULL;
    }

    void print(){
        for(int i=0;i<ts;i++){
            node* head= arr[i];
            while(head){
                cout<<head->name<<"-->"<<head->value;
                head=head->next;
                 cout<<endl;
            }

        }
    }

    int& operator[](string key){
        node* ans=search(key);
        if(ans){
            return ans->value;
        }
        else{
            int agrbagevalue;
            insert(key,agrbagevalue);
            ans= search(key);
            return ans->value;
        }

    }

    void Delete(string key){
        int i=hashfn(key);
        node* head= arr[i];
        node* trail=NULL;

            while(head){

                    //first
                    if(head->name==key && trail==NULL){
                        arr[i]=NULL;
                        delete head;
                    }
                    //in end
                    if(head->name==key && trail!=NULL && head->next==NULL){
                        trail->next=NULL;
                        delete head;
                    }
                    //mid
                    if(head->name==key && head->next!=NULL){
                        node*ptr =head;
                        head=head->next;
                        delete ptr;
                        arr[i]=head;

                    }
                    trail =head;
                    head=head->next;
            }
        return;
    }

};

int main(){
    hashmap h;
    h.insert("Alphonso_Mango",100);
    h.insert("Kiwi",150);
    h.insert("Banana",200);
    h.insert("WaterMelon",180);
    h.print();

    node* x=  h.search("Kiwi");
    cout<<"SEARCH RESULT"<<endl;
    cout<<x->name<<" "<<x->value;

}

=======

输出结果

Data Hashing Output

  • 在输出中,你可以看到我们已经将不同的键值对插入到我们所建立的自定义哈希表中。试着用这些值进行实验,你会发现有碰撞,如果有很多碰撞,也会重新洗牌。

  • 使用搜索功能,我们可以在O(1)的时间复杂度中找到键的值。

总结

在哈希表的帮助下,我们可以在O(1)时间内插入、搜索和删除,这是一个伟大的成就。哈希表被广泛用于使我们的代码更加有效和快速。下面是一些数据结构的问题,你可以尝试一下使用哈希表的方法。