C语言实现数据结构之哈希表

本博客所有源代码:Github托管项目地址

链式哈希表介绍

链式哈希表从根本上来说是由一组链表构成。每个链表都可以看作一个“桶”,我们将所有的元素通过散列的方式放到具体的不同的桶中。插入元素时,首先将其键传入一个哈希函数(此过程称为哈希键),函数通过散列的方式告知元素属于哪一个“桶”,然后再相应的链表头插入元素,直到我们发现我们想要查找的元素。

解决冲突的方法

1开放定址法

这种方法也称再散列法其基本思想是:当关键字key的哈希地址p=Hkey)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,直到找出一个不冲突的哈希地址pi 将相应元素存入其中。这种方法有一个通用的再散列函数形式:

          Hi=Hkey+di% m   i=12…,n

    其中Hkey)为哈希函数,为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:

l         线性探测再散列

    dii=123m-1

这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

l         二次探测再散列

    di=12-1222-22k2-k2    ( k<=m/2 )

    这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。

l         伪随机探测再散列

    di=伪随机数序列。

具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。

例如,已知哈希表长度m=11,哈希函数为:Hkey= key  %  11,则H47=3H26=4H60=5,假设下一个关键字为69,则H69=3,与47冲突。如果用线性探测再散列处理冲突,下一个哈希地址为H1=3 + 1% 11 = 4,仍然冲突,再找下一个哈希地址为H2=3 + 2% 11 = 5,还是冲突,继续找下一个哈希地址为H3=3 + 3% 11 = 6,此时不再冲突,将69填入5号单元,参图8.26 (a)。如果用二次探测再散列处理冲突,下一个哈希地址为H1=3 + 12% 11 = 4,仍然冲突,再找下一个哈希地址为H2=3 - 12% 11 = 2,此时不再冲突,将69填入2号单元,参图8.26 (b)。如果用伪随机探测再散列处理冲突,且伪随机数序列为:259……..,则下一个哈希地址为H1=3 + 2% 11 = 5,仍然冲突,再找下一个哈希地址为H2=3 + 5% 11 = 8,此时不再冲突,将69填入8号单元。

2.         再哈希法

    这种方法是同时构造多个不同的哈希函数:

    Hi=RH1key  i=12k

当哈希地址Hi=RH1key)发生冲突时,再计算Hi=RH2key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

3.         链地址法

    这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

4建立公共溢出区

这种方法的基本思想是:将哈希表分为基本表溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

哈希表的构造方法

直接定址法

例如:有一个从1到100岁的人口数字统计表,其中,年龄作为关键字,哈希函数取关键字自身。

数字分析法

有学生的生日数据如下:
年.月.日
75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15
...
经分析,第一位,第二位,第三位重复的可能性大,取这三位造成冲突的机会增加,所以尽量不取前三位,取后三位比较好。

平方取中法

取关键字平方后的中间几位为哈希地址。

折叠法

将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。
例如:每一种西文图书都有一个国际标准图书编号,它是一个10位的 十进制数字,若要以它作关键字建立一个哈希表,当馆藏书种类不到10,000时,可采用此法构造一个四位数的哈希函数。

除留余数法

取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。
H(key)=key MOD p (p<=m)

随机数法

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即
H(key)=random(key),其中random为随机函数。通常用于关键字长度不等时采用此法。
若已知哈希函数及冲突处理方法,哈希表的建立步骤如下:
Step1. 取出一个数据元素的关键字key,计算其在哈希表中的存储地址D=H(key)。若存储地址为D的存储空间还没有被占用,则将该数据元素存入;否则发生冲突,执行Step2。
Step2. 根据规定的冲突处理方法,计算关键字为key的数据元素之下一个存储地址。若该存储地址的存储空间没有被占用,则存入;否则继续执行Step2,直到找出一个存储空间没有被占用的存储地址为止。

一个简单处理字符串的哈希函数hashpjw.c

/*hashpjw.c*/

unsigned int hashpjw(const void *key)
{
    const char *ptr;
    unsigned int val;

    val = 0;
    ptr = key;
    while (*ptr != '\0'){
        unsigned int tmp;
        val = (val << 4) +(*ptr);
        if (tmp = (val & 0xf0000000)){
            val = val ^ (tmp >> 24);
            val = val ^ tmp;
        }
        ptr++;
    }
    return val%PRIME_TBLSIZ 
}
链式哈希表头文件chtbl.h
/*chtbl.h*/

#ifndef CHTBL_H
#define CHTBL_H

#include <stdlib.h>
/*单链表的头文件*/
#include "list.h"

/* 定义一个链式哈希表的结构体*/

typedef struct Chtbl_ {
    int buckets; /*哈希表分配桶的个数*/
    int (*h)(const void *key);/*哈希函数指针h */
    int (*match)(const void *key1,const void *key2);/*判断2个键是否匹配*/
    void (*destroy)(void *data);/*释放内存空间*/

    int size;/*成员个数*/
    List *table;/*哈希表中链表桶的头指针*/
}Chtbl;

