数据结构与算法-散列表查找

一、定义

散列技术是记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个对应关系找到给定值key的映射f(key)。若查找集合中存在这个记录,则必定在f(key)的位置上。
存储位置 = f (关键字)
我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。
采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为散列表或者哈希表
关键字对应的记录存储位置我们称为散列地址

二、散列查找步骤

  1. 在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。
  2. 当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。
    散列技术既是一种存储方法,也是一种查找方法
    与线性表、树、图等结构不同的是,线性表、树、图数据元素之间存在某种逻辑关系,可以用连线图表示出来,而散列技术记录的数据元素之间不存在什么逻辑关系,它只与关键字有关联。
    我们时常会碰到两个关键字key1不等于key2,但是却有f(key1)=f(key2),这种现象我们称为冲突,并把key1和key2称为这个散列函数的同义词

三、散列函数的构造方法

1. 直接定址法

我们可以取关键字的某个线性函数值为散列地址:
f(key) = a*key+b(a、b为常数)
优点:简单、均匀、也不会产生冲突。
缺点:需要事先知道关键字的分布情况。
适用范围:查找表较小且连续的情况。

2. 数字分析法

使用关键字的一部分来计算散列存储位置的方法,如电话号码存储,可抽取电话号码的中间几位进行反转/右移/左移等操作,然后将关键字分配到散列表的各位置。
适用范围:通常用于处理关键字位数比较大的情况。

3. 平方取中法

这个方法计算比较简单,假设关键字1234,那么它的平方为1522756,再抽取中间3位就是227,用做散列地址。再比如4321,那么它的平方为18671041,抽取中间3位就可以是671,也可以是710用做散列地址。
适用范围:不知道关键字分布情况,而且位数又不是很大的情况。

折叠法

将关键字从左到右分割成位数相等的几部分(最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位做为散列地址。
适用范围:事先不需要知道关键字的分布,适合关键字位数较多的情况。

4. 除留余数法

对于散列表长为m的散列函数公式为:
f(key) = key mod p(p<=m)
其中mod是取模的意思。
事实上,这个方法不仅可以对关键字取模,也可以在折叠、平方取中后再取模。
此方法的关键在于如何选择p,p如果选不好,则可能很容易产生同义词。通常p为小于或等于表长的最小质数或不包含小于20质因子的合数。

5. 随机数法

选择一个随机数,取关键字的随机函数值为它的散列地址。
f(key) = random(key)
适用范围:关键字长度不等时,可采用此方法。

四、处理散列冲突的办法

1. 开放定址法

开放定址法是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散表地址总能被找到,并将记录存入。
f(key) = (f(key)+d) mod m (d=1,2,3...m-1)
这种解决冲突的开放定址法称为线性探测法。

2. 再散列函数法

每当发生散列冲突的时候,就换一个散列函数计算,相信总有一个会把冲突解决掉。
f(key) = RH(key)
其中RH是另一个散列函数。

3. 链地址法

将所有关键字的同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。
此时已经完全不存在冲突问题,无论有多少个冲突,都只是在当前位置给单链表增加结点而已。
链地址法对于可能会造成很多冲突的散列函数来说,提供了绝对不会出现找不到地址的保障,但是查找时需要遍历单链表,增加了性能损耗。

五、散列表查找实现

typedef int Status;

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 //存储空间初始分配量
#define SUCCESS 1
#define UNSUCCESS 0

//定义散列表长为数组的长度
#define HASHSIZE 12
#define NULLKEY -32768

typedef struct
{
    //数据元素存储基址,动态分配数组
    int *elem;
    //当前数据元素个数
    int count;
}HashTable;
int m=0; /* 散列表表长,全局变量 */

//1.初始化散列表
Status InitHashTable(HashTable *H)
{
    int i;
    
    //① 设置H.count初始值; 并且开辟m个空间
    m=HASHSIZE;
    H->count=m;
    H->elem=(int *)malloc(m*sizeof(int));
    
    //② 为H.elem[i] 动态数组中的数据置空(-32768)
    for(i=0;i<m;i++)
        H->elem[i]=NULLKEY;
    
    return OK;
}

//2. 散列函数
int Hash(int key)
{
    //除留余数法
    return key % m;
}

//3. 插入关键字进散列表
void InsertHash(HashTable *H,int key)
{
    
    
    //① 求散列地址
    int addr = Hash(key);
    
    //② 如果不为空,则冲突
    while (H->elem[addr] != NULLKEY)
    {
        //开放定址法的线性探测
        addr = (addr+1) % m;
    }
    
    //③ 直到有空位后插入关键字
    H->elem[addr] = key;
}

//4. 散列表查找关键字
Status SearchHash(HashTable H,int key,int *addr)
{
    //① 求散列地址
    *addr = Hash(key);
    
    //② 如果不为空,则冲突
    while(H.elem[*addr] != key)
    {
        //③ 开放定址法的线性探测
        *addr = (*addr+1) % m;
        
        //④H.elem[*addr] 等于初始值或者循环有回到了原点.则表示关键字不存在;
        if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
            //则说明关键字不存在
            return UNSUCCESS;
    }
    
    return SUCCESS;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    int arr[HASHSIZE]={12,67,56,16,25,37,22,29,15,47,48,34};
    int i,p,key,result;
    HashTable H;
    
    //1.初始化散列表
    InitHashTable(&H);
    
    //2.向散列表中插入数据
    for(i=0;i<m;i++)
        InsertHash(&H,arr[i]);
    
    //3.在散列表查找key=39
    key=39;
    result=SearchHash(H,key,&p);
    if (result)
        printf("查找 %d 的地址为:%d \n",key,p);
    else
        printf("查找 %d 失败。\n",key);
    
    //4.将数组中的key,打印出所有在散列表的存储地址
    for(i=0;i<m;i++)
    {
        key=arr[i];
        SearchHash(H,key,&p);
        printf("查找 %d 的地址为:%d \n",key,p);
    }

    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345