码迷,mamicode.com
首页 > 其他好文 > 详细

数据结构——全面学习哈希

时间:2015-08-18 14:20:22      阅读:165      评论:0      收藏:0      [点我收藏+]

标签:c++   数据结构   哈希   

    相信学计算机的童鞋对于“哈希”这个词会很熟悉,但是能明明白白的说清楚,并且用程序来描述的人还是比较少的。这里,我们就全面学习这个重要的数据结构,以及它的思想和应用。

    首先,我们来学习一下几个基本概念。

    哈希(hash)

    是一种数据编码方式。将大尺寸的数据(如一句话,一张图片,一段音乐、一个视频等)浓缩到一个数字中,从而方便地实现数据匹配和查找的功能。

    哈希表(hash table)

    一种数据表结构。hash表,有时候也被称为散列表。个人认为,hash表是介于链表和二叉树之间的一种中间结构。链表使用十分方便,但是数据查找十分麻烦;二叉树中的数据严格有序,但是这是以多一个指针作为代价的结果。hash表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

    打个比方来说,所有的数据就好像许许多多的书本。如果这些书本是一本一本堆起来的,就好像链表或者线性表一样,整个数据会显得非常的无序和凌乱,在你找到自己需要的书之前,你要经历许多的查询过程;而如果你对所有的书本进行编号,并且把这些书本按次序进行排列的话,那么如果你要寻找的书本编号是n,那么经过二分查找,你很快就会找到自己需要的书本;但是如果你每一个种类的书本都不是很多,那么你就可以对这些书本进行归类,哪些是文学类,哪些是艺术类,哪些是工科的,哪些是理科的,你只要对这些书本进行简单的归类,那么寻找一本书也会变得非常简单,比如说如果你要找的书是计算机方面的书,那么你就会到工科一类当中去寻找,这样查找起来也会显得麻烦。

    映像

    由哈希函数得到的哈希表是一个映像。

    冲突

    如果两个关键字的哈希函数值相等,这种现象称为冲突。

    哈希算法

    并不是一个特定的算法而是一类算法的统称。 哈希算法也叫散列算法,一般来说满足这样的关系:f(data)=key,输入任意长度的data数据,经过哈希算法处理后输出一个定长的数据key。同时这个过程是不可逆的,无法由key逆推出data。

    处理冲突的几个方法(后面的程序会详解)

  1.     开放地址法:用开放地址处理冲突就是当冲突发生时,形成一个地址序列,沿着这个序列逐个深测,直到找到一个“空”的开放地址,将发生冲突的关键字值存放到该地址中去。 例如:hash(i)=(hash(key)+d(i)) MOD m (i=1,2,3,......,k(k<m-1)) d为增量函数,d(i)=d1,d2,d3,...,dn-1 根据增量序列的取法不同,可以得到不同的开放地址处理冲突探测方法。 有线性探测法、二次方探测法、伪随机探测法。
  2.     链地址法:把所有关键字为同义词的记录存储在一个线性链表中,这个链表成为同义词链表,即把具有相同哈希地址的关键字值存放在同义链表中。
  3.     二次哈希表:费时间的一种方法 如果是一个data数据集,经过哈希算法处理后得到key的数据集,然后将keys与原始数据进行一一映射就得到了一个哈希表。一般来说哈希表M符合M[key]=data这种形式。 哈希表的好处是当原始数据较大时,我们可以用哈希算法处理得到定长的哈希值key,那么这个key相对原始数据要小得多。我们就可以用这个较小的数据集来做索引,达到快速查找的目的。

    稍微想一下就可以发现,既然输入数据不定长,而输出的哈希值却是固定长度的,这意味着哈希值是一个有限集合,而输入数据则可以是无穷多个。那么建立一对一关系明显是不现实的。所以"碰撞"(不同的输入数据对应了相同的哈希值)是必然会发生的,所以一个成熟的哈希算法会有较好的抗冲突性。同时在实现哈希表的结构时也要考虑到哈希冲突的问题。

    密码上常用的MD5,SHA都是哈希算法,因为key的长度(相对大家的密码来说)较大所以碰撞空间较大,有比较好的抗碰撞性,所以常常用作密码校验。


    下面,我们通过一个例子,来自己实现一个完整的哈希表

    问题描述:针对某个集体(比如你所在的班级)中的“人名”设计一个哈希表,使得平均查找长度不超过R,完成相应的建表和查表程序。

    思路:假设人名为中国人姓名的汉语拼音形式。待填入哈希表的人名共有30个,取平均查找长度的上限为2。哈希函数用除留余数法构造,用伪随机探测再散列法处理冲突。

    代码如下 :

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#ifndef _HashTest_H_
#define _HashTest_H_
#define NAME_NO 30
#define HASH_LENGTH 50
#define M 50;

