解决Hash冲突的四种方法(含代码解析)

目录

1 引言

2 开放定址法

2.1 线性探测法

2.2 二次探测法

2.3 双重哈希法

3 链地址法

4 再哈希法

5 建立公共溢出区

6 总结


1 引言

在现代计算机系统中,哈希表(Hash Table)因其高效的查找、插入和删除性能而被广泛应用于数据库索引、缓存系统、编译器符号表等场景。然而,由于哈希函数将无限多的输入映射到有限的地址空间,不可避免地会出现哈希冲突(Hash Collision)——即不同的键映射到了相同的位置。如何优雅、高效地解决这些冲突,是哈希表设计中的关键问题。本文将详细介绍解决哈希冲突的四种经典方法:开放定址法、链地址法、再哈希法和建立公共溢出区,分析它们的原理、优缺点及适用场景,并给出对比总结,帮助读者深入理解哈希表冲突处理机制。

2 开放定址法

开放定址法(Open Addressing)是一种解决哈希冲突的策略,其核心思想是在哈希表中继续“探查”其他空位以安置冲突元素。当插入的元素与已有元素发生哈希地址冲突时,不是另开空间,而是在哈希表内部按照一定的探查序列(如线性探测、二次探测或双重哈希)寻找下一个可用槽位。插入、查找和删除操作都基于相同的探查逻辑,以保持一致性。这种方法避免了额外的链表结构,占用空间较少,但在负载因子较高时容易出现“聚集”现象,影响查找效率。

2.1 线性探测法

线性探测法(Linear Probing)是开放定址法中最简单的一种形式,其原理是在哈希冲突发生时,按照固定的步长(通常为1)依次向后探查下一个位置,直到找到一个空槽为止。即如果元素 key 的初始哈希地址为 h(key),发生冲突时就尝试 h(key)+1h(key)+2、……,直到遇到空位为止。查找和删除时也采用相同的线性序列进行探测,确保操作的一致性。该方法实现简单,但容易形成“聚集”现象(即冲突元素聚集在一起),影响性能。

#include <stdio.h>
#include <stdlib.h>

#define TABLE_SIZE 10
#define EMPTY -1

// 哈希表结构
int hashTable[TABLE_SIZE];

// 初始化哈希表
void initHashTable()
{
    for (int i = 0; i < TABLE_SIZE; i++)
    {
        hashTable[i] = EMPTY;
    }
}

// 哈希函数
int hash(int key)
{
    return key % TABLE_SIZE;
}

// 插入函数(线性探测法)
void insert(int key)
{
    int index = hash(key);
    int originalIndex = index;
    int i = 0;

    while (hashTable[index] != EMPTY)
    {
        i++;
        index = (originalIndex + i) % TABLE_SIZE;
        if (index == originalIndex)
        {
            printf("Hash table is full, cannot insert %d\n", key);
            return;
        }
    }
    hashTable[index] = key;
    printf("Inserted %d at index %d\n", key, index);
}

// 查找函数(线性探测)
int search(int key)
{
    int index = hash(key);
    int originalIndex = index;
    int i = 0;

    while (hashTable[index] != EMPTY)
    {
        if (hashTable[index] == key)
        {
            return index;
        }
        i++;
        index = (originalIndex + i) % TABLE_SIZE;
        if (index == originalIndex) break;
    }
    return -1; // 未找到
}

// 打印哈希表
void printHashTable()
{
    printf("Hash Table:\n");
    for (int i = 0; i < TABLE_SIZE; i++)
    {
        if (hashTable[i] != EMPTY)
            printf("[%d]: %d\n", i, hashTable[i]);
        else
            printf("[%d]: (empty)\n", i);
    }
}

int main()
{
    initHashTable();

    insert(12);
    insert(22);
    insert(32);
    insert(42);
    insert(52);

    printHashTable();

    int key = 32;
    int result = search(key);
    if (result != -1)
        printf("Found %d at index %d\n", key, result);
    else
        printf("%d not found in hash table\n", key);

    return 0;
}

