网络编程之 哈希表原理讲解 来自老司机的源码

简介: 鉴于博主很久没由跟新过数据结构的内容了,所以博主打算给大家讲解一下哈希表的操作下面的内容来自于一位老司机 martin的源码,博主在这里借用一下,目的是突出哈希表的原理,明天博主就周末了,也能腾出时间来给上传自己的哈希表的应用。
鉴于博主很久没由跟新过数据结构的内容了,所以博主打算给大家讲解一下哈希表的操作

下面的内容来自于一位老司机 martin的源码,博主在这里借用一下,目的是突出哈希表的原理,明天博主就周末了,也能腾出时间来给上传自己的哈希表的应用。


这个是可以插入字符串的哈希表,一般的都是对数字的操作,所以这个的逼格是很高的!!!!(难点剖析放在最后)

#pragma once 
#define DEFAULT_SIZE 16 
/*哈希表元素定义*/
typedef struct _ListNode{
  struct _ListNode *next;
  int key;
  void *data;
}ListNode;
typedef ListNode *List;
typedef ListNode *Element;
/*哈希表结构定义*/
typedef struct _HashTable{
  int TableSize;
  List *Thelists;
}HashTable;
/*哈希函数*/
int Hash(void *key, int TableSize);
/*初始化哈希表*/
HashTable *InitHash(int TableSize);
/*哈希表插入*/
void Insert(HashTable *HashTable, int key,  void *value);
/*哈希表查找*/
Element Find(HashTable *HashTable, const int key);
/*哈希表销毁*/
void Destory(HashTable *HashTable);
/*哈希表元素中提取数据*/
void *Retrieve(Element e);
hash_table.cpp
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include"hash_table.h"
/*根据 key 计算索引,定位 Hash 桶的位置*/ 
int Hash(int key, int TableSize) { 
    return (key%TableSize); //返回索引值
}
/*初始化哈希表*/
HashTable *InitHash(int TableSize){ //传入 哈希桶的个数,在函数内部给哈希表分配空间,再将初始化好了的哈希表传出去。
  int i = 0;
  HashTable *hTable = NULL;
  if (TableSize <= 0) {  //如果用户恶意输入数字,那么我们就可以把哈希表的个数定为我们最初的 16个
    TableSize = DEFAULT_SIZE;
  }
  hTable = (HashTable *)malloc(sizeof(HashTable));
  if (NULL == hTable){    //如果地址分配失败
    printf("HashTable malloc error.\n");
    return NULL;
  }
  hTable->TableSize = TableSize;
  //为 Hash 桶分配内存空间,其为一个指针数组 
  hTable->Thelists = (List *)malloc(sizeof(List)*TableSize);  
      //hTable->Thelists其实就是可以来指向哈希表里面的与元素了
  if (NULL == hTable->Thelists){  //分配空间失败,打印错误,然后返回
    printf("HashTable malloc error\n");
    free(hTable);
    return NULL;
  }
  //为 Hash 桶对应的指针数组初始化链表节点 
  for (i = 0; i < TableSize; i++){
    hTable->Thelists[i] = (ListNode *)malloc(sizeof(ListNode));//也就是给头节点分配空间地,这样后续就能完成插入了
    if (NULL == hTable->Thelists[i]){   //如果分配失败,那我们就释放之前分配了的空间地址,以免造成内存泄漏
      printf("HashTable malloc error\n");
      free(hTable->Thelists);
      free(hTable);
      return NULL;
    } else
    {
      memset(hTable->Thelists[i], 0, sizeof(ListNode));   //将好小标指向的元素全部清空。主要是防止意外情况。
    }
  }
  return hTable;
}
Element Find(HashTable *HashTable, int key)
{
  int i = 0;  //目的接受调用HashTable函数返回的索引值
  List L = NULL;  //定位到(第几个哈希桶)指针数组里面的第几个头结点。
  Element e = NULL;  //指向定位到的(第几个哈希桶)的第一个节点(第一个节点是不包括头结点的哦)。
  i = Hash(key, HashTable->TableSize);
  L = HashTable->Thelists[i];
  e = L->next;
  while (e != NULL && e->key != key) //目的是为了遍历对应的哈希桶是否存在相同的key值。
    e = e->next;
  return e;   //返回两种情况,找到了就返回e对应的结构体,失败了就返回NULL
}
/*哈希表插入元素,元素为键值对*/
void Insert(HashTable *HashTable, int key, void *value)
{
  Element e = NULL, tmp = NULL;   //e用来接收查找后的e,tmp是用来插入的
  List L = NULL;  //定位到第几个哈希桶,和我们的find中的L的作用是一样的。
  e = Find(HashTable, key);   //现在就找到了对应的第几个哈希桶的第一个节点了。或者是NULL
  if (NULL == e){ //如果接收到的e为空的话我们就可以进行插入元素的操作了
    tmp = (Element)malloc(sizeof(ListNode));   // 因为我们接收到的是NULL所以非配空间为了插入
    if (NULL == tmp)
    {
      printf("malloc error\n");
      return;
    }
    L = HashTable->Thelists[Hash(key, HashTable->TableSize)];   //\定位到第几个哈希桶
    //经典的头插法
    tmp->data = value;
    tmp->key = key;
    tmp->next = L->next;
    L->next = tmp;
  } else
    printf("the key already exist\n");
}
/*哈希表删除元素,元素为键值对*/
void Delete(HashTable *HashTable, int key){ //key是你要删除的第几个位置的键值对
  Element e = NULL, last = NULL;  
  List L = NULL;
  int i = Hash(key, HashTable->TableSize);    //找到对应的i是第几个
  L = HashTable->Thelists[i]; //找到对应的第几个哈希桶
  last = L;   //让last 等于 对应的第几个哈希桶的头结点
  e = L->next;    //e指向对应的第几个哈希桶的第一个节点。
  while (e != NULL && e->key != key) {    //遍历对应的哈希桶的全部链表,结束条件是 找到了要删除的key或者是遍历完成
    last = e;   //last为e也就是以后要删除的结点的上一个结点
    e = e->next;    //指向后面的结点
  }
  if (e) {//如果键值对存在,那么就可以删除了。
    last->next = e->next;   
    delete(e);
  }
}
/*哈希表元素中提取数据*/
void *Retrieve(Element e)   //在元素e存在的情况下降找到的 e->data 返回出去
{
  return e ? e->data : NULL;  //三目运算符
}
/*销毁哈希表*/
void Destory(HashTable *HashTable)
{
  int i = 0;
  List L = NULL;
  Element cur = NULL, next = NULL;
  for (i = 0; i < HashTable->TableSize; i++)  //将所有的哈希桶都遍历完成
  {
    L = HashTable->Thelists[i];
    cur = L->next;
    while (cur != NULL) //将对应的链表全部销毁,跳出之后只有头结点没有销毁了
    {
      next = cur->next;
      free(cur);
      cur = next;
    }
    free(L);    //释放那一个头结点
  }   //完成之后所有的哈希桶全部销毁
  free(HashTable->Thelists);  //释放那个二级指针
  free(HashTable);    //释放哈希表。
}
int main(void)
{
  char  *elems[] = { "翠花","小芳","苍老师" };
  int i = 0;
  HashTable *HashTable;
  HashTable = InitHash(31);
  Insert(HashTable, 1, elems[0]);
  Insert(HashTable, 2, elems[1]);
  Insert(HashTable, 3, elems[2]);
  Delete(HashTable, 1);
  for (i = 0; i < 4; i++) {
    Element e = Find(HashTable, i);
    if (e) {
      printf("%s\n", (const char *)Retrieve(e));
    } else {
      printf("Not found [key:%d]\n", i);
    }
  }
  system("pause");
  return 0;
}