/* 函数接口*/
int  chtbl_init (Chtbl *htbl,int buckets,int (*h)(const void *key),int 
(*match)(const void *key1,const void *key2),void (*destroy)(void *data));
int chtbl_destroy (Chtbl *htbl);
int chtbl_insert(Chtbl *htbl,const void *data);
int chtbl_remove (Chtbl *htbl,void **data);
int chtbl_lookup(const Chtbl *htbl,void **data);

#define chtbl_size(Chtbl *htbl) ((htbl)->size)

#endif
        
链式哈希表主文件chtbl.c

/*chtbl_c*/
#include <stdlib.h>
#include <string.h>

#include "list.h"
#include "chtbl.h"

/* 初始化哈希表结构体*/
int chtbl_init(Chtbl * htbl, int buckets, int(* h)(const void * key), int(* match)(const void * key1, const void * key2), void(* destroy)(void * data))
{
    int i;
    /* 申请空间为桶*/
    if ((htbl->table = (List *)malloc(buckets * sizeof(List))) == NULL){
        return -1;
    }
    /* 初始化哈希表*/
    htbl->buckets = buckets;
    for (i = 0; i < buckets; i++){
        list_init(&htbl->table[i], destroy);
    }
    htbl->h = h;
    htbl->match = match;
    htbl->destroy = destroy;
    htbl->size = 0;
    return 0;

}

/*哈希表销毁*/
void chtbl_destroy(Chtbl * htbl)
{
    int i;
    if (htbl == NULL)
        return;
    /*销毁各个桶*/
    for (i = 0; i < htbl->buckets; i++){
        list_destory(htbl->table[i]);
    }

    /* 释放hash table 空间*/
    free(htbl->table);

    /*清空htbl结构体*/
    memset(htbl,0,sizeof(Chtbl));
    return ;
    
}
/* 插入数据
 * return 成功返回0;失败返回-1;存在返回1;
 *
 */
int chtbl_insert(Chtbl * htbl, const void * data)
{
    void *temp;
    int bucket;
    int retval;

    /*如何data已经在哈希表中返回1*/
    temp = (void *)data;
    if (chtbl_lookup(htbl, &temp) == 0)
        return 1;
    /*获得哈希键*/
    bucket = htbl ->h(data) % htbl->buckets;
    /*插入哈希表*/
    if ((retval = list_ins_next(htbl->table[bucket], NULL, data)) == 0)
        htbl ->size ++;
    return retval;
}

/*删除数据
  *return 成功返回0;失败返回-1;
  */
int chtbl_remove(Chtbl * htbl, void * * data)
{
    ListElmt *element = NULL;
    ListElmt *prev = NULL;
    int bucket = -1;

    /*获得哈希键*/
    bucket = htbl ->h(*data)%htbl -> buckets;
    /*查找在哈希表中的data*/
    for (element = list_head(&htbl->table [bucket]);element != NULL;element = 
    list_next(element)){
        if (htbl->match(*data,list_data(element))){
            if (list_rem_next(&htbl->table[bucket], prev, data) == 0){
                htbl->size --;
                return 0;
            } else {
                return -1;
            }
        }
        prev = element;
    }
    return -1;
}

/* 在哈希表中查找元素
  *return :如果找到元素返回0;否则返回-1.
  */
int chtbl_lookup(const Chtbl * htbl, void * * data)
{
    ListElmt *element;
    int bucket;

    /*获得哈希键*/
    bucket = htbl -> h(*data)%htbl->buckets;
    /*在哈希表中查找元素*/
   for (element = list_head(&htbl->table [bucket]);element != NULL;element = 
    list_next(element)){
        if (htbl->match(*data,list_data(element))){
            
*data = list_data(element);
           return 0;
        }
    }
   return -1;
}
开地址哈希表头文件ohtbl.h
/*ohtbl.h*/
#ifndef OHTBL_H
#define OHTBL_H

#include <stdlib.h>

/*定义开地址哈希表结构体*/
typedef struct Ohtbl_{
    int positions;/*槽位的个数*/
    void *vacated;/*指针将被初始化来指向一个特殊的地址空间,
                         来证明这个特殊地址上曾经删除过一个元素*/

    int (*h1)(const void * key);
    int (*h2)(const void *key);
    int (*match)(const void *key1,const void *key2);
    void (*destroy)(void *data);

    int size;/*元素的个数*/
    void **table;/*存储元素的数组(malloc申请)*/
}Ohtbl;

/*函数接口*/
int ohtbl_init(Ohtbl *htbl,int positions,int (*h1)(const void *key),int 
    (*h2)(const void *key),int (*match)(const void *key1,const void *key2),void 
    (*destroy)(void *data));