2.2 二次探测法

二次探测法(Quadratic Probing)是开放定址法的一种改进策略,用于解决哈希冲突。当发生冲突时,不是简单地顺序往后查找(如线性探测),而是按照二次函数间隔探测下一个位置,避免“主聚集”现象。

公式:

hi = (h(key) + c1*i + c2*i^2) % m

其中:

  • h(key) 是基本哈希函数

  • i 是探测次数(从 0 开始)

  • c1c2 是常数,一般可取 c1 = c2 = 1

  • m 是哈希表大小

#include <stdio.h>
#include <stdlib.h>

#define TABLE_SIZE 10
#define EMPTY -1

int hashTable[TABLE_SIZE];

// 初始化哈希表
void initHashTable() {
    for (int i = 0; i < TABLE_SIZE; i++) {
        hashTable[i] = EMPTY;
    }
}

// 哈希函数
int hash(int key) {
    return key % TABLE_SIZE;
}

// 插入函数(二次探测)
void insert(int key) {
    int index = hash(key);
    int i = 0;

    while (hashTable[(index + i * i) % TABLE_SIZE] != EMPTY) {
        i++;
        if (i == TABLE_SIZE) {
            printf("Hash table is full, cannot insert %d\n", key);
            return;
        }
    }

    int finalIndex = (index + i * i) % TABLE_SIZE;
    hashTable[finalIndex] = key;
    printf("Inserted %d at index %d\n", key, finalIndex);
}

// 查找函数(二次探测)
int search(int key) {
    int index = hash(key);
    int i = 0;

    while (hashTable[(index + i * i) % TABLE_SIZE] != EMPTY) {
        int curIndex = (index + i * i) % TABLE_SIZE;
        if (hashTable[curIndex] == key) {
            return curIndex;
        }
        i++;
        if (i == TABLE_SIZE) break;
    }
    return -1; // 未找到
}

// 打印哈希表
void printHashTable() {
    printf("Hash Table:\n");
    for (int i = 0; i < TABLE_SIZE; i++) {
        if (hashTable[i] != EMPTY)
            printf("[%d]: %d\n", i, hashTable[i]);
        else
            printf("[%d]: (empty)\n", i);
    }
}

int main() {
    initHashTable();

    insert(10);
    insert(20);
    insert(30);
    insert(25);
    insert(35);
    insert(15);

    printHashTable();

    int key = 25;
    int result = search(key);
    if (result != -1)
        printf("Found %d at index %d\n", key, result);
    else
        printf("%d not found in hash table\n", key);

    return 0;
}

 2.3 双重哈希法

双重哈希法(Double Hashing)是一种开放定址法的高级策略,利用两个不同的哈希函数来解决冲突。发生冲突时,用第二个哈希函数生成探测步长,按该步长跳跃式地寻找下一个可用槽位。其探测公式如下:

hi = (h1(key) + i * h2(key)) % m

其中 h1(key) 是主哈希函数,h2(key) 是第二哈希函数,i 是第 i 次冲突。与线性探测和二次探测相比,双重哈希更能有效避免聚集问题,具有更好的查找性能。

// 双重哈希法

#include <stdio.h>
#include <stdlib.h>

#define TABLE_SIZE 11
#define EMPTY -1

int hashTable[TABLE_SIZE];

// 主哈希函数
int h1(int key) {
    return key % TABLE_SIZE;
}

// 第二哈希函数(确保返回值不为0)
int h2(int key) {
    return 7 - (key % 7);  // 7 应小于 TABLE_SIZE 且为素数
}

// 初始化哈希表
void initHashTable() {
    for (int i = 0; i < TABLE_SIZE; i++) {
        hashTable[i] = EMPTY;
    }
}

