数据结构与算法----散列/哈希

1. 简介

  散列表的实现叫散列hashing,散列用于以常数平均时间执行 插入、删除、查找,不支持排序、findMin、findMax。

  查找关键字不需要 比较

  在一个记录的存储位置和它的关键字之间建立映射关系:key--f(key)   这个关系就是散列函数/哈希函数。将一些记录存储在一块 连续 的存储空间,这块空间就是散列表/哈希表。

  与线性表、树、图比较:

    数据元素之间没有什么逻辑关系,也不能用连线图表示出来。

  问题:

    关键字不同,但通过散列函数计算的结果相同,即出现了冲突 collision,这两个关键字就是这个散列函数的同义词 synonym

2. 散列函数

2.1 设计原则

  计算简单

  散列地址分布均匀

2.2

  计算散列地址所需时间

  关键字长度

  散列表大小

  关键字分布情况

  记录查找的频率

2.3 常用方法

(1)直接定址

  线性函数:f(key) = a*key + b (a、b为常数)

  特点:简单、均匀

  适合:要实现知道关键字的分布情况,适合表较小且连续的情况

(2)数字分析

  根据关键字特点,抽取关键字的一部分来计算散列存储位置

  适合:关键字位数比较大,事先知道关键字的分布且关键字的若干位分布均匀

(3)平方取中

  关键字平方,再取中间的几位

  适合:不知道关键字的分布,而位数不大的情况

(4)折叠法

  将关键字从左到右分割成位数相等的几部分,再将这几部分叠加求和,并按散列表表长取后几位作为散列地址

  例:散列表长3位,关键字9876543210

    将关键字分成四组,叠加求和987+654+321+0=1962   取后3位 962 作为散列地址

    不够均匀的话,可折叠某几个数再叠加:789+654+123+0=1566 取566作为散列地址

  适合:不知道关键字的分布,位数较多

(5)除留余数法

  已知散列表长为m, f(key) = key mod p (p≤m)

  也可以先对key折叠、平方取中等之后再除留余数

  p的选择:小于等于表长的最小质数 或 不包含小于20质因子的合数

(6)随机数法

  f(key) = random(key)

  适合关键字长度不等的情况

  关键字是字符串时:转化为数字,如ASCII码、Unicode码

3. 处理散列冲突

(1)开放定址法/线性探测法

  当发生了冲突的时候就去寻找下一个空的散列地址,只要散列表够大,总能找到空的散列地址。

    fi(key) = (f(key) + di) mod m     (di=1,2...m-1)

  问题:key2因为与key1冲突,找到空的散列地址存储数据,但是key3本来和key2不是同义词,key3的位置却被key2占了,所以又需要为key3再找位置,甚至可能导致冲突不断出现。即出来了堆积。

  不足:当前散列函数只能在冲突发生时往下一个地址寻址,不能往前,因此可让 d为负值,并且让步进的间隔大点,不让关键字聚集在某一块区域。

  改进:二次探测法

    fi(key) = (f(key) + di) mod m   (di=12,-12,22,-22...q2,-q2   q≤m/2)

    

  另一种:di用随机函数计算得到------随机探测法

  

  特点:在散列表未满时,总能找到不发生冲突的地址

(2)再散列函数法

  当发生冲突时,就换一个散列函数计算

(3)链地址法

  发生冲突时,不换地方存储,而是在原地址用链表存储。即将所有关键字为同义词的记录存储在一个链表中。

(4)公共溢出区法

  为所有冲突的关键字建立一个公共的溢出区存放。

  

4. 装填因子α

  α=填入表中的记录个数/散列表长度

  装填因子越大,产生冲突的可能性就越大。

  不产生冲突时,查找的时间复杂度为O(1)

5. 散列表的简单实现

《大话数据结构》里面的代码

#define HASHSIZE 12
typedef struct {
    int *elem;
    int count; //当前数据元素个数
}MyHashTable;

初始化:

bool initHashTable(MyHashTable *h)
{
    h->count = 0;
    h->elem = (int *)malloc(HASHSIZE*sizeof(int));
    for(int i=0;i<HASHSIZE;i++) {
        h->elem[i] = -32768;
    }
    return true;
}

散列函数:

static int MyHash(int key)
{
    return key % HASHSIZE;
}

插入元素:

void insertHash(MyHashTable *h,int key)
{
    int addr = MyHash(key);  //求得散列地址
    while(h->elem[addr] != -32768) {
        addr = (addr+1) % HASHSIZE; //开放定址法的线性探测
    }
    h->elem[addr] = key;
    (h->count)++;
}

查找:

