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

简介: 鉴于博主很久没由跟新过数据结构的内容了,所以博主打算给大家讲解一下哈希表的操作下面的内容来自于一位老司机 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);    //释放哈希表。
}

博主认为比较难的就是:

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


明天上传自己敲得代码


目录
相关文章
|
27天前
|
网络协议 安全 5G
网络与通信原理
【10月更文挑战第14天】网络与通信原理涉及众多方面的知识,从信号处理到网络协议,从有线通信到无线通信,从差错控制到通信安全等。深入理解这些原理对于设计、构建和维护各种通信系统至关重要。随着技术的不断发展,网络与通信原理也在不断演进和完善,为我们的生活和工作带来了更多的便利和创新。
64 3
|
2月前
|
并行计算 安全 网络协议
探索未来网络:量子互联网的原理与应用
本文深入探讨了量子互联网的基本概念、技术原理及其潜在应用。通过对量子纠缠、量子叠加和量子隐形传态等核心概念的解释,文章展示了量子互联网如何利用量子力学特性来实现超高速、超高安全性的通信。此外,还讨论了量子互联网在金融、医疗、国防等领域的应用前景,以及当前面临的技术挑战和未来的发展方向。
72 2
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的奥秘:探索神经网络的核心原理
本文将深入浅出地介绍深度学习的基本概念,包括神经网络的结构、工作原理以及训练过程。我们将从最初的感知机模型出发,逐步深入到现代复杂的深度网络架构,并探讨如何通过反向传播算法优化网络权重。文章旨在为初学者提供一个清晰的深度学习入门指南,同时为有经验的研究者回顾和巩固基础知识。
73 11
|
9天前
|
运维 物联网 网络虚拟化
网络功能虚拟化(NFV):定义、原理及应用前景
网络功能虚拟化(NFV):定义、原理及应用前景
24 3
|
20天前
|
网络协议 安全 算法
网络空间安全之一个WH的超前沿全栈技术深入学习之路(9):WireShark 简介和抓包原理及实战过程一条龙全线分析——就怕你学成黑客啦!
实战:WireShark 抓包及快速定位数据包技巧、使用 WireShark 对常用协议抓包并分析原理 、WireShark 抓包解决服务器被黑上不了网等具体操作详解步骤;精典图示举例说明、注意点及常见报错问题所对应的解决方法IKUN和I原们你这要是学不会我直接退出江湖;好吧!!!
网络空间安全之一个WH的超前沿全栈技术深入学习之路(9):WireShark 简介和抓包原理及实战过程一条龙全线分析——就怕你学成黑客啦!
|
1月前
|
机器学习/深度学习 人工智能 监控
深入理解深度学习中的卷积神经网络(CNN):从原理到实践
【10月更文挑战第14天】深入理解深度学习中的卷积神经网络(CNN):从原理到实践
84 1
|
1月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
1月前
|
存储 安全 算法
网络安全与信息安全:构建数字世界的防线在数字化浪潮席卷全球的今天,网络安全与信息安全已成为维系现代社会正常运转的关键支柱。本文旨在深入探讨网络安全漏洞的成因与影响,剖析加密技术的原理与应用,并强调提升公众安全意识的重要性。通过这些综合性的知识分享,我们期望为读者提供一个全面而深刻的网络安全视角,助力个人与企业在数字时代中稳健前行。
本文聚焦网络安全与信息安全领域,详细阐述了网络安全漏洞的潜在威胁、加密技术的强大防护作用以及安全意识培养的紧迫性。通过对真实案例的分析,文章揭示了网络攻击的多样性和复杂性,强调了构建全方位、多层次防御体系的必要性。同时,结合当前技术发展趋势,展望了未来网络安全领域的新挑战与新机遇,呼吁社会各界共同努力,共筑数字世界的安全防线。
|
1月前
|
存储 安全 自动驾驶
探索未来网络:量子互联网的原理与应用
【10月更文挑战第2天】 本文旨在探讨量子互联网的基本原理、技术实现及其在通讯领域的革命性应用前景。量子互联网利用量子力学原理,如量子叠加和量子纠缠,来传输信息,有望大幅提升通信的安全性和速度。通过详细阐述量子密钥分发(QKD)、量子纠缠交换和量子中继等关键技术,本文揭示了量子互联网对未来信息社会的潜在影响。
|
20天前
|
网络协议 安全 算法
网络空间安全之一个WH的超前沿全栈技术深入学习之路(9-2):WireShark 简介和抓包原理及实战过程一条龙全线分析——就怕你学成黑客啦!
实战:WireShark 抓包及快速定位数据包技巧、使用 WireShark 对常用协议抓包并分析原理 、WireShark 抓包解决服务器被黑上不了网等具体操作详解步骤;精典图示举例说明、注意点及常见报错问题所对应的解决方法IKUN和I原们你这要是学不会我直接退出江湖;好吧!!!