希望大家能好好的观看我的注释,相信一定能给你收获的。

如果觉得代码太长的,博主在这里给大家将模块分解了。大家也可以观看分解之后的代码,这样
压力会小一点。 
头文件就不用说了相信大家都能看明白,就只是声明和定义结构体的类型而已。


哈希函数

/*根据 key 计算索引,定位 Hash 桶的位置*/ 
int Hash(int key, int TableSize) { 
    return (key%TableSize); //返回索引值
}


哈希表的初始化

/*初始化哈希表*/
HashTable *InitHash(int TableSize){ //传入 哈希桶的个数,在函数内部给哈希表分配空间,再将初始化好了的哈希表传出去。
  int i = 0;
  HashTable *hTable = NULL;
  if (TableSize <= 0) {  //如果用户恶意输入数字,那么我们就可以把哈希表的个数定为我们最初的 16个
    TableSize = DEFAULT_SIZE;
  }
  hTable = (HashTable *)malloc(sizeof(HashTable));
  if (NULL == hTable){    //如果地址分配失败
    printf("HashTable malloc error.\n");
    return NULL;
  }
  hTable->TableSize = TableSize;
  //为 Hash 桶分配内存空间,其为一个指针数组 
  hTable->Thelists = (List *)malloc(sizeof(List)*TableSize);  
      //hTable->Thelists其实就是可以来指向哈希表里面的与元素了
  if (NULL == hTable->Thelists){  //分配空间失败,打印错误,然后返回
    printf("HashTable malloc error\n");
    free(hTable);
    return NULL;
  }
  //为 Hash 桶对应的指针数组初始化链表节点 
  for (i = 0; i < TableSize; i++){
    hTable->Thelists[i] = (ListNode *)malloc(sizeof(ListNode));//也就是给头节点分配空间地,这样后续就能完成插入了
    if (NULL == hTable->Thelists[i]){   //如果分配失败,那我们就释放之前分配了的空间地址,以免造成内存泄漏
      printf("HashTable malloc error\n");
      free(hTable->Thelists);
      free(hTable);
      return NULL;
    } else
    {
      memset(hTable->Thelists[i], 0, sizeof(ListNode));   //将好小标指向的元素全部清空。主要是防止意外情况。
    }
  }
  return hTable;
}


