散列查找-(三)散列函数的构造方法

博客介绍了数字和字符关键词的散列函数构造方法。数字关键词的构造方法有直接定址法、除留余数法等;字符关键词的构造方法有ASCII码加和法、前3个字符移位法等。同时指出一个好的散列函数应计算简单、地址空间分布均匀以减少冲突。

 

 


1 数字关键词的散列函数构造

 

1.1 直接定址法

取关键词的某个线性函数值为散列地址,即
h(key) = a × key + b (a、b为常数)

地址 h(key)出生年月(key人数(attribute)

0

1990

1285万

1

1991

1281万

2

1992

1280万

……

……

……

10

2000

1250万

……

……

……

21

2011

1180万

根据上表,得出散列函数的构造函数为:h(key)=key-1990

 

1.2 除留余数法

散列函数为:h(key) = key mod p
例: h(key) = key % 17

地址012345678910111213141516
关键词3418220  23742 2711 30 15 

一般,p 取素数,这里:p = Tablesize = 17

 

1.3 数字分析法

分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址
比如:取11位手机号码key的后4位作为地址:
散列函数为:h(key) = atoi(key+7) (char *key)

手机号133-0408-1767  地址是1767

 

如果关键词 key是18位的身份证号码:
在这里插入图片描述
用红线标出来的是变化比较大的的6位数字,这里就取这6位数字来组建散列函数
h1(key) = (key[6]-‘0’)×10^{^{4}} + (key[10]-‘0’)×10^{^{3}}+(key[14]-‘0’)×10^{^{2}}+ (key[16]-‘0’)×10^{^{1}}+ (key[17]-‘0’)

其他数字都不随机,

比如第一位只有1~5,代表中国的五大区

第七位只有1和2,毕竟3000年出生的人还要等一千年

第12位也只有1和2,因为一年12个月,两位数字的月数只有11和12

 

最后

当 key[18] = ‘x’时,身份证最后一位为X
h(key) = h1(key)×10 + 10

当 key[18] 为’0’~’9’时,身份证最后一位为数字
h(key) = h1(key)×10 + key[18]-‘0’

 

1.4 折叠法

把关键词分割成位数相同的几个部分,然后叠加
在这里插入图片描述

1.5 平方取中法

在这里插入图片描述

 


2 字符关键词的散列函数构造

 

2.1 一个简单的散列函数——ASCII码加和法

对字符型关键词key定义散列函数如下:
h(key) = ( Σkey[i] ) mod TableSize

问题:对于ASCII之和相同的字符串就会冲突,

比如

a3、 b2、c1;

eat、 tea;

他们加起来的值是一样的

 

2.2 简单的改进——前3个字符移位法

把字符串的前三个字符取出来,看成27进制种的个位数,十位数,百位数,为什么使用27,因为有26个字符,考录空格,就是27。
h(key)=(key[0]×27^{^{2}}+ key[1]×27 + key[2]) mod TableSize

问题:
1、仍然冲突:string、 street、strong、structure等等,前三个字符相同;

假设9个字符以内的有3000个英语单词,我们假设所有的字母(26个英文字母)排列组合有26^{_{3}} 种情况,散列表只用到30%。

2、空间浪费:3000/26^{_{3}} ≈ 30%

 

2.3 好的散列函数——移位法

涉及关键词所有n个字符,并且分布得很好:
这里就考虑这个字符串的所有位。把它看成是32进制数的每一位,为什么是32进制,后面会说。
在这里插入图片描述
快速计算:
h(“abcde”)=‘a’*32^{_{4}}+’b’*32^{_{3}}+’c’*32^{_{2}}+’d’*32+’e’

计算过程如下:
h = a
h = a * 32 + b
h = (a * 32 +b) * 32 + c
h = ((a * 32 + b) * 32 + c) * 32 + d
h = (((a * 32 + b) * 32 + c) * 32 + d) * 32 + e
h = h%TableSize
这里a * 32 == a<<5,左移5位相当于乘以32

快速计算C++代码如下:

Index Hash(const char *Key, int TableSize)
{
	unsigned int h = 0;		//散列函数值,初始化为0
	while (*Key != '\0')		//位移映射
		h = (h << 5) + *Key++;
	return h % TableSize;
}

快速计算Java代码如下:

public class Hash {

    String string;//要计算哈希值的字符串(小于等于5位)
    int tableSize= (int) Math.pow(26,5);//26的5次方的哈希表的空间
    int myhashCode=0;//初始的HashCode是0

    /*
    计算方法(散列函数,散列方法)
     */
    public int getHashCode( ){
        char[] chars=string.toCharArray();//字符串转char数组
        for (int i = chars.length-1; i>=0 ; i--) {
            myhashCode = (myhashCode<<5)+(chars[i]-'a');
        }
        return myhashCode%tableSize;
    }

    /*
    建立String的set方法
     */
    public void setString(String string) {
        this.string = string;
    }


}

 

public class MyController {
    public static void main(String[] args) {
        Hash hash=new Hash();
        hash.setString("abcde");
        hash.getHashCode();
        System.out.println("hash.tableSize = " + hash.tableSize);
        System.out.println("hashcode="+hash.myhashCode);

    }
}

控制台输出:

 

 

 

 

 

 

 

 

 

参考文章:https://blog.youkuaiyun.com/happyjacob/article/details/84878458

 

<think> 我们被要求解释哈希表(散列表)中散列函数的定义。根据引用[1]和引用[2],散列函数是将关键码值(Key value)映射到散列表中位置的函数。下面详细展开: ### 散列函数的定义 散列函数(Hash Function)是一个从关键码值(Key)到散列地址(Hash Address)的映射函数。其数学形式可表示为: $$ h: K \rightarrow A $$ 其中: - $K$ 是所有可能的关键码的集合(关键码空间), - $A$ 是散列表(哈希表)的地址集合,通常是一个连续的存储空间(索引范围从$0$到$M-1$,$M$为散列表的大小)。 ### 核心作用 散列函数的主要作用是将任意长度的输入(关键码)映射为固定长度的输出(散列地址),从而快速定位记录在散列表中的存储位置。这种映射可以显著加快查找速度,因为通过散列函数可以直接计算出存储位置,而不需要逐个比较[^1][^2]。 ### 设计要求 一个理想的散列函数应满足以下条件: 1. **计算高效**:散列函数的计算应简单快速,时间复杂度最好为$O(1)$。 2. **均匀分布**:关键码映射到散列表中每个位置的概率应尽可能相等,以减少冲突(Collision)[^4]。 - 数学上,对于任意关键码$k$,$h(k)$在$0$到$M-1$的范围内均匀分布。 3. **确定性**:相同关键码必须始终映射到相同的散列地址。 4. **抗碰撞性**(在非完美散列函数中):尽可能减少不同关键码映射到同一地址的概率[^4]。 ### 常见散列函数类型 以下是一些常见的散列函数构造方法($k$为关键码,$M$为散列表大小): 1. **除留余数法**: $$ h(k) = k \mod M $$ - 其中$M$通常选择一个质数,以改善分布均匀性[^4]。 2. **乘法散列法**: $$ h(k) = \lfloor M \cdot (k \cdot A - \lfloor k \cdot A \rfloor) \rfloor $$ - 其中$A$为一个常数($0 < A < 1$),如黄金分割数$(\sqrt{5}-1)/2 \approx 0.618$[^4]。 3. **平方取中法**: - 将关键码$k$平方后取中间几位作为散列地址。 4. **折叠法**: - 将关键码分割成几部分,然后叠加(或异或)得到散列地址。 ### 冲突处理 由于关键码空间通常远大于散列表大小$M$,冲突不可避免。常见冲突解决方法包括: 1. **开放定址法**(如线性探测、平方探测)[^4]。 2. **链地址法**(开链法):每个散列地址维护一个链表存放冲突元素(如引用[5]所述)[^5]。 ### 示例代码 以下是一个使用除留余数法和链地址法实现的散列表的散列函数示例(C语言): ```c #define HASH_TABLE_MAX_SIZE 100 typedef struct Node { char name[50]; char tel[20]; struct Node *pnext; } HSNode_t; // 散列函数:将字符串关键码映射到0到HASH_TABLE_MAX_SIZE-1的整数 int hash_function(const char *key) { unsigned long hash = 0; while (*key) { hash = hash * 31 + *key; // 31是常用的质数乘子 key++; } return hash % HASH_TABLE_MAX_SIZE; } ``` 此函数将字符串关键码(如姓名)转换为散列地址,并通过取模运算确保结果在散列表范围内[^3]。 ### 总结 散列函数散列表的核心机制,它决定了关键码到存储位置的映射关系。设计良好的散列函数能有效提升散列表的查找、插入和删除效率[^1][^2][^4]。 §§相关问题§§ 1. 散列函数中为什么常用质数作为模数? 2. 如何处理散列函数中的冲突问题? 3. 在分布式系统中,一致性哈希函数是如何工作的? 4. 散列函数的安全性如何评估,哪些场景需要加密散列函数? 5. 如何设计一个针对字符串类型关键码的高效散列函数? [^1]: 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。 [^2]: 哈希表(散列表)基础知识总结。这里我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。 [^3]: 遍历哈希表 void hash_for_each(HSNode_t **hash_table) { ... } [^4]: 深入浅出 散列(哈希表) 带实现。 [^5]: 散列表(哈希表)。散列表(hashtable)是基于“开链的做法”。 </think> ### 哈希表散列函数的定义 散列函数(Hash Function)是哈希表的核心组件,其定义为:**一个将任意长度的输入(关键码 Key)映射为固定长度输出(散列地址)的确定性函数**。数学表达为: $$ h: K \rightarrow \{0, 1, 2, \dots, M-1\} $$ 其中: - $K$:关键码的集合(如整数、字符串等) - $M$:散列表的大小(桶的数量) - $h(k)$:关键码 $k$ 对应的散列地址(桶的索引) #### 核心特性 1. **确定性** 相同关键码始终映射到相同地址,即: $$ \forall k \in K, \ h(k) \text{ 的值恒定} $$ 2. **快速计算** 时间复杂度通常为 $O(1)$ 或 $O(\text{len}(k))$,例如: ```c // 字符串关键码的散列函数示例 int hash_function(const char* key) { unsigned long hash = 0; while (*key) { hash = hash * 31 + *key; // 31是常用质数乘子 key++; } return hash % M; // 映射到桶范围 } ``` 3. **均匀分布性**(理想情况) 关键码应均匀分布在 $M$ 个桶中,减少冲突概率: $$ P\big(h(k)=i\big) \approx \frac{1}{M}, \quad \forall i \in [0, M-1] $$ #### 设计目标 1. **最小化冲突** 不同关键码映射到同一地址的概率需尽可能低: $$ P\big(h(k_1) = h(k_2)\big) \ll 1 \quad \text{当} \ k_1 \neq k_2 $$ 常用技术包括: - **除留余数法**:$h(k) = k \mod M$($M$ 取质数) - **乘法散列法**:$h(k) = \lfloor M \cdot (k \cdot A - \lfloor k \cdot A \rfloor) \rfloor$($A \approx 0.618$) 2. **覆盖全部地址空间** 函数值应覆盖 $0$ 到 $M-1$ 的所有整数。 #### 实际应用中的考虑 - **冲突处理**:当 $h(k_1) = h(k_2)$ 时,需通过开放定址法或链地址法解决(引用[4][5])。 - **动态扩容**:当负载因子(元素数/桶数)超过阈值时,需再散列(rehashing)重新分配地址(引用[4])。 - **非数值关键码**:如字符串需先转换为整数(如 ASCII 码加权求和)。 #### 示例:除留余数法 ```c #define HASH_TABLE_SIZE 97 // 质数桶大小 int hash(int key) { return key % HASH_TABLE_SIZE; // 直接取模 } ``` > **关键点**:散列函数的质量直接影响哈希表操作(插入、查找、删除)的效率。设计不当会导致聚集现象(clustering),大幅降低性能(引用[4])。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值