zoukankan      html  css  js  c++  java
  • 【数据结构】非常有用的hash表

        这篇博客的目的是让尚未学会hash表的朋友们对hash表有一个直观的理解,并且能根据本文定义出属于自己的第一个hash表,但算不上研究文,没有深究概念和成功案例。    
        什么是hash表?
        hash表也叫做散列表,是一种通过键值快速访问数据的结构,hash表有两种常见的定义形式:数组、数组和链表的结合。
        理解hash表的关键:
        1.散列法
            将字符组成的字符和字符串转换为固定长度的数值和索引值的方法,通过更短的hash值进行搜索比用原值搜索更快,通常用于数据库建立索引或者加解密。
        2.装填因子
            设m和n分别表示表长和表中填入的节点数,将α=n/m定义为散列表的装填因子,装填因子越大,越容易冲突。
        3.散列函数
            压缩待处理的键值,降低空间开销。
        4.冲突
            两个不同的键值具有同一个散列函数值,因而映射到散列表的同一位置,称为冲突或碰撞,冲突的两个键值称为同义词。
            冲突与散列函数有关,也和表的装填因子有关,hash表在即将填满时冲突几率提高,性能下降严重,但整体是一种极其高效的算法。
            hash表基本上无法避免冲突。
        5.处理冲突
            如果由键值得到的散列函数值(以后用hash地址称呼)已经存有记录,则继续寻找下一个空的hash地址。
            常见的处理冲突方法有开放寻址法、再散列法、拉链法、建立公共溢出区。
        以拉链法定义hash表为例:
        #include<stdio.h>
        #include<stdlib.h>
        #include<string.h>
        #include<Windows.h>
        using namespace std;
        //用于避免冲突的链表,同义键值插入链表
        //散列法中装填因子可以大于1,即待插入表元素总数可以大于表长度,但通常建议装填因子<=1,可以最大限度通过键值直接映射hash表
        struct Node
        {
    int data;
    struct Node* next;
        };
        //hash表主体,上面的链表是用来辅助解决冲突的
        struct Hash_Table
        {
    Node* Value[100];
        };
        //创建hash表
        Hash_Table* CreateHashTable()
        {
    Hash_Table* ptHash = (Hash_Table*)malloc(sizeof(Hash_Table));
    memset(ptHash, 0, sizeof(Hash_Table));
    return ptHash;
        }
        //在Hash表中寻找数据
        //hash法为除留余数法
        Node* FindHashData(Hash_Table* pHashTbl, int data)
        {
    Node* pNode;
    if (NULL == pHashTbl)
    return NULL;
    if (NULL == (pNode = pHashTbl->Value[data % 100]))    //该hash地址尚未插入数据,data%100就是此处应用的hash法
    return NULL;
    //遍历该hash地址指向的单链表的数据,如果键值等于hash表中存储的键值数据,匹配到就返回节点。
    while (pNode)
    {
    if (data = pNode->data)
    return pNode;
    pNode = pNode->next;
    }
    return NULL;
        }
        //在Hash表中插入数据
        BOOL InsertDataIntoHashTable(Hash_Table* pHashTbl, int data)
        {
    Node* pNode;
    if (NULL == pHashTbl)
    return NULL;
    if (NULL == pHashTbl->Value[data % 100])    //该节点尚未插入数据
    {
    pNode = (Node*)malloc(sizeof(Node));
    pNode->data = data;
    pNode->next = NULL;
    pHashTbl->Value[data % 100] = pNode;
    return TRUE;
    }
    //如果该键值已经插入hash表则插入失败,hash表存在同义键值,但不保存重复键值的数据
    if (NULL != FindHashData(pHashTbl, data))
    return FALSE;
    pNode = pHashTbl->Value[data % 100];
    while (NULL != pNode)
    pNode = pNode->next;
    //插入hash地址指向链表的末尾
    pNode->next = (Node*)malloc(sizeof(Node));
    pNode->next->data = data;
    pNode->next->next = NULL;
    return TRUE;
        }
        //从hash表中删除数据
        BOOL DeleteDataFromHashTable(Hash_Table* pHashTbl, int data)
        {
    Node* pNode,*pHead;
    if (NULL == pHashTbl)
    return FALSE;
    if (NULL == pHashTbl->Value[data % 100])
    return FALSE;
    if (NULL == (pNode = FindHashData(pHashTbl, data)))
    return FALSE;
    //如果查找到的hash节点是hash地址链表的首元素,重定向指针并删除。
    if (pNode == pHashTbl->Value[data % 100])
    {
    pHashTbl->Value[data % 100]->next = pNode->next;
    free(pNode);
    return TRUE;
    }
    //如果查找到的hash节点不是hash地址链表的首元素,定位到pNode的上一个节点后重定向指针并删除。
    pHead = pHashTbl->Value[data % 100];
    while (pHead->next != pNode)
    pHead = pHead->next;
    pHead->next = pNode->next;
    free(pHead);
    return TRUE;
        }
        上面的例子是读过一位前辈的例子后模仿的,前辈的例子已经非常精炼,很难有修改的地方,就加了一些注释方便大家理解,下面是学习开放定址法后自己写的一个例子,请大家指教。

        //依据hash处理冲突的开放寻址法

        //开放寻址法有三种探查技术:这里用的线性探测再散列方法
        typedef enum _USE_STATUS {
    STATUS_EMPTY = 0,
    STATUS_NORMAL_USE = 1,
    STATUS_DELETED = 2
        }USE_STATUS;
        struct HASH_DATA
        {
    int                  keyvalue;
    USE_STATUS  use;
        };
        struct HASH_TABLE
        {
    HASH_DATA data[100];
        };
        //定义哈希函数,又称散列函数
        int hash_func(int key)
        {
    return key%10;
        };
        //定义处理冲突的增量序列,线性探测再散列方法增量
        //线性探测的缺点:1.处理溢出需另编程序。2.删除工作困难,删除元素的时候需要将单元打上删除标记,不能直接设置元素为空,否则影响后续探测。
        //3.处理不确定的关键字域时很容易产生堆聚现象,堆聚具有一旦堆聚就更加容易堆聚的特点。
        int hash_di(int val)
        {
    return val;
        };
        HASH_TABLE* CreateHashTable()
        {
    HASH_TABLE* hash = new HASH_TABLE;
    memset(hash, 0, sizeof(HASH_TABLE));
    return hash;
        };
        //若是当前探查的单元为空,表示查找失败
        //若探查到T[d-1]仍然没有查找到,表示查找失败
        HASH_DATA* FindHashTable(HASH_TABLE* head, int key)
        {
    for(int i = 0; i < 100; i++)
    {
    if(head->data[(hash_func(key)+hash_di(i))%100].keyvalue == key && STATUS_NORMAL_USE == head->data[(hash_func(key)+hash_di(i))%100].use)
    {
    return &(head->data[hash_func(key)+i]);
    }
    if(STATUS_EMPTY == head->data[(hash_func(key)+hash_di(i))%100].use)
    return NULL;
    }
    return NULL;
        }
        //若是当前探查的单元中含有key,则插入失败
        //找到空元素或者已删除的元素就插入其中
        BOOL InsertHashNode(HASH_TABLE* head, int key)
        {
    for(int i = 0; i < 100; i++)
    {
    if(head->data[(hash_func(key)+hash_di(i))%100].use == STATUS_DELETED || head->data[(hash_func(key)+hash_di(i))%100].use == STATUS_EMPTY)
    {
    head->data[(hash_func(key)+hash_di(i))%100].keyvalue = key;
    head->data[(hash_func(key)+hash_di(i))%100].use = STATUS_NORMAL_USE;
    return TRUE;
    }
    }
    return FALSE;
        }
        //若是探查T[d-1]的时候仍然未找到包含key的单元,则删除失败
        //线性探查法找到待删除的元素时只能标记为已删除
        //当找到为空的元素仍然没有找到对应的key,则删除失败
        BOOL DeleteHashNode(HASH_TABLE* head, int key)
        {
    for(int i = 0; i < 100; i++)
    {
    if(head->data[(hash_func(key)+hash_di(i))%100].keyvalue == key && STATUS_NORMAL_USE == head->data[(hash_func(key)+hash_di(i))%100].use)
    {
    head->data[(hash_func(key)+hash_di(i))%100].use = STATUS_DELETED;
    return TRUE;
    }
    else if(STATUS_EMPTY == head->data[(hash_func(key)+hash_di(i))%100].use)
    {
    return FALSE;
    }
    }
    return FALSE;
        }
  • 相关阅读:
    自定义key解决zabbix端口监听取值不准确的问题
    Redis——主从同步原理
    Leetcode 24——Swap Nodes in Pairs
    Struts2——第一个helloworld页面
    Leetcode 15——3Sum
    Leetcode 27——Remove Element
    C#简单入门
    Leetcode 12——Integer to Roman
    Leetcode 6——ZigZag Conversion
    eclipse如何debug调试jdk源码(任何源码)并显示局部变量
  • 原文地址:https://www.cnblogs.com/learn-my-life/p/3726754.html
Copyright © 2011-2022 走看看