using namespace std;

typedef struct
{
    char *py;      //名字的拼音
    int k;         //拼音所对应的整数
}NAME;

typedef struct         //哈希表
{
    char *py;     //名字的拼音
    int k;        //拼音所对应的整数
    int si;       //查找长度
}HASH;

#endif

HASH HashList[HASH_LENGTH],NameList[HASH_LENGTH];


void InitNameList()
{
    char *f;
    int r,s0,i;
    NameList[0].py="zhoujielun";
    NameList[1].py="naying";
    NameList[2].py="halin";
    NameList[3].py="wangfeng";
    NameList[4].py="fenghanzao";
    NameList[5].py="fuzongkai";
    NameList[6].py="hujingbin";
    NameList[7].py="huangjianwu";
    NameList[8].py="lailaifa";
    NameList[9].py="lijiahao";
    NameList[10].py="liangxiaocong";
    NameList[11].py="linchunhua";
    NameList[12].py="liujianhui";
    NameList[13].py="luzhijian";
    NameList[14].py="luonan";
    NameList[15].py="quegaoxiang";
    NameList[16].py="sugan";
    NameList[17].py="suzhiqiang";
    NameList[18].py="taojiayang";
    NameList[19].py="wujiawen";
    NameList[20].py="xiaozhuomin";
    NameList[21].py="xujinfeng";
    NameList[22].py="yanghaichun";
    NameList[23].py="yeweixiong";
    NameList[24].py="zengwei";
    NameList[25].py="zhengyongbin";
    NameList[26].py="zhongminghua";
    NameList[27].py="chenliyan";
    NameList[28].py="liuxiaohui";
    NameList[29].py="panjinmei";
    for(i=0;i<NAME_NO;i++)
    {
        s0=0;
        f=NameList[i].py;
        for(r=0;*(f+r)!='\0';r++)/*将字符串的各个字符所对应的ASCII码相加,所得的整数做为哈希表的关键字*/
            s0=*(f+r)+s0;
        NameList[i].k=s0;
    }
}

void CreateHashList()
{
    int i;
    for(i=0; i<HASH_LENGTH;i++)
    {
        HashList[i].py="";
        HashList[i].k=0;
        HashList[i].si=0;
    }
    for(i=0;i<NAME_NO;i++)
    {
        int sum=0;
        int adr=(NameList[i].k)%M;//哈希函数
        int d=adr;
        if(HashList[adr].si==0)//如果不冲突
        {
            HashList[adr].k=NameList[i].k;
            HashList[adr].py=NameList[i].py;
            HashList[adr].si=1;
        }
        else//冲突
        {
            do
            {
                d=(d+NameList[i].k%10+1)%M;//伪随机探测再散列法处理冲突
                sum=sum+1;                 //查找次数加1
            }while (HashList[d].k!=0);   //找到一个空位置
            HashList[d].k=NameList[i].k;
            HashList[d].py=NameList[i].py;
            HashList[d].si=sum+1;
        }
    }
}