// 插入函数(双重哈希)
void insert(int key) {
    int index = h1(key);
    int step = h2(key);
    int i = 0;

    while (hashTable[(index + i * step) % TABLE_SIZE] != EMPTY) {
        i++;
        if (i == TABLE_SIZE) {
            printf("Hash table is full, cannot insert %d\n", key);
            return;
        }
    }

    int finalIndex = (index + i * step) % TABLE_SIZE;
    hashTable[finalIndex] = key;
    printf("Inserted %d at index %d\n", key, finalIndex);
}

// 查找函数(双重哈希)
int search(int key) {
    int index = h1(key);
    int step = h2(key);
    int i = 0;

    while (hashTable[(index + i * step) % TABLE_SIZE] != EMPTY) {
        int curIndex = (index + i * step) % TABLE_SIZE;
        if (hashTable[curIndex] == key) {
            return curIndex;
        }
        i++;
        if (i == TABLE_SIZE) break;
    }
    return -1;
}

// 打印哈希表
void printHashTable() {
    printf("Hash Table:\n");
    for (int i = 0; i < TABLE_SIZE; i++) {
        if (hashTable[i] != EMPTY)
            printf("[%d]: %d\n", i, hashTable[i]);
        else
            printf("[%d]: (empty)\n", i);
    }
}

int main() {
    initHashTable();

    insert(10);
    insert(22);
    insert(31);
    insert(4);
    insert(15);
    insert(28);
    insert(17);
    insert(88);
    insert(59);

    printHashTable();

    int key = 28;
    int result = search(key);
    if (result != -1)
        printf("Found %d at index %d\n", key, result);
    else
        printf("%d not found in hash table\n", key);

    return 0;
}

3 链地址法

链地址法(Separate Chaining)是一种解决哈希冲突的经典方法。其核心思想是:将哈希表的每个槽位扩展为一个链表(或其他结构),当多个键哈希到同一个槽位时,依次插入到该槽位的链表中。这种方式允许多个元素共享同一哈希地址,插入和删除操作较为灵活,且不受负载因子的限制。尽管查找效率在冲突较多时会下降,但链地址法结构简单、易于实现,是实际应用中非常常见的哈希冲突解决方案。

// 链地址法

#include <stdio.h>
#include <stdlib.h>

#define TABLE_SIZE 10

// 链表节点
typedef struct Node {
    int key;
    struct Node* next;
} Node;

// 哈希表数组(每个元素是链表头指针)
Node* hashTable[TABLE_SIZE];

// 哈希函数
int hashFunction(int key) {
    return key % TABLE_SIZE;
}

// 插入函数
void insert(int key) {
    int index = hashFunction(key);
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->key = key;
    newNode->next = hashTable[index];  // 插入到链表头部
    hashTable[index] = newNode;
    printf("Inserted %d at index %d\n", key, index);
}

// 查找函数
int search(int key) {
    int index = hashFunction(key);
    Node* current = hashTable[index];
    while (current != NULL) {
        if (current->key == key) return 1;
        current = current->next;
    }
    return 0;
}

// 打印哈希表
void printHashTable() {
    printf("Hash Table:\n");
    for (int i = 0; i < TABLE_SIZE; i++) {
        printf("[%d]:", i);
        Node* current = hashTable[i];
        while (current != NULL) {
            printf(" -> %d", current->key);
            current = current->next;
        }
        printf(" -> NULL\n");
    }
}

// 释放内存
void freeHashTable() {
    for (int i = 0; i < TABLE_SIZE; i++) {
        Node* current = hashTable[i];
        while (current != NULL) {
            Node* temp = current;
            current = current->next;
            free(temp);
        }
        hashTable[i] = NULL;
    }
}

