哈希表算法实现

来源:互联网 发布:2016淘宝c店越来越难做 编辑:程序博客网 时间:2024/06/05 02:46

1、什么是Hash?

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

HASH主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128位的编码,这些编码值叫做HASH值. 也可以说,hash就是找到一种数据内容和数据存放地址之间的映射关系。

数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法——拉链法,我们可以理解为“链表的数组”,如图:

这里写图片描述

左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。

元素特征转变为数组下标的方法就是散列法。散列法当然不止一种,下面列出三种比较常用的:

1,除法散列法
最直观的一种,上图使用的就是这种散列法,公式:
index = value % 16
学过汇编的都知道,求模数其实是通过一个除法运算得到的,所以叫“除法散列法”。

2,平方散列法
求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式:
index = (value * value) >> 28 (右移,除以2^28。记法:左移变大,是乘。右移变小,是除。)
如果数值分配比较均匀的话这种方法能得到不错的结果,但我上面画的那个图的各个元素的值算出来的index都是0——非常失败。也许你还有个问题,value如果很大,value * value不会溢出吗?答案是会的,但我们这个乘法不关心溢出,因为我们根本不是为了获取相乘结果,而是为了获取index。

3,斐波那契(Fibonacci)散列法

平方散列法的缺点是显而易见的,所以我们能不能找出一个理想的乘数,而不是拿value本身当作乘数呢?答案是肯定的。

1,对于16位整数而言,这个乘数是40503
2,对于32位整数而言,这个乘数是2654435769
3,对于64位整数而言,这个乘数是11400714819323198485

这几个“理想乘数”是如何得出来的呢?这跟一个法则有关,叫黄金分割法则,而描述黄金分割法则的最经典表达式无疑就是著名的斐波那契数列,即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946,…。另外,斐波那契数列的值和太阳系八大行星的轨道半径的比例出奇吻合。

对我们常见的32位整数而言,公式:
index = (value * 2654435769) >> 28

如果用这种斐波那契散列法的话,那上面的图就变成这样了:

这里写图片描述

很明显,用斐波那契散列法调整之后要比原来的取摸散列法好很多。

上文取自:
http://blog.csdn.net/v_JULY_v/article/details/6256463

2、哈希表算法实现

本程序建立的哈希表示意图:

这里写图片描述

