1 散列存储(hash)
班级名称
班级编号 | 关键字 |
---|---|
20011 | 1 |
20021 | 2 |
20031 | 3 |
20041 | 4 |
20051 | 5 |
在元素的存储位置和其关键字之间建立某种直接的联系。(1V1)
关键字: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岁的人口数字统计表。
地址 | 年龄 | 出生年份 | 人数 |
---|---|---|---|
00 | 0 | 2021 | 500万 |
01 | 1 | 2020 | 600万 |
02 | 2 | 2019 | 450万 |
··· | ···· | ···· | ······· |
20 | 20 | 2001 | 1500万 |
······ | ······ | ······ | ··········· |
关键字:出生年份 --- 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质因子的合数
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 25 | 38 | 15 | 16 | 29 | 78 | 67 | 56 | 21 | 22 | 47 |
思考:p的值应该为多少?p=12
下标 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 | 108 | 120 | 132 |
答案:p=11;
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 | 108 | 120 | 132 | 144 |
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 散列表生成
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 | 108 | 120 | 132 | 144 |
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]++;//字符个数++ } }