void ohtbl_destroy(Ohtbl *htbl);
int ohtbl_insert(Ohtbl *htbl,const void *data);
int ohtbl_remove(Ohtbl *htbl,void **data);
int ohtbl_lookup(const Ohtbl *htbl,void **data);
#define ohtbl_size(htbl)  ((htbl) ->size)
#endif


开地址哈希表主文件ohtbl.c

/*ohtbl.c*/
#include <stdlib.h>
#include <string.h>

#include "ohtbl.h"

/*预订一个内存地位为腾出的元素*/
static char vacated;

/*结构体初始化
  *return 初始化成功: 0,  失败:-1.
  */

int ohtbl_init(Ohtbl * htbl, int positions, int(* h1)(const void * key), int(* h2)(const void * key), int(* match)(const void * key1, const void * key2), void(* destroy)(void * data))
{
    int i;
    /*申请空间为哈希表*/
    if ((htbl ->table = (void **)malloc(positions *sizeof(void *))) == NULL)
        return -1;
    /*初始化每个positions*/
    htbl ->positions = positions;
    for (i = 0; i < htbl->positions; i++)
        htbl -> table[i] = NULL;
    /*设置腾出成员的内存地址*/
    htbl->vacated = &vacated;
    /*初始化封装的函数*/
    htbl -> h1 = h1;
    htbl -> h2 = h2;
    htbl ->match = match;
    htbl ->destroy = destroy;
    /*初始化元素个数*/
    htbl ->size = 0;
    return 0;
}

/*开地址哈希表销毁
  *
  *
  */
  int ohtbl_destroy(Ohtbl * htbl)
{
    int i;
    if (htbl ->destroy !=NULL){
        /*使用用户定义的函数去释放存放数据的内存*/
        for (i = 0; i < htbl ->positions; i++){
             if (htbl ->table[i] != NULL && htbl ->table[i] != htbl ->vacated)
                htbl ->destroy(htbl ->table[i]);
             
        }
    }
    /*释放table 内存*/
    free(htbl ->table);
    /*清空哈希表结构体*/
    memest(htbl,0,sizeof(Ohtbl));
    return;
}

/* 哈希表中插入元素
  *return 初始化成功: 0,  失败:-1.
  *  数据已经在哈希表内返回:1
  */
int ohtbl_insert(Ohtbl * htbl, const void * data)
{
    void *temp;
    int position = 0;
    int  i = 0;

    /*但哈希表已经满的话返回-1*/
    if (htbl ->size == htbl ->positions)
        return -1;
    /*如果数据已经在哈希表内,返回1*/
    temp = (void *)data;
    if (ohtbl_lookup(htbl, &temp) == 0)
        return 1;
    /*找到哈希编码值存在数据*/
    for (i = 0; i <htbl -> positions; i++){
        /*获得哈希编码值*/
        position = (htbl->h1(data) + i * htbl ->h2(data)) %htbl ->positions;
        if (htbl ->table[position] == NULL || htbl ->table[position] ==htbl 
        ->vacated){
            /*插入数据*/
            htbl ->table[position] = (void *)data;
            htbl ->size ++;
            return 0;
        }
    }
    return -1;
}

/* 删除哈希表数值
  *return 初始化成功: 0,  失败:-1.
  */
int ohtbl_remove(Ohtbl * htbl, void * * data)
{
    int i = 0;
    int position = 0;

    if (htbl ->size == 0)
        return -1;
    for (i = 0; i <htbl -> positions; i++){
        /*获得哈希编码值*/
        position = (htbl->h1(data) + i * htbl ->h2(data)) %htbl ->positions;
        if (htbl ->table[position] == NULL){/*表明元素未存放到哈希表内*/
            return -1;
        } else if (htbl ->table[position] == htbl ->vacated){
            /*搜索vacated 之外*/
            continue;
        } else if(htbl -> match(htbl->table[position],*data)){
            *data = htbl->table[position];
            htbl->table[position] = htbl ->vacated;
            htbl->size --;
            return 0;
        }
    }
    return -1
}

/*查找数据
  *return 初始化成功: 0,  失败:-1.
  */
int ohtbl_lookup(const Ohtbl * htbl, void * * data)
{
    int position = 0;
    int i = 0;
    for (i = 0; i <htbl -> positions; i++){
        /*获得哈希编码值*/
        position = (htbl->h1(data) + i * htbl ->h2(data)) %htbl ->positions;
        if (htbl ->table[position] == NULL){/*表明元素未存放到哈希表内*/
            return -1;
        } else if(htbl -> match(htbl->table[position],*data)){
            *data = htbl->table[position];
            return 0;
        }
    }
    return -1;
}



本文原地址:http://blog.csdn.net/sjin_1314/article/details/8478969

猜你喜欢

转载自blog.csdn.net/shiyipaisizuo/article/details/79008367