/********************************************************************* *                          哈希表算法实现 *                       (c)copyright 2013,jdh *                         All Right Reserved *文件名:main.c *程序员:jdh **********************************************************************/  #include <stdio.h>  #include <stdlib.h>  /********************************************************************* *                           宏定义 **********************************************************************/  /********************************************************************* *                           数据类型重定义 **********************************************************************/  #define uint8_t unsigned char  #define uint16_t unsigned short  #define uint32_t unsigned long  /********************************************************************* *                           哈希表长度 **********************************************************************/  #define HASH_TABLE_LEN  100  /********************************************************************* *                           数据结构 **********************************************************************/  //链表节点  typedef struct _Link_Node    {        uint16_t id;      uint16_t data;      struct _Link_Node *next;    }Link_Node,*Link_Node_Ptr;   //哈希表头  typedef struct _Hash_Header    {        struct _Link_Node *next;    }Hash_Header,*Hash_Header_Ptr;  /********************************************************************* *                           全局变量 **********************************************************************/  //哈希表  Hash_Header_Ptr Hash_Table[HASH_TABLE_LEN];  /********************************************************************* *                           函数 **********************************************************************/  /********************************************************************* *                           哈希表函数 *说明: *1.用哈希函数生成id对应的哈希表中的位置 输入:id 返回:位置 **********************************************************************/  uint8_t hash_func(uint16_t id)  {      uint8_t pos = 0;      pos = id % HASH_TABLE_LEN;      return pos;  }  /********************************************************************* *                           初始化节点 *返回:结点指针 **********************************************************************/  Link_Node_Ptr init_link_node(void)  {      Link_Node_Ptr node;      //申请节点      node = (Link_Node_Ptr) malloc(sizeof(Link_Node));      //初始化长度为0      node->next = NULL;      return node;  }  /********************************************************************* *                           初始化哈希表头结点 *返回哈希表头结点指针 **********************************************************************/  Hash_Header_Ptr init_hash_header_node(void)  {      Hash_Header_Ptr node;      //申请节点      node = (Hash_Header_Ptr) malloc(sizeof(Hash_Header));      //初始化长度为0      node->next = NULL;      return node;  }  /********************************************************************* *                           哈希表初始化 *说明: *1.初始化哈希表Hash_Table *2.哈希表长度最大不能超过256 **********************************************************************/  void init_hash_table(void)  {      uint8_t i = 0;      for (i = 0;i < HASH_TABLE_LEN;i++)      {          Hash_Table[i] = init_hash_header_node();          Hash_Table[i]->next = NULL;      }  }  /********************************************************************* *                           在哈希表增加节点 *说明: *1.在哈希表的某个链表末增加数据 输入:new_node:新节点 **********************************************************************/  void append_link_node(Link_Node_Ptr new_node)  {      Link_Node_Ptr node;      uint8_t pos = 0;      //新节点下一个指向为空      new_node->next = NULL;      //用哈希函数获得位置      pos = hash_func(new_node->id);      //判断是否为空链表      if (Hash_Table[pos]->next == NULL)      {          //空链表          Hash_Table[pos]->next = new_node;      }      else      {          //不是空链表          //获取根节点          node = Hash_Table[pos]->next;          //遍历          while (node->next != NULL)          {              node = node->next;          }          //插入          node->next = new_node;      }  }  /********************************************************************* *                           在哈希表查询节点 *说明: *1.知道在哈希表某处的单链表中,并开始遍历. *2.返回的是查询节点的前一个节点指针.这么做是为了做删除操作. 输入:pos:哈希表数组位置,从0开始计数      id:所需要查询节点的id      root:如果是根节点,则*root = 1,否则为0 返回:所需查询的节点的前一个节点指针,如果是根节点则返回根节点,失败返回0 **********************************************************************/  Link_Node_Ptr search_link_node(uint16_t id,uint8_t *root)  {      Link_Node_Ptr node;      uint8_t pos = 0;      //用哈希函数获得位置      pos = hash_func(id);      //获取根节点      node = Hash_Table[pos]->next;      //判断单链表是否存在      if (node == NULL)      {          return 0;      }      //判断是否是根节点      if (node->id == id)      {          //是根节点          *root = 1;          return node;      }      else      {          //不是根节点          *root = 0;          //遍历          while (node->next != NULL)          {              if (node->next->id == id)              {                  return node;              }              else              {                  node = node->next;              }          }          return 0;      }  }  /********************************************************************* *                           在哈希表删除节点 *说明: *1.删除的不是当前节点,而是当前节点后的一个节点 输入:node:删除此节点后面的一个节点      new_node:新节点 **********************************************************************/  void delete_link_node(Link_Node_Ptr node)  {      Link_Node_Ptr delete_node;      //重定向需要删除的前一个节点      delete_node = node->next;      node->next = delete_node->next;      //删除节点      free(delete_node);      delete_node = NULL;  }  /********************************************************************* *                           在哈希表删除根节点 输入:node:根节点 **********************************************************************/  void delete_link_root_node(Link_Node_Ptr node)  {      uint8_t pos = 0;      //用哈希函数获得位置      pos = hash_func(node->id);      //哈希表头清空      if (node != NULL)      {          Hash_Table[pos]->next = node->next;          //删除节点          free(node);          node = NULL;      }  }  /********************************************************************* *                           获得哈希表中所有节点数 输入:node:根节点 **********************************************************************/  uint16_t get_node_num(void)  {      Link_Node_Ptr node;      uint16_t i = 0;      uint16_t num = 0;      //遍历      for (i = 0;i < HASH_TABLE_LEN;i++)      {          //获取根节点          node = Hash_Table[i]->next;          //遍历          while (node != NULL)          {              num++;              node = node->next;          }      }      return num;  }  /********************************************************************* *                           从哈希表中获得对应序号的节点 *参数:index:序号.从1开始,最大值为节点总数值 *     root:如果是根节点,则*root = 1,否则为0 返回:所需查询的节点的前一个节点指针,如果是根节点则返回根节点,失败返回0 **********************************************************************/  Link_Node_Ptr get_node_from_index(uint16_t index,uint8_t *root)  {         Link_Node_Ptr node;      uint16_t i = 0;      uint16_t num = 0;      //遍历      for (i = 0;i < HASH_TABLE_LEN;i++)      {          //获取根节点          node = Hash_Table[i]->next;          //判断单链表是否存在          if (node == NULL)          {              continue;          }          //根节点          num++;          if (num == index)          {              //是根节点              *root = 1;              return node;           }          //遍历          while (node->next != NULL)          {              num++;              if (num == index)              {                  //不是根节点                  *root = 0;                  return node;               }              node = node->next;          }      }      return 0;  }  /********************************************************************* *                           删除hash表中所有节点 **********************************************************************/  void drop_hash()  {      Link_Node_Ptr node;      uint16_t i = 0;      Link_Node_Ptr node_next;      //遍历      for (i = 0;i < HASH_TABLE_LEN;i++)      {          //获取根节点          node = Hash_Table[i]->next;          while (1)          {              //判断单链表是否存在              if (node == NULL)              {                  //不存在                  Hash_Table[i]->next = NULL;                  break;              }              //根节点下一个节点              node_next = node->next;              //删除根节点              free(node);              //重指定根节点              node = node_next;          }      }  }  /********************************************************************* *                           输出所有节点 **********************************************************************/  void printf_hash()  {      Link_Node_Ptr node;      uint8_t root = 0;      uint8_t i = 0;      uint8_t num = 0;      printf("-------------打印hash表-------------\n");      num = get_node_num();      for (i = 1;i <= num;i++)      {          node = get_node_from_index(i,&root);          if (node != 0)          {              if (root)              {                  printf("根节点:节点号%d,id为%d\n",i,node->id);              }              else              {                  printf("普通节点:节点号%d,id为%d\n",i,node->next->id);              }          }      }  }  /********************************************************************* *                           主函数 *说明:实现对哈希表的新建,建立节点,查询及增加,删除节点的操作 **********************************************************************/  int main()  {      Link_Node_Ptr node;      uint8_t temp = 0;      uint8_t root = 0;      uint8_t i = 0;      init_hash_table();      //插入数据id = 1,data = 2;      node = init_link_node();      node->id = 1;      node->data = 2;      append_link_node(node);      //查询节点数      printf("1节点数为%d\n",get_node_num());      node = init_link_node();      node->id = 100;      node->data = 101;      append_link_node(node);      node = init_link_node();      node->id = 1002;      node->data = 1001;      append_link_node(node);      node = init_link_node();      node->id = 10000;      node->data = 10001;      append_link_node(node);      node = init_link_node();      node->id = 1000;      node->data = 10001;      append_link_node(node);      node = init_link_node();      node->id = 2;      node->data = 10001;      append_link_node(node);      //查询节点数      printf("2节点数为%d\n",get_node_num());      //查询id = 1000;      node = search_link_node(100,&temp);      if (node != 0)      {          if (temp == 0)          {              printf("删除普通节点:所需查询id的值为%d,数据为%d\n",node->next->id,node->next->data);              //删除              delete_link_node(node);          }          else          {              //根节点              printf("删除根节点所需查询id的值为%d,数据为%d\n",node->id,node->data);              //删除              delete_link_root_node(node);          }      }      else      {          printf("查询失败\n");      }      //查询id = 1000;      node = search_link_node(1001,&temp);      if (node != 0)      {          if (temp == 0)          {              printf("所需查询id的值为%d\n",node->next->data);          }          else          {              //根节点              printf("所需查询id的值为%d\n",node->data);          }      }      else      {          printf("查询失败\n");      }      //查询节点数      printf("节点数为%d\n",get_node_num());      printf_hash();      getchar();      return 0;  }  

说实话,我觉得这一段代码写的非常清晰,简洁明了。适合大家学习。
代码来自:
http://blog.csdn.net/jdh99/article/details/8490704#

原创粉丝点击