哈希表中插入元素:

/*哈希表插入元素,元素为键值对*/
void Insert(HashTable *HashTable, int key, void *value)
{
  Element e = NULL, tmp = NULL;   //e用来接收查找后的e,tmp是用来插入的
  List L = NULL;  //定位到第几个哈希桶,和我们的find中的L的作用是一样的。
  e = Find(HashTable, key);   //现在就找到了对应的第几个哈希桶的第一个节点了。或者是NULL
  if (NULL == e){ //如果接收到的e为空的话我们就可以进行插入元素的操作了
    tmp = (Element)malloc(sizeof(ListNode));   // 因为我们接收到的是NULL所以非配空间为了插入
    if (NULL == tmp)
    {
      printf("malloc error\n");
      return;
    }
    L = HashTable->Thelists[Hash(key, HashTable->TableSize)];   //\定位到第几个哈希桶
    //经典的头插法
    tmp->data = value;
    tmp->key = key;
    tmp->next = L->next;
    L->next = tmp;
  } else
    printf("the key already exist\n");
}

哈希表中查找元素:

Element Find(HashTable *HashTable, int key)
{
  int i = 0;  //目的接受调用HashTable函数返回的索引值
  List L = NULL;  //定位到(第几个哈希桶)指针数组里面的第几个头结点。
  Element e = NULL;  //指向定位到的(第几个哈希桶)的第一个节点(第一个节点是不包括头结点的哦)。
  i = Hash(key, HashTable->TableSize);
  L = HashTable->Thelists[i];
  e = L->next;
  while (e != NULL && e->key != key) //目的是为了遍历对应的哈希桶是否存在相同的key值。
    e = e->next;
  return e;   //返回两种情况,找到了就返回e对应的结构体,失败了就返回NULL
}


哈希表中删除元素:

/*哈希表删除元素,元素为键值对*/
void Delete(HashTable *HashTable, int key){ //key是你要删除的第几个位置的键值对
  Element e = NULL, last = NULL;  
  List L = NULL;
  int i = Hash(key, HashTable->TableSize);    //找到对应的i是第几个
  L = HashTable->Thelists[i]; //找到对应的第几个哈希桶
  last = L;   //让last 等于 对应的第几个哈希桶的头结点
  e = L->next;    //e指向对应的第几个哈希桶的第一个节点。
  while (e != NULL && e->key != key) {    //遍历对应的哈希桶的全部链表,结束条件是 找到了要删除的key或者是遍历完成
    last = e;   //last为e也就是以后要删除的结点的上一个结点
    e = e->next;    //指向后面的结点
  }
  if (e) {//如果键值对存在,那么就可以删除了。
    last->next = e->next;   
    delete(e);
  }
}

哈希表中提取元素以及销毁哈希表:

/*哈希表元素中提取数据*/
void *Retrieve(Element e)   //在元素e存在的情况下降找到的 e->data 返回出去
{
  return e ? e->data : NULL;  //三目运算符
}
/*销毁哈希表*/
void Destory(HashTable *HashTable)
{
  int i = 0;
  List L = NULL;
  Element cur = NULL, next = NULL;
  for (i = 0; i < HashTable->TableSize; i++)  //将所有的哈希桶都遍历完成
  {
    L = HashTable->Thelists[i];
    cur = L->next;
    while (cur != NULL) //将对应的链表全部销毁,跳出之后只有头结点没有销毁了
    {
      next = cur->next;
      free(cur);
      cur = next;
    }
    free(L);    //释放那一个头结点
  }   //完成之后所有的哈希桶全部销毁
  free(HashTable->Thelists);  //释放那个二级指针
  free(HashTable);    //释放哈希表。
}

