查找---散列表

1 散列存储(hash)

班级名称

班级编号关键字
200111
200212
200313
200414
200515

在元素的存储位置和其关键字之间建立某种直接的联系。(1V1)

1614145621975

 关键字:key
 散列地址:p(即数组下标)
 散列函数:H(key)对元素的关键字进行某种运算,直接求出元素的地址。
 散列表:又称哈希表(hash table),一个连续的地址空间,用以存储按散列函数计算得到的相应散列地址的数据记录

2 散列表查找

直接通过关键字找到结果,不需要比较。

我们只需要通过某个函数使得

 存储位置=f(关键字)

散列技术:是在记录的存储位置和其关键字之间建立一个确定的 对应关系f,使得每个关键字key对应一个存储位置f(key);查找时,根据这个确定的对应关系找到给定值key的映射f(key)。

若查找集合中存在这个记录,则必定在f(key)的位置上。

对应关系f称为散列函数,又称为哈希函数。

采用散列技术将记录存储在一块连续的 存储空间中,这块连续存储空间称为散列表或者哈希表。

散列技术:既是存储方法,也是查找方法。

冲突:

有两个关键字key1不等于key2,但是却有f(key1)=f(key2);这种现象我们称之为冲突,并把key1和key2称为这个散列函数的同义词。

缺陷:无逻辑关系,无法“连线”

多对一的映射,冲突不可避免。只能通过“好”的散列技术减少。

示例:

 a[26]={a,····,z};
 H(key)=key-'a';

2.1 散列构造

2.1.1 直接定址法

如果我们现在要对0-100岁的人口数字统计表。

地址年龄出生年份人数
0002021500万
0112020600万
0222019450万
··················
202020011500万
·····························

关键字:出生年份 --- H(key)=2021 - key

年龄 --- H(key)=key

示例:

 #include<stdio.h>
 #define MAX_SIZE 5
 int main()
 {
     int age_num[MAX_SIZE];
     int i ,age ,num;
     for(i=0;i<MAX_SIZE;i++)
     {
         scanf("%d%d",&age,&num);//获取年龄和出生年月
         set_hash(age_num,age,num);
     }
     printf("input select age:");
     scanf("%d",&age);
     //查找
     printf("**:%d\n",select_hash(age_num ,age));
 }
 ​
 int select_hash(int *num, int age)
 {
     int pos;
     pos = hash_fun(age);
     if(pos<0 || pos>=MAX_SIZE)
     {
         printf("not find\n");
         return -1;
     }
     return num[pos];
 }
 void set_hash(int *p,int age,int num)
 {
     int pos;//地址信息
     pos = hash_fun(age);//地址的hash()函数
     //根据关键字,确定取存储位置
     p[pos] = num;//存储信息
 }
 int hash_fun(int key)
 {
     return key-1;//直接定址法//年龄-1
 }
 ​

2.1.2 数字分析法

快递单号,手机号码,学生学号,身份证号

11位手机号:3位接入号(运营商)+4位HLR识别号(表示用户归属地)+4位用户号

现在要登记南京本地用户的移动用户,可以抽取手机后4位---用户号,作为散列地址,以减少冲突。

数字分析法:通常适合处理关键字位数比较大,且事先知道关键字的分布规律

2.1.3 平方取中法

不知道关键字分布,关键字位数不多的情况。

示例:key=1234

key^2=1522756

再抽取中间三位 ,得到H(1234)=227

2.1.4 折叠法