int searchHash(MyHashTable h,int key,int *addr)
{
    *addr = MyHash(key);
    while(h.elem[*addr] != key) {   //当找到了这个key则退出循环
        *addr = (*addr + 1) % HASHSIZE;
        if(*addr == MyHash(key)) {
            //循环到了原来的地方  说明整个表里没有这个key
            return -1;
        }
    }
    return *addr;
}

原文地址:https://www.cnblogs.com/taoXiang/p/12329491.html

时间: 2024-07-30 09:13:36

数据结构与算法----散列/哈希的相关文章

JavaScript数据结构与算法-散列练习

散列的实现 // 散列类 - 线性探测法 function HashTable () { this.table = new Array(137); this.values = []; this.simpleHash = simpleHash; this.betterHash = betterHash; this.showDistro = showDistro; this.put = put; this.get = get; } function put (key, data) { let pos

算法----散列

/* * 算法--散列 * 散列表是基于数组进行设计的.数组的长度是预先设定的 * 所有元素根据和该元素对应的键,保存在数组的特定位置,该键和我们前面讲到的字典中的键是类似的概念 * 使用散列表存储数据时,通过一个散列函数将键映射为一个数字,这个数字的范围是 0 到散列表的长度. * * 这里所说的键 应该就是数组的索引 只不过 数组既然长度确定了,那么索引的个数也是确定了 * 每个元素是无限的,每个元素的键值映射出来的索引也许就会发生重复或说碰撞. 这个叫有效碰撞. * * 键值映射到数组的索

【数据结构】之散列链表(Java语言描述)

散列链表,在JDK中的API实现是 HashMap 类. 为什么HashMap被称为"散列链表"?这与HashMap的内部存储结构有关.下面将根据源码进行分析. 首先要说的是,HashMap中维护着的是一个数组: transient Node<K,V>[] table; ,数组中的每个元素都是一个 Node 对象.这里的Node是HashMap的一个内部类,代码如下: static class Node<K,V> implements Map.Entry<

数据结构和算法: 散列表

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构.也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度.这个映射函数称做散列函数,存放记录的数组称做散列表 散列表的时间复杂度不是严格的O(1), 因为和多种因素有关, 比如散列函数, 还有就是如果采用链表法处理冲突, 那么最坏情况是所有数据都散列到一个链表中, 此时是O(n). hash函数有以下几种简单实现的方法 取余法 常见的对一个数进行取余操作

数据结构与算法——散列表类的C++实现(分离链接散列表)

散列表简介: 散列表的实现常被称为散列.散列是一种用于以常数平均时间执行插入.删除和查找的技术. 散列的基本思想: 理想的散列表数据结构只不过是一个包含一些项的具有固定大小的数组.(表的大小一般为素数) 设该数组的大小为TbaleSize,我们向该散列表中插入数据,首先我们将该数据用一个函数(散列函数)映射一个数值x(位于0到TbaleSize1-1之间):然后将该数据插入到散列表的第x的单元.(如果有多个数据映射到同一个数值,这个时候就会发生冲突) 散列函数介绍: 为了避免散列函数生成的值不是

散列+哈希

散列法: #include <cstdio> #include <iostream> #include <string.h> using namespace std; const int N = 100010, null = 0x3f3f3f3f; int h[N], e[N], ne[N], idx; void insert(int x) { int k = (x % N + N) % N; e[idx] = x; ne[idx] = h[k]; h[k] = idx

数据结构与算法07 之哈希表

哈希表也称为散列表,是根据关键字值(key value)而直接进行访问的数据结构.也就是说,它通过把关键字值映射到一个位置来访问记录,以加快查找的速度.这个映射函数称为哈希函数(也称为散列函数),映射过程称为哈希化,存放记录的数组叫做散列表.比如我们可以用下面的方法将关键字映射成数组的下标:arrayIndex = hugeNumber % arraySize. 哈希化之后难免会产生一个问题,那就是对不同的关键字,可能得到同一个散列地址,即同一个数组下标,这种现象称为冲突,那么我们该如何去处理冲

Redis数据操作--字符串与散列键的区别

| 如果散列键能做的事情,字符串键也能做,那么 我们为什么不直接使用字符串键呢? | 散列的好处 -- 将数据放在同一个地方     # 散列可以让我们将一些相关的信息储存在同一个额地方,而不是直接分散地     储存在整个数据中里面,这不仅方便了数据管理,还可以尽量避免误操作发生.     # 举个例子,要删除字符串键记录的消息信息,我们需要输入三个键,而删除     散列键储存的消息信息,我们只要输入一个键     字符串键:id >> 10086; sender >> pet

散列算法和哈希表结构

散列算法和哈希表结构 算法概述 Hash ,一般翻译做" 散列" ,也有直接音译为" 哈希" 的,就是把任意长度的输入(又叫做预映射, pre-image ),通过散列算法,变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不 同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值.简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数. 哈希表 数组的特点是:寻址容易,插入和删除困难: