Hash Table哈希表和Hash List哈希链表的知识汇总

本文分为三个部分:
一  Hash Table 概论
二 Linux源代码中的Hash List

三  Linux源代码中的Hash Table

一  Hash Table概论
字符串的算法一般大公司都会考到,我们首先要想到高效的hash。如百度查找一组字符串是否出现在某个文本中,这个不是考什么kmp,他们想听到的是hash。趋势科技考的是从某个文本中删除一组字符串,我想也是要hash吧。
1 概述
链表查找的时间效率为O(N),二分法为log2N,B+ Tree为log2N,但Hash链表查找的时间效率为O(1)。 
设计高效算法往往需要使用Hash链表,常数级的查找速度是任何别的算法无法比拟的,Hash链表的构造和冲突的不同实现方法对效率当然有一定的影响,然 而Hash函数是Hash链表最核心的部分,本文尝试分析一些经典软件中使用到的字符串Hash函数在执行效率、离散性、空间利用率等方面的性能问题。
2 经典字符串Hash函数介绍 
先提一个简单的问题,如果有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做?有一个方法最简单,老老实实从头查到尾,一个一个比较,直到找到为止,我想只要学过程序设计的人都能把这样一个程序作出来,但要是有程序员把这样的程序交给用户,我只能用无语来评价,或许它真的能工作,但...也只能如此了。最合适的算法自然是使用HashTable(哈希表),先介绍介绍其中的基本知识,所谓Hash,一般是一个整数,通过某种算法,可以把一个字符串"压缩" 成一个整数,这个数称为Hash.
HDOJ-1800题目分析
除去马甲,本题的本质是——求相同级别(level)的人最多是几个。
如果level的范围不大的话(64位整数可以表示)——本题很简单,简单贪心
本题的难点:level的范围较大,需用大数或者字符串比较(去首0)
效率较高、编程简单的方法:Hash!
此外,字典树也是不错的选择


#include "stdio.h"
#include "memory.h"
#define MAXN 10000
inline int ELFhash(char *key)
{
    unsigned long h = 0;
    unsigned long g;
    while( *key )
    {
        h =( h<< 4) + *key++;
        g = h & 0xf0000000L;
        if( g ) h ^= g >> 24;
        h &= ~g;
    }
    return h;
}
int hash[MAXN],count[MAXN];
int maxit,n;
inline void hashit(char *str)
{
    int k,t;
    while( *str == '0' )    str++;
    k = ELFhash(str);
    t = k % MAXN;
    while( hash[t] != k && hash[t] != -1 )
        t = ( t + 5 ) % MAXN;
    if( hash[t] == -1 )    
        count[t] = 1,hash[t] = k;
    else if( ++count[t] > maxit ) 
        maxit = count[t];
}
int main()
{
    char str[100];
    while(scanf("%d",&n)!=EOF)
    {
        memset(hash,-1,sizeof(hash));
        for(maxit=1,gets(str);n>0;n--)   // 此处的gets是吸收掉之前输入个数后的回车
        {
            gets(str);
            hashit(str);
        }
        printf("%d\n",maxit);
    }
}
延伸:  在C语言中,接受输入单个字符的函数如scanf、getchar()等都有一个毛病,就是程序接受完用户输入的字符后并没有清空键盘输入缓冲区(或者说标准输入流),,因此当用户输完字符和回车后,回车字符还留在标准输入流中,而gets函数又恰好从缓冲区的当前字符开始接收输入,这样,它接收的到第一个字符就是上次输入遗留下来的回车符号,于是程序又会把它作为结束输入的标志,不再接受用户的键盘输入。     而C++的输入流就改正了这个毛病,接受完后自动清空输入流。  
 
==================================================
#用的是开放定址法处理冲突,线性探测再散列确定增量
#include
#include
#include
#include
#include 
#include
#include
 
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define SUCCESS 1
#define UNSUCCESS 0
#define DUPLICATE -1
#define NULLKEY 0 // 0为无记录标志
#define N 10 // 数据元素个数
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
typedef int Status; // Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int Boolean; // Boolean是布尔类型,其值是TRUE或FALSE
typedef int KeyType; // 设关键字域为整型
struct ElemType // 数据元素类型
{
   KeyType key;
   int ord;
};
int hashsize[]={11,19,29,37}; // 哈希表容量递增表,一个合适的素数序列
int m=0; // 哈希表表长,全局变量
struct HashTable
{
   ElemType *elem; // 数据元素存储基址,动态分配数组
   int count; // 当前数据元素个数
   int sizeindex; // hashsize[sizeindex]为当前容量
};
Status InitHashTable(HashTable &H)// 操作结果: 构造一个空的哈希表
{ int i;
   H.count=0; // 当前元素个数为0
   H.sizeindex=0; // 初始存储容量为hashsize[0]
   m=hashsize[0];
   H.elem=(ElemType*)malloc(m*sizeof(ElemType));
   if(!H.elem)
     exit(OVERFLOW); // 存储分配失败
   for(i=0;i       H.elem[i].key=NULLKEY; // 未填记录的标志
   return OK;
}
void DestroyHashTable(HashTable &H)// 初始条件: 哈希表H存在。操作结果: 销毁哈希表H
{ free(H.elem);
   H.elem=NULL;
   H.count=0;
   H.sizeindex=0;
}
unsigned Hash(KeyType K)// 一个简单的哈希函数(m为表长,全局变量)
{ return K%m;
}
void collision(int &p,int d) // 线性探测再散列

   p=(p+d)%m;// 开放定址法处理冲突
}
Status SearchHash(HashTable H,KeyType K,int &p,int &c)// 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据
{ p=Hash(K); // 求得哈希地址
   while(H.elem[p].key!=NULLKEY&&!EQ(K,H.elem[p].key))
   { // 该位置中填有记录.并且关键字不相等
     c++;
     if(c         collision(p,c); // 求得下一探查地址p
     else
       break;
   }
   if EQ(K,H.elem[p].key)
     return SUCCESS; // 查找成功,p返回待查数据元素位置
   else
     return UNSUCCESS; // 查找不成功(H.elem[p].key==NULLKEY),p返回的是插入位置
}
Status InsertHash(HashTable &,ElemType); // 对函数的声明
void RecreateHashTable(HashTable &H) // 重建哈希表
{ int i,count=H.count;
   ElemType *p,*elem=(ElemType*)malloc(count*sizeof(ElemType));
   p=elem;
   printf("重建哈希表\n");
   for(i=0;i     if((H.elem+i)->key!=NULL

本文来自CSDN博客,转载请标明出处: http://blog.csdn.net/hell2pradise/archive/2009/08/12/4437041.aspx


二: Linux内核中Hash List的使用
其代码位于include/linux/list.h中,3.0内核中将其数据结构定义放在了include/linux/types.h中
哈希表的数据结构定义 如图
 

点击(此处)折叠或打开

  1. struct hlist_head{
  2. struct hlist_node *first;
  3. }
  4. struct hlist_node {
  5.         struct hlist_node *next,**pprev;
  6. }


1> 头节点和其他节点结构不一致。
hlist_head表示哈希表的头结点。哈希表中每一个entry(list_entry)所对应的都是一个链表。
hlist_head结构体只有一个域,即first。First指针指向该hlist链表的第一个结点。

思考 为什么采用单向链表,即头结点中没有prev变量?
散列表的目的是为了方便快速的查找,所以散列表通常是一个比较大的数组,否则“冲突”的概率会非常大,这样就失去了散列表的意义。如何来做到既能维护一张大表,又能不占用过多的内存呢?此时只能对于哈希表的每个entry(表头结点)它的结构体中只能存放一个指针。【简言之,节省空间


2> hlist_node结构体有两个域,next和pprev。
(1)next指向下个hlist_node结点,倘若改结点是链表的最后一个节点,next则指向NULL
(2)pprev是一个二级指针,它指向前一个节点的next指针

思考*****为什么要采用pprev,而不采用一级指针?********
由于hlist不是一个完整的循环链表,在list中,表头和结点是同一个数据结构,直接用prev是ok的。在hlist中,表头中没有prev,只有一个first。
1>为了能统一地修改表头的first指针,即表头的first指针必须修改指向新插入的结点,hlist就设计了pprev。list结点的pprev不再是指向前一个结点的指针,而是指向前一个节点(可能是表头)中的next(对于表头则是first)指针,从而在表头插入的操作中可以通过一致的node->pprev访问和修改前结点的next(或first)指针。
2>还解决了数据结构不一致,hlist_node巧妙的将pprev指向上一个节点的next指针的地址,由于hlist_head和hlist_node指向的下一个节点的指针类型相同,就解决了通用性。

参考:http://blog.csdn.net/tigerjibo/article/details/8450995


三 Linux源代码中的Hash Table
这一部分是由 第二部分引出来的,以内核代码2.6.26为例
在文件error.c (net\9p)中有一个定义 
static struct hlist_head hash_errmap[ERRHASHSZ];


点击(此处)折叠或打开

  1. int p9_error_init(void)
  2. {
  3.     struct errormap *c;
  4.     int bucket;

  5.     /* initialize hash table */
  6.     for (bucket = 0; bucket < ERRHASHSZ; bucket++)
  7.         INIT_HLIST_HEAD(&hash_errmap[bucket]);

  8.     /* load initial error map into hash table */
  9.     for (= errmap; c->name != NULL; c++) {
  10.         c->namelen = strlen(c->name);
  11.         /*Hash function 是由自己来确定的*/
  12.         bucket = jhash(c->name, c->namelen, 0) % ERRHASHSZ;
  13.         INIT_HLIST_NODE(&c->list);
  14.         hlist_add_head(&c->list, &hash_errmap[bucket]);
  15.     }

  16.     return 1;
  17. }
你也可以自己来通过 创建一个新的module来利用上内核中的代码,例子请参考:
http://fanrenhao.blog.51cto.com/3961213/1033529
构建一个文件hlist-module.c,


点击(此处)折叠或打开

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/list.h>

  4. struct q_coef
  5. {
  6.     u8 coef;
  7.     u8 index;
  8.     struct hlist_node hash;
  9. };

  10. #define HASH_NUMBER 15
  11. u8 coef[HASH_NUMBER] = {
  12.     0x01, 0x02, 0x04, 0x08,0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13,
  13. };
  14. struct q_coef q_coef_list[HASH_NUMBER];

  15. struct hlist_head hashtbl[HASH_NUMBER];

  16. static inline int hash_func(u8 k)
  17. {
  18.     int a, b, p, m;
  19.     a = 104;
  20.     b = 52;
  21.     p = 233;
  22.     m = HASH_NUMBER;
  23.     return ((* k + b) % p) % m;
  24. }

  25. static void hash_init(void)
  26. {
  27.     int i, j;
  28.     for (= 0 ; i < HASH_NUMBER ; i++) {
  29.         INIT_HLIST_HEAD(&hashtbl[i]);
  30.         INIT_HLIST_NODE(&q_coef_list[i].hash);
  31.         q_coef_list[i].coef = coef[i];
  32.         q_coef_list[i].index = i + 1;
  33.     }
  34.     for (= 0 ; i < HASH_NUMBER ; i++) {
  35.         j = hash_func(q_coef_list[i].coef);
  36.         hlist_add_head(&q_coef_list[i].hash, &hashtbl[j]);
  37.     }
  38. }

  39. static void hash_test(void)
  40. {
  41.     int i, j;
  42.     struct q_coef *q;
  43.     struct hlist_node *hn;
  44.     for (= 0 ; i < HASH_NUMBER ; i++) {
  45.         j = hash_func(coef[i]);
  46.         hlist_for_each_entry(q, hn, &hashtbl[j], hash)
  47.             if (q->coef == coef[i])
  48.                 printk("found: coef=0x%02x index=%d\n", q->coef, q->index);
  49.     }
  50. }
  51. static int htest_init (void)
  52. {
  53.     hash_init();
  54.     hash_test();
  55.     return -1;
  56. }

  57. static void htest_exit (void)
  58. {
  59. }

  60. module_init(htest_init);
  61. module_exit(htest_exit);

  62. MODULE_LICENSE("Dual BSD/GPL");
Makefile如下:

点击(此处)折叠或打开

  1. # Makefile2.6
  2. obj-+= hlist-module.o # ??hellomod ???????
  3. CURRENT_PATH := $(shell pwd) #?????????
  4. LINUX_KERNEL := $(shell uname -r) #Linux??????????
  5. LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL) #Linux??????????
  6. all:
  7.     make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #?????
  8. clean:
  9.     make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #??


然后make, 先把dmesg清空一下
$sudo dmesg --clear
然后
$ sudo insmod hlist_module.ko
这个一直显示有错误,不过不影响你看结果
ubuntu12:~/program/example/module2$ sudo insmod hlist_module.ko
insmod: error inserting 'hlist_module.ko': -1 Operation not permitted
看结果
$dmesg
yyyy@mren-ubuntu12:~/program/example/module2$ dmesg 
[1318688.795697] found: coef=0x01 index=1
[1318688.795701] found: coef=0x02 index=2
[1318688.795703] found: coef=0x04 index=3
[1318688.795705] found: coef=0x08 index=4
[1318688.795707] found: coef=0x10 index=5
[1318688.795709] found: coef=0x20 index=6
[1318688.795711] found: coef=0x40 index=7
[1318688.795713] found: coef=0x80 index=8
[1318688.795715] found: coef=0x1d index=9
[1318688.795717] found: coef=0x3a index=10
[1318688.795719] found: coef=0x74 index=11
[1318688.795721] found: coef=0xe8 index=12
[1318688.795723] found: coef=0xcd index=13
[1318688.795725] found: coef=0x87 index=14
[1318688.795727] found: coef=0x13 index=15

你可能感兴趣的:(数据结构与算法)