int main() {
    // 初始化哈希表
    for (int i = 0; i < TABLE_SIZE; i++) {
        hashTable[i] = NULL;
    }

    // 插入元素
    insert(10);
    insert(20);
    insert(30);
    insert(15);
    insert(25);

    printHashTable();

    // 查找元素
    int key = 15;
    if (search(key)) {
        printf("%d found in hash table.\n", key);
    } else {
        printf("%d not found in hash table.\n", key);
    }

    // 释放内存
    freeHashTable();
    return 0;
}

4 再哈希法

再哈希法(Rehashing)是一种通过使用多个不同的哈希函数来解决哈希冲突的方法。当一个元素在主哈希函数(如 h1(key))位置发生冲突后,系统会尝试依次使用其他哈希函数(如 h2(key)h3(key))重新计算位置,直到找到一个空槽。相比开放定址法中单一线性或二次探测的方式,再哈希法通过“多函数多路径”减少聚集问题,冲突分布更均匀,从而提高哈希表性能。不过,再哈希法实现上要求设计多个良好的哈希函数,增加了编程复杂度。

这里我们采用 双重哈希作为再哈希的代表:

  • h1(key) = key % TABLE_SIZE

  • h2(key) = PRIME - (key % PRIME)(PRIME 为小于 TABLE_SIZE 的素数)

 

/* 
    再哈希法
    双重哈希
*/

#include <stdio.h>
#include <stdlib.h>

#define TABLE_SIZE 11
#define PRIME 7  // 用于第二哈希函数

int hashTable[TABLE_SIZE];

// 初始化哈希表
void initHashTable() {
    for (int i = 0; i < TABLE_SIZE; i++)
        hashTable[i] = -1;
}

// 第一个哈希函数
int hash1(int key) {
    return key % TABLE_SIZE;
}

// 第二个哈希函数
int hash2(int key) {
    return PRIME - (key % PRIME);
}

// 插入函数
void insert(int key) {
    int index1 = hash1(key);
    int index2 = hash2(key);
    int i = 0;

    while (i < TABLE_SIZE) {
        int newIndex = (index1 + i * index2) % TABLE_SIZE;
        if (hashTable[newIndex] == -1) {
            hashTable[newIndex] = key;
            printf("Inserted %d at index %d\n", key, newIndex);
            return;
        }
        i++;
    }
    printf("Failed to insert %d: Table is full.\n", key);
}

// 查找函数
int search(int key) {
    int index1 = hash1(key);
    int index2 = hash2(key);
    int i = 0;

    while (i < TABLE_SIZE) {
        int newIndex = (index1 + i * index2) % TABLE_SIZE;
        if (hashTable[newIndex] == key)
            return newIndex;
        if (hashTable[newIndex] == -1)
            return -1; // 说明不存在
        i++;
    }
    return -1;
}

// 打印哈希表
void printHashTable() {
    printf("Hash Table:\n");
    for (int i = 0; i < TABLE_SIZE; i++) {
        if (hashTable[i] != -1)
            printf("[%d]: %d\n", i, hashTable[i]);
        else
            printf("[%d]: NULL\n", i);
    }
}

int main() {
    initHashTable();

    insert(10);
    insert(22);
    insert(31);
    insert(4);
    insert(15);
    insert(28);
    insert(17);
    insert(88);
    insert(59);

    printHashTable();

    int key = 31;
    int pos = search(key);
    if (pos != -1)
        printf("%d found at index %d\n", key, pos);
    else
        printf("%d not found in table.\n", key);

    return 0;
}

5 建立公共溢出区

建立公共溢出区法是一种处理哈希冲突的策略,它将哈希表划分为两个部分:主表(Main Area)和溢出区(Overflow Area)。当哈希函数定位的主表槽位已被占用时,冲突的元素不在主表中探测或扩展,而是统一存入溢出区。查找时,若主表不匹配,就扫描溢出区。该方法结构清晰、插入简单,尤其适用于冲突频繁但查找频率较低的应用场景,但溢出区可能退化为线性搜索,影响查找性能。

// 建立公共溢出区法

#include <stdio.h>
#include <stdlib.h>

#define MAIN_TABLE_SIZE 7     // 主表大小
#define OVERFLOW_SIZE   10    // 溢出区大小

typedef struct {
    int key;
    int isOccupied;
} HashNode;

HashNode mainTable[MAIN_TABLE_SIZE];
HashNode overflowArea[OVERFLOW_SIZE];

// 初始化哈希表和溢出区
void initHashTable() {
    for (int i = 0; i < MAIN_TABLE_SIZE; i++) {
        mainTable[i].isOccupied = 0;
    }
    for (int i = 0; i < OVERFLOW_SIZE; i++) {
        overflowArea[i].isOccupied = 0;
    }
}

// 哈希函数
int hashFunction(int key) {
    return key % MAIN_TABLE_SIZE;
}

// 插入函数
void insert(int key) {
    int index = hashFunction(key);
    if (!mainTable[index].isOccupied) {
        mainTable[index].key = key;
        mainTable[index].isOccupied = 1;
        printf("Inserted %d in mainTable[%d]\n", key, index);
        return;
    }

    // 插入溢出区
    for (int i = 0; i < OVERFLOW_SIZE; i++) {
        if (!overflowArea[i].isOccupied) {
            overflowArea[i].key = key;
            overflowArea[i].isOccupied = 1;
            printf("Inserted %d in overflowArea[%d]\n", key, i);
            return;
        }
    }

    printf("Failed to insert %d: overflow area is full.\n", key);
}

// 查找函数
int search(int key) {
    int index = hashFunction(key);
    if (mainTable[index].isOccupied && mainTable[index].key == key) {
        return index;
    }

    for (int i = 0; i < OVERFLOW_SIZE; i++) {
        if (overflowArea[i].isOccupied && overflowArea[i].key == key) {
            return MAIN_TABLE_SIZE + i;
        }
    }

    return -1;
}

// 打印函数
void printHashTable() {
    printf("\nMain Table:\n");
    for (int i = 0; i < MAIN_TABLE_SIZE; i++) {
        if (mainTable[i].isOccupied)
            printf("[%d]: %d\n", i, mainTable[i].key);
        else
            printf("[%d]: NULL\n", i);
    }

    printf("\nOverflow Area:\n");
    for (int i = 0; i < OVERFLOW_SIZE; i++) {
        if (overflowArea[i].isOccupied)
            printf("[%d]: %d\n", i, overflowArea[i].key);
        else
            printf("[%d]: NULL\n", i);
    }
}

int main() {
    initHashTable();

    // 插入测试数据
    int keys[] = {10, 17, 24, 3, 31, 45, 38, 52, 59, 66};
    int n = sizeof(keys) / sizeof(keys[0]);
    for (int i = 0; i < n; i++) {
        insert(keys[i]);
    }

    printHashTable();

    // 查找示例
    int key = 59;
    int pos = search(key);
    if (pos != -1) {
        if (pos < MAIN_TABLE_SIZE)
            printf("\nKey %d found in mainTable[%d]\n", key, pos);
        else
            printf("\nKey %d found in overflowArea[%d]\n", key, pos - MAIN_TABLE_SIZE);
    } else {
        printf("\nKey %d not found\n", key);
    }

    return 0;
}

6 总结

哈希表作为高效的数据结构,其核心挑战在于处理哈希冲突。本文详细介绍了四种经典方法:开放定址法(包括线性探测、二次探测和双重哈希)通过表内探查解决冲突,节省空间但易受聚集现象影响;链地址法将冲突元素组织成链表,结构简单但需额外内存;再哈希法利用多哈希函数减少聚集,但实现复杂;公共溢出区法隔离冲突元素,适合低频查找场景。每种方法各有优劣,实际应用中需根据数据特征、负载因子和性能需求选择合适策略。理解这些冲突解决机制,有助于优化哈希表设计,提升系统性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值