散列技术(哈希表)

634 阅读5分钟

定义

散列列技术是记录的存储位置和它的关键字之间建⽴立⼀一个确定的对应关系f,使得每 个关键字key对应⼀一个存储位置f(key). 查找时,根据这个对应关系找到给定值 key的映射f(key). 若查找集合中存在这个记录,则必定在f(key)的位置上.

常用的散列方法

1.直接定址法

比如,存储的数据为:1,2,3,4,5…… 则它们的存储地址可直接定址为位置:1,2,3,4,5…… 这样数据n一定在位置

数据分析法

比如: 数据:10001、10002、10003、…… 则它们的存储地址可以为10001-10000=1,10001-10000=2,10001-10000=3,…… 这样数据n的存储位置为:n-1000.

平方取中法

比如: 数据1234 2 = 1522756; 则我们可以把1234这个数据存在227的位置。

折叠法

折叠法就是将关键字从左到右分割成位数相等的⼏几部分(注意最后⼀一部分位数不不够可以稍微短 些); 然后将⼏几部分叠加求和,并按散列列表表⻓长,取后⼏几位作为散列列地址

比如: 数据 9876543210 ->分成3位一组,共4组:987 654 321 0 987+654+321+0 = 1962; 将1962的后3位:962作为散列地址

除留余数法

比如: 你要存5个数据:1,23,34,3,43 则可以将它们分享对5取余,得到存储地址,1%5 = 1,23%5 = 3,……

随机数法

比如: 你要存5个数据:1,23,34,3,43 构一个随机函数存它们的值

构造散列函数和选择散列技术时要注意:

  1. 计算公式花费时间
  2. 关键字长度;
  3. 散列表大小
  4. 关键字分布情况
  5. 记录查找概率 f(key) =random(key);

处理散列冲突

开放定址法

开放定址法就是⼀一旦发⽣生了了冲突,就去寻找下⼀一个空的散列列地址.只有散列列表⾜足够⼤大,空的散列列 地址总能找到,并将记录存⼊入.

开放定址法公式: fi (key)=(f(key)+di )Modm ; (di =1,2,3,......,m-1)

再散列列函数法

对于散列列表来说, 我们事先准备多个散列列函数:

fi(key)=RHi (key)(i=1,2,...,k) RHi 指的是不不同的散列列函数.

RHi 指的是不不同的散列列函数

多次散列,直到不冲为止

链地址法

将所有的关键字为同义词的记录存储在⼀一个单链表中,我们称为这种同义词⼦子表. 在散列列表中只 存储所有同义词⼦子表的头指针(头地址).

比如:将1,21,6,4,8,9

公共溢出法

关键字集合 { 12,67,56,16,25,37,22,29,15,47,48,34 } 表⻓长为12, 我们⽤用散列列函数 f ( key ) = key mod 12;

例如:12,48,用除留余数法得到的地址都是0,这时,我们可以构造两个散列表,一个基本表,在其0的位置存12;一个溢出表,在其0位置存48,以此类推。

散列表查找实现

设计散列表结构

#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; /* 散列表表长,全局变量 */

初始化散列表

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;
}

设计散列函数

int Hash(int key)
{
    //除留余数法
    return key % m;
}

插入关键字到散列表

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

在散列表中查找关键字

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;
}