【数据结构】散列表(hash)的查找技术

本文深入探讨了散列技术,包括散列函数的构造方法如直接定址法、除留余数法、数字分析法等,以及冲突处理策略如开放定址法、链地址法等。此外,还详细讨论了散列函数设计的关键问题和冲突处理方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 散列函数的构造
    直接定址法
    除留余数法
    数字分析法
    平方取中法
    折叠法(分段叠加法)
  • 冲突处理方法
    开放定址法
    链地址法
    建立公共溢出区

散列的基本思想:在记录的存储地址和它的关键码之间建立一个确定的对应关系。这样,不经过比较,一次读取就能得到所查元素的查找方法。

基本概念

1.散列表:采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为散列表。
2.散列函数:将关键码映射为散列表中适当存储位置的函数。
3.散列地址:由散列函数所得的存储位置址 。
4.冲突:对于两个不同关键码ki≠kj,有H(ki)=H(kj),即两个不同的记录需要存放在同一个存储位置,ki和kj相对于H称做同义词。

  • 散列既是一种查找技术,也是一种存储技术。

  • 散列只是通过记录的关键码定位该记录,没有完整地表达记录之间的逻辑关系,所以,散列主要是面向查找的存储结构。

  • 散列技术一般不适用于允许多个记录有同样关键码的情况。
    有冲突,降低了查找效率,体现不出计算式查找的优点

  • 散列方法也不适用于范围查找
    不能查找最大值、最小值
    也不可能找到在某一范围内的记录。

散列技术的关键问题:
⑴ 散列函数的设计。如何设计一个简单、均匀、存储利用率高的散列函数。
⑵ 冲突的处理。如何采取合适的处理冲突方法来解决冲突。

散列函数的设计

直接定址法

散列函数是关键码的线性函数,即:H(key) = a* key + b (a,b为常数)
事先知道关键码,关键码集合不是很大且连续性较好。

除留余数法

H(key)=key mod p
一般情况下,选p为小于或等于表长(最好接近表长)的最小素数

数字分析法

根据关键码在各个位上的分布情况,选取分布比较均匀的若干位组成散列地址。

平方取中法

对关键码平方后,按散列表大小,取中间的若干位作为散列地址(平方后截取)。

折叠法

将关键码从左到右分割成位数相等的几部分,将这几部分叠加求和,取后几位作为散列地址。

冲突的处理

开散列方法( open hashing,也称为拉链法,separate chaining ,链地址法)
闭散列方法( closed hashing,也称为开地址方法,open addressing ,开放定址法)
建立公共溢出区

开放地址法

由关键码得到的散列地址一旦产生了冲突,就去寻找下一个空的散列地址,并将记录存入。
寻找下一个空的散列地址
(1)线性探测法
(2)二次探测法
(3)随机探测法
(4)再hash法

线性探测法

当发生冲突时,从冲突位置的下一个位置起,依次寻找空的散列地址。
对于键值key,设H(key)=d,闭散列表的长度为m,则发生冲突时,寻找下一个散列地址的公式为:
Hi=(H(key)+di) % m (di=1,2,…,m-1)

二次探测法

当发生冲突时,寻找下一个散列地址的公式为:
Hi=(H(key)+di)% m
(di=12,-12,22,-22,…,q2,-q2且q≤m/2)

随机探测法

当发生冲突时,下一个散列地址的位移量是一个随机数列,即寻找下一个散列地址的公式为:
Hi=(H(key)+di)% m
(di是一个随机数列,i=1,2,……,m-1)

拉链法(链地址法)

基本思想:将所有散列地址相同的记录,即所有同义词的记录存储在一个单链表中(称为同义词子表),在散列表中存储的是所有同义词子表的头指针。
用拉链法处理冲突构造的散列表叫做开散列表。
设n个记录存储在长度为m的散列表中,则同义词子表的平均长度为n / m。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

### 数据结构散列表 #### 查找算法 散列表是一种基于键值映射的数据结构,其核心思想是通过哈希函数将关键字直接映射到存储位置,从而实现快速查找。相比传统的线性查找或二分查找散列表能够在理想情况下达到 O(1) 的时间复杂度。 查找过程的核心在于利用哈希函数计算目标关键字对应的存储地址,并验证该地址处的元素是否匹配目标关键字。如果发生冲突,则需进一步处理冲突情况[^4]。 #### 构造 构建一个高效的散列表涉及以下几个方面: 1. **选择合适的哈希函数** 哈希函数的设计直接影响散列表性能的好坏。一个好的哈希函数应满足以下条件: - 能均匀分布数据,减少冲突概率。 - 易于计算,降低开销。 常见的哈希函数包括留余数、平方取中等。例如,留余数可以表示为 \( H(k) = k \mod m \),其中 \( m \) 是表长[^3]。 2. **解决冲突的方** 即使选择了优秀的哈希函数,仍然无完全避免冲突的发生。以下是两种常见的冲突解决方案: - **开放定址** 开放定址的基本思想是在发生冲突时寻找下一个可用的位置。常用的技术有线性探测、二次探测和双重哈希。例如,\( H_i = (H(k) + d_i) \mod m \)。 - **拉链** 拉链通过在每个桶位维护一个链表来存储具有相同哈希值的元素。这种方能够有效应对大量冲突的情况,但会增加额外的空间开销[^4]。 #### 哈希表实现 下面是一个简单的 C 语言实现示例,展示了如何构造和查询散列表: ```c #include <stdio.h> #include <stdlib.h> #define TABLE_SIZE 1000 #define NULLKEY -65535 typedef struct { int key; } Element; typedef struct { Element *elem; int size; } HashTable; // 初始化哈希表 void initHashTable(HashTable *ht) { ht->size = TABLE_SIZE; ht->elem = (Element *)malloc(sizeof(Element) * TABLE_SIZE); for (int i = 0; i < TABLE_SIZE; ++i) { ht->elem[i].key = NULLKEY; } } // 插入操作(简单版) void insertKey(HashTable *ht, int key) { int index = abs(key) % TABLE_SIZE; while (ht->elem[index].key != NULLKEY && ht->elem[index].key != key) { index = (index + 1) % TABLE_SIZE; // 线性探测 } if (ht->elem[index].key == NULLKEY || ht->elem[index].key == key) { ht->elem[index].key = key; } } // 查询操作 int searchKey(const HashTable *ht, int key) { int index = abs(key) % TABLE_SIZE; int start_index = index; do { if (ht->elem[index].key == key) { return index; // 成功返回索引 } index = (index + 1) % TABLE_SIZE; } while (ht->elem[index].key != NULLKEY && index != start_index); return -1; // 失败返回-1 } int main() { HashTable hash_table; initHashTable(&hash_table); insertKey(&hash_table, 123); insertKey(&hash_table, 456); printf("Search result for 123: %d\n", searchKey(&hash_table, 123)); printf("Search result for 789: %d\n", searchKey(&hash_table, 789)); free(hash_table.elem); // 清理资源 return 0; } ``` 以上代码实现了散列表的基本功能,包括初始化、插入和查询操作。注意,在实际应用中还需要考虑删操作以及动态调整表大小等问题[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值