哈希表的创建(C++)

hash.h

/**************************************************************************
Description:
哈希表说白了就是有一个数组,然后数组的每个元素都装了一条链表
哈希查找复杂度为O(1)
可用于数据存储,数据查询,字符串比较等
**************************************************************************/
#pragma once
#define MAXSIZE 128
 
//链表 节点的数据结构
typedef struct _node_t
{
 
    int key;
    const void* value;
    _node_t* next;
 
}node_t;
 
typedef node_t* pNode;
typedef struct _hash_t
{
    int nTotalNum;		// 总数量
    pNode* pArray;		// 定义链表类型的数组
}hash_t;
 

class MyHash
{
public:
   explicit MyHash();
   ~MyHash();

   static MyHash* getInstance();
   bool getStatus();
   int getHashIndex(int nKey);//简单取模法
   int getBKDRHashIndex(const char *str, int nTotal);//BKDR

   void insertHash(int nKey, const void* value);
   void deleteHash(int nKey);
   pNode searchNodeByKey(int nKey);
   const void* getValue(pNode node);
   static int m_nTotal;

private:
   hash_t* m_pHash;
   bool m_bStatus;
   static MyHash* m_pInstance;
};

hash.cpp

/**************************************************************************
Description:
哈希表说白了就是有一个数组,然后数组的每个元素都装了一条链表
哈希查找复杂度为O(1)
可用于数据存储,数据查询,字符串比较等
**************************************************************************/
#include "hash.h"
#include <iostream>
using namespace std;
 
int MyHash::m_nTotal = MAXSIZE;
MyHash::MyHash()
{
    m_bStatus = true;
    m_pHash = new hash_t;
    if(!m_pHash) //判断分配内存是否成功
    {
        m_bStatus = false;
    }
    else
    {
        m_pHash->nTotalNum = MyHash::m_nTotal;
        m_pHash->pArray = new pNode[m_pHash->nTotalNum];
        if(!m_pHash->pArray) //判断指针数组是否分配内存成功
        {
            delete m_pHash;
            m_bStatus = false;
        }
        else
        {
            for(int i = 0; i < m_pHash->nTotalNum; i++)
            {
                m_pHash->pArray[i] = new node_t;
                if(!m_pHash->pArray[i]) //判断节点是否分配内存成功
                {
                    delete[] m_pHash->pArray;
                    delete m_pHash;
                    m_bStatus = false;
                }
                else
                {
                    memset(m_pHash->pArray[i], 0, sizeof(node_t));//初始化节点都置为0
                }
            }
        }
    }
}

MyHash::~MyHash()
{
    pNode pHead = NULL, pCur = NULL, pTmp = NULL;
    for(int i = 0; i < m_pHash->nTotalNum; i++)//循环销毁每一个链表
    {
        pHead = m_pHash->pArray[i];//获取链表的头节点
        pCur = pHead->next;
        while(pCur != NULL)  //循环销毁链表中内容
        {
            pTmp = pCur;
            pCur = pCur->next;
            delete pTmp;
        }
        delete pHead; //销毁链表头部节点
    }
    delete[] m_pHash->pArray;
    delete m_pHash;
}

//单例
MyHash* MyHash::m_pInstance = nullptr;
MyHash* MyHash::getInstance()
{
    if(m_pInstance == nullptr)
    {
        m_pInstance = new MyHash;
    }
    return m_pInstance;
}

//获取哈希表创建状态
bool MyHash::getStatus()
{
    return m_bStatus;
}

//简单取模法
//散列函数的作用就是用来提供节点应该随机分布在哪个链表当中,用来确定添加的位置
int MyHash::getHashIndex(int nKey)
{
    return nKey % m_pHash->nTotalNum;
}

//BKDR 最出众的一种字符串散列函数
int MyHash::getBKDRHashIndex(const char* str, int nTotal)
{
    unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
    unsigned int key = 0;
    while (*str)
    {
        key = key * seed + (*str++);//每一位乘一个系数
    }
    /*0x7FFFFFFF是一个用16进制表示的整型,是整型里面的最大值,hash与其按位与得到一个正数,
    */
    return (key & 0x7FFFFFFF)%nTotal;
}

//插入节点
void MyHash::insertHash(int nKey, const void *value)
{
    pNode pHead = NULL, pCur = NULL, pTmp = NULL;

    //判断key值,不能有相同的key值
    pCur = searchNodeByKey(nKey);
    if(pCur == NULL)
    {
        pTmp = new node_t;
        if(!pTmp)
        {
            cout << "Insert node failed.(mem alloc fail)" << endl;
            return;
        }
        pHead = m_pHash->pArray[getHashIndex(nKey)];

        //头插法
        pTmp->next = pHead->next;
        pTmp->key = nKey;
        pTmp->value = value;
        pHead->next = pTmp;
    }
    else
        cout << "key exist." << endl;
}

//删除节点
void MyHash::deleteHash(int nKey)
{
    pNode pHead = NULL, pCur = NULL, pPre = NULL;

    pHead = m_pHash->pArray[getHashIndex(nKey)];
    pCur = pHead->next; //获取第一个有效节点,因为头节点是空的,所以需要next
    pPre = pHead;
    while(pCur != NULL && pCur->key != nKey)
    {
        pPre = pCur; //记录上一次的节点,方便把链表重新连接起来
        pCur = pCur->next;
    }
    if(pCur) //重新连接链表
    {
        pPre->next  = pCur->next;
        delete pCur;
    }
}

//根据key值查找节点
pNode MyHash::searchNodeByKey(int nKey)
{
    pNode pHead = NULL, pCur = NULL;
    pHead = m_pHash->pArray[getHashIndex(nKey)];
    pCur = pHead->next;
    while(pCur != NULL && pCur->key != nKey)
    {
        pCur = pCur->next;
    }
    return pCur;
}

//获取节点的值
const void *MyHash::getValue(pNode node)
{
    return node ? node->value : NULL;
}

main.cpp

#include "hash.h"
#include <iostream>
using namespace std;

#define MaxKey 1000
int main(int argc, char *argv[])
{
    //哈希表存储数据,操作数据
    const char* test_arr[] = {"AAA","BBB","CCC","DDD","EEE","FFF"};

    MyHash::m_nTotal = 10;
    MyHash* hash = MyHash::getInstance();

    //插入节点
    for (int i = 0; i < 6; ++i)
    {
        hash->insertHash(i+1, test_arr[i]);
    }

    //输出内容
    for (int i = 0; i < MyHash::m_nTotal; ++i)
    {
        pNode node = hash->searchNodeByKey(i);
        if(node)
            cout << "Key: " << i + 1 << ", Value: " << (const char*)(hash->getValue(node)) << endl;
        else
            cout << "Not find node, key:" << i + 1 << endl;
    }

    //删除key为2的节点
    hash->deleteHash(2);

    //再次输出内容
    for (int i = 0; i < MyHash::m_nTotal; ++i)
    {
        pNode node = hash->searchNodeByKey(i);
        if (node)
            cout << "Key: " << i + 1 << ", Value: " << (const char*)(hash->getValue(node)) << endl;
        else {
            cout << "Not find node, key:" << i + 1 <<  endl;
        }
    }

    //BKDR Hash 简单使用
    const char* pStr[4] = {"how","old","are","you" };
    const char* pHash_table[MaxKey] = {NULL};
    for (int i = 0; i < 4; i++)
    {
        cout << "i:" << i << ",str:" << pStr[i] << ", key:" << hash->getBKDRHashIndex(pStr[i], MaxKey) <<  endl;
        pHash_table[hash->getBKDRHashIndex(pStr[i], MaxKey)] = pStr[i];
    }

    return 0;
}

其他补充

哈希算法的应用

 1)加密 (MD5)
 2)唯一标识
 3)数据校验 保存文件时对文件去哈希值
 4)散列表 (根据键(Key)而直接访问在内存存储位置的数据结构) 散列函数(hash(key) )
 5)负载均衡  映射表
 6)分布式存储
   将数据分布在多台机器上,通过一致性哈希算法,保持了各个机器上数据数量的均衡
 7)区域链

解决哈希冲突

哈希冲突就是key1=key2,而hash(key1) = hash(key2)的情况
链地址法
再哈希法( 提供多个哈希函数,如果第一个哈希函数计算出来的key的哈希值冲突了,则使用第二个哈希函数计算key的哈希值)
优点:
不易产生聚集
缺点:
增加了计算时间

建立公共溢出区 (将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表)
开放定址法(当关键字key的哈希地址p =H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,若p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中)
         4.1 线性探测再散列
顺序查看下一个单元,直到找出一个空单元或查遍全表
di=1,2,3,…,m-1
  4.2 二次(平方)探测再散列
在表的左右进行跳跃式探测,直到找出一个空单元或查遍全表
di=1^2,-1^2,2^2,-2^2,…,k^2,-k^2 ( k<=m/2 )
  4.3 伪随机探测再散列
建立一个伪随机数发生器,并给一个随机数作为起点
di=伪随机数序列。具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。
例如,已知哈希表长度m=11,哈希函数为:H(key)= key % 11,则H(47)=3,H(26)=4,H(60)=5,假设下一个关键字为69,则H(69)=3,与47冲突。

如果用线性探测再散列处理冲突,下一个哈希地址为H1=(3 + 1)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 + 2)% 11 = 5,还是冲突,继续找下一个哈希地址为H3=(3 + 3)% 11 = 6,此时不再冲突,将69填入5号单元。

如果用二次探测再散列处理冲突,下一个哈希地址为H1=(3 + 12)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 - 12)% 11 = 2,此时不再冲突,将69填入2号单元。

如果用伪随机探测再散列处理冲突,且伪随机数序列为:2,5,9,……..,则下一个哈希地址为H1=(3 + 2)% 11 = 5,仍然冲突,再找下一个哈希地址为H2=(3 + 5)% 11 = 8,此时不再冲突,将69填入8号单元。

优点:
容易序列化
若可预知数据总数,可以创建完美哈希数列
缺点:
1.占空间很大。(开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间)
2.删除节点很麻烦。不能简单地将被删结点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。																																
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅笑一斤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值