博主认为比较难的就是:

指针数组里面存放的是每个哈希桶的头结点,通过求余来锁定要查找或删除的值在哪一个哈希桶里(认为就是这个最难理解,理解了之后其实哈希就不难了)。其他的就是链表的操作了。


明天上传自己敲得代码


目录
相关文章
|
20天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的奥秘:探索神经网络的核心原理
本文将深入浅出地介绍深度学习的基本概念,包括神经网络的结构、工作原理以及训练过程。我们将从最初的感知机模型出发,逐步深入到现代复杂的深度网络架构,并探讨如何通过反向传播算法优化网络权重。文章旨在为初学者提供一个清晰的深度学习入门指南,同时为有经验的研究者回顾和巩固基础知识。
38 11
|
1月前
|
机器学习/深度学习 存储 算法
回声状态网络(Echo State Networks,ESN)详细原理讲解及Python代码实现
本文详细介绍了回声状态网络(Echo State Networks, ESN)的基本概念、优点、缺点、储层计算范式,并提供了ESN的Python代码实现,包括不考虑和考虑超参数的两种ESN实现方式,以及使用ESN进行时间序列预测的示例。
74 4
回声状态网络(Echo State Networks,ESN)详细原理讲解及Python代码实现
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的自适应神经网络:原理与应用
【8月更文挑战第14天】在深度学习领域,自适应神经网络作为一种新兴技术,正逐渐改变我们处理数据和解决问题的方式。这种网络通过动态调整其结构和参数来适应输入数据的分布和特征,从而在无需人工干预的情况下实现最优性能。本文将深入探讨自适应神经网络的工作原理、关键技术及其在多个领域的实际应用,旨在为读者提供一个全面的视角,理解这一技术如何推动深度学习向更高效、更智能的方向发展。
|
18天前
|
机器学习/深度学习 人工智能 自然语言处理
深度剖析深度神经网络(DNN):原理、实现与应用
本文详细介绍了深度神经网络(DNN)的基本原理、核心算法及其具体操作步骤。DNN作为一种重要的人工智能工具,通过多层次的特征学习和权重调节,实现了复杂任务的高效解决。文章通过理论讲解与代码演示相结合的方式,帮助读者理解DNN的工作机制及实际应用。
|
15天前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【9月更文挑战第14天】网络协议是机器间交流的约定格式,确保信息准确传达。主要模型有OSI七层与TCP/IP模型,通过分层简化复杂网络环境。IP地址全局定位设备,MAC地址则在本地网络中定位。网络分层后,数据包层层封装,经由不同层次协议处理,最终通过Socket系统调用在应用层解析和响应。
|
16天前
|
网络协议 网络架构 数据格式
TCP/IP基础:工作原理、协议栈与网络层
TCP/IP(传输控制协议/互联网协议)是互联网通信的基础协议,支持数据传输和网络连接。本文详细阐述了其工作原理、协议栈构成及网络层功能。TCP/IP采用客户端/服务器模型,通过四个层次——应用层、传输层、网络层和数据链路层,确保数据可靠传输。网络层负责IP寻址、路由选择、分片重组及数据包传输,是TCP/IP的核心部分。理解TCP/IP有助于深入掌握互联网底层机制。
69 2
|
1月前
|
缓存 网络协议 算法
网络编程原理
网络编程原理
|
1月前
|
网络协议 算法 安全
网络原理问题
网络原理问题
|
1月前
|
网络协议 网络安全 数据安全/隐私保护
网工老司机最常用的11个网络命令,看你用过几个?
网工老司机最常用的11个网络命令,看你用过几个?
|
1月前
|
机器学习/深度学习 人工智能 算法
深度学习的奥秘:探索神经网络的核心原理
深度学习,一个听起来既神秘又充满魔力的词汇,它如同一扇通往未知世界的大门,背后隐藏着无尽的智慧与可能。本文将以一种通俗易懂的方式,带领读者走进深度学习的世界,探索那些构成神经网络核心的基本原理。我们将从最初的感知机模型出发,逐步深入到复杂的多层网络结构,揭示数据如何在这些网络中流动、变化,最终实现智能决策的过程。通过这篇文章,你将了解到深度学习不仅仅是技术的堆砌,更是对自然界智慧的一种模仿与致敬。
48 1