void  FindList() //查找
{
    char name[20]={0};
    int s0=0,r,sum=1,adr,d;
    printf("input name:");
    scanf("%s",name);
    for(r=0;r<20;r++)   //求出姓名的拼音所对应的整数(关键字)
        s0+=name[r];
    adr=s0%M;   //使用哈希函数
    d=adr;
    if(HashList[adr].k==s0)          //分3种情况进行判断
        printf("\nname:%s   key:%d   find times: 1",HashList[d].py,s0);
    else if (HashList[adr].k==0)
        printf("no records!");
    else
    {
        int g=0;
        do
        {
            d=(d+s0%10+1)%M;       //伪随机探测再散列法处理冲突
            sum=sum+1;
            if(HashList[d].k==0)
            {
                printf("no records! ");
                g=1;
            }
            if(HashList[d].k==s0)
            {
                printf("\nname:%s   key:%d   find times:%d",HashList[d].py,s0,sum);
                g=1;
            }
        }while(g==0);
    }
}

void Display()
{
    int i;
    float average=0;
    printf("\naddress\tkey\t\ttimes\tH(key)\t name\n"); //显示的格式
    for(i=0; i<50; i++)
    {
        printf("%d ",i);
        printf("\t%d ",HashList[i].k);
        printf("\t\t%d ",HashList[i].si);
        printf("\t\t%d ",HashList[i].k%30);
        printf("\t %s ",HashList[i].py);
        printf("\n");
    }
    for(i=0;i<HASH_LENGTH;i++)
        average+=HashList[i].si;
    average/=NAME_NO;
}

int main()
{
    char ch1;
    InitNameList();
    CreateHashList ();
    do
    {
        printf("D. show hashTable\nF. find\nQ. quit\nplease choose");
        cin>>&ch1;
        switch(ch1)
        {
            case 'D':Display(); cout<<endl;break;
            case 'F':FindList(); cout<<endl;break;
            case 'Q':exit(0);
        }
        cout<<"come on !(y/n):";
        cin>>&ch1;
    }while(ch1!='n');
    return 0;
}

    我们执行这段程序,会出现如下结果:

    技术分享

    根据输出我们可以看到四个值:

  •     地址
  •     关键字
  •     搜索长度 H(key)
  •     姓名

    关键字就是拼音的ascll码之和。然后通过哈希函数形成了一个值,我们叫做value这段程序是用50取余数之后得到的,所以key值在0~50之间。这个value值决定了数据存放的位置,所以能看到value值跟地址很多是一样的。这里是如何解决“冲突”的呢。如果有两个元素的经过哈希函数之后的结果是一样的呢?这里定义了一个si的值,意思为搜索次数,如果没有冲突意思是一次性找到了位置。

    我们来看这一段函数 

else //冲突,d的初始值为adr,adr意思是address,也是value值赋予的
{
    do
    {
        d=(d+NameList[i].k%10+1)%M;
        sum=sum+1;
    }while(HashList[d].k!=0);     //找到空位
    HashList[d].k=NameList[i].k;   //放入元素
    HashList[d].py=NameList[i].py;
    HashList[d].si=sum+1;
}

    其中 

d=(d+NameList[i].k%10+1)%M

    叫做伪随机探测再散列法处理冲突,就是将散列值(value),d,重新散列一次重新获得一次d的值找到一个空的位置,把元素塞进去。

    再看一看key是如何得到的,也就是姓名的ascll码之和是如何得到的。  

for(i=0;i<NAME_NO;i++)
{
    S0=0;
    f=NameList[i].py;
    for(r=0;*(f+r)!='\0';r++)
        s0=*(f+r)+s0;
    NameList[i].k=s0;
}

    这里注意一下,f是字符类型,r是int型,有一个类型转换在这里。



版权声明:本文为博主原创文章,未经博主允许不得转载。

数据结构——全面学习哈希

标签:c++   数据结构   哈希   

原文地址:http://blog.csdn.net/baidu_28312631/article/details/47749737

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!