将关键字从左到右分割成位数相等的几部分(最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按照散列表表长,取后几位作为散列地址。

示例:key=9876543210;

分组:987|654|321|0

叠加:987+654+321+0=1962

再取后三位作为散列地址,H(9876543210)=962;

若不能保证均匀分布,还可以进行数字逆序,如987变成789 321变成123

789+654+123+0=1566 ,取后三位566作为散列地址

折叠法适合不知道关键字分布,且关键字位数较多的情况

2.1.5 除留余数法

此方法为常用的构造散列函数的方法。对于散列表长为m的散列函数公式为

 f(key)=key mod  p (p<=m);
 如果p设置不好,会产生同义词
 尽量选择:接近m的最小质数,或者不包含小于20质因子的合数

下标01234567891011
关键字122538151629786756212247

思考:p的值应该为多少?p=12

下标
关键字1224364860728496108120132

答案:p=11;

下标1234567891001
关键字1224364860728496108120132144

2.1.6 随机数法

选择一个随机数,取关键字的随机函数值作为其散列地址。

 f(key)=random(key)
 适用于:当关键字不等长的时候

字符串及其他数据,都可以转换为数字对待(ASCII码或者Unicode编码)

2.2 处理冲突

2.2.1 公共溢出区法

建立公共的溢出区来存放产生冲突的关键字

2.2.2 链地址法

将散列地址冲突的数据,用链表的形式存储。

访问时,需要链表遍历

2.3 散列表实现

2.3.1 散列表存储结构

 #define SUCCESS  1
 #define FAILED   0
 #define HASHSIZE 12
 #define NULLKEY  -10086//不可能的数
 typedef struct  
 {
     int *elem;//数据元素存储基址,动态分配数组
     int count;//当前数据元素个数
 }hash_table;

2.3.2 初始化散列表

 status  init_hash_table(hash_table *h)
 {
     int i;
     h->count=HASHSIZE;
     h->elem=(int *)malloc(HASHSIZE*sizeof(int));
     for(i=0;i<m;i++)
         h->elem[i]=NULLKEY;
     return 0;
 }

2.3.3 散列函数

 int hash(int key)
 {
     int m=11;
     return key%m;//除留余数法,m由程序员自行确定
 }

2.3.4 散列表生成

下标1234567891001
关键字1224364860728496108120132144
 void  insert_hash(hash_table *h,int *bak,int key)
 {
     int pos;
     int i=bak[0];//元素个数;
     pos=hash(key);
     if(h->elem[pos]==NULLKEY)
         h.elem[pos]=key;
     else 
     {
         bak[i+1]=key;
         bak[0]=i+1;//元素个数+1
     }   
 }

2.3.5 散列查找

 typedef struct
 {
     int pos;
     int *tab;
 }hash_addr;
 status ser_hash(hash_table *h,int key,hash_addr *addr ,int *bak)
 //哈希表首地址:
 //地址:
 {
     int i=1;
     addr->pos=hash(key);//关键字确定位置
     if(h->elem[addr->pos]==key) //对应位置的元素等于关键字
     {
             addr->tab=h;//存储表名
             return SUCCESS;
     }
      else//查找备用表
         {
             while(i<=bak[0]) //i<元素个数
             {
             if(bak[i]==key) //找到
                 {
                     addr->pos=i; //存储下标
                     addr->tab=bak;//存储备用表表明
                     return SUCCESS;
                 }
                 i++;//否则:i++ 继续比较
             }
         }
     return FAILED;
 }

2.4 练习---众数

一个字符串中可能包含a-z中的多个字符,字符也可能重复 例如char a[] = "dfafdafdafdafadfdafjkadfdafdafiofidafefadfa" 求字符串中出现次数最多的那个字母以及出现的次数,若次数最多的字母有多个,则全部求出。

 #define MAX_SIZE 26
 int main()
 {
     int i, max = 0;
     char a[] = "dfafdafdafdafadfdafjkadfdafdafiofidafefadfa";
     int hash_link[MAX_SIZE] = {0};//存储每个字符的个数
     set_hash(hash_link, a);
     for(i=0;i<MAX_SIZE;i++)
     {
         if(max < hash_link[i])
             max = hash_link[i];//find   max
     }
     for(i=0;i<MAX_SIZE;i++)//若存在多个则全部打印
     {
         if(hash_link[i] == max)
             printf("%c %d\n",i+'a',hash_link[i]);
     }
 }
 int hash_fun(char key)
 {
     int pos;
     pos = key - 'a';//散列地址
     return pos;
 }
 void set_hash(int *link,char *dat)
 {
     int pos;
     while(*dat != '\0')//遍历字符串
     {
         pos = hash_fun(*dat);//确定散列位置
         link[pos]++;//字符个数++
     }
 }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值