哈希表的几种实现方式与比较

news2024/11/19 3:38:02

版权声明

  • 本文原创作者:谷哥的小弟
  • 作者博客地址:http://blog.csdn.net/lfdfhl

在这里插入图片描述

哈希表概述

哈希表(Hash Table)是一种常用的数据结构,用于实现键值对的映射关系。它通过哈希函数将键映射到一个特定的索引位置,然后在该位置存储相应的值。这样可以实现快速的插入、删除和查找操作,使得哈希表在很多场景下具有高效的性能。

哈希表的主要组成部分和工作原理如下:

  • 1、哈希函数(Hash Function): 这是哈希表的核心。哈希函数接受一个键作为输入,并输出一个固定大小的哈希值。这个哈希值通常是一个整数,它将键映射到哈希表中的一个索引位置。一个好的哈希函数应该尽量避免冲突,即不同的键映射到相同的索引位置。
    当我们使用哈希表时,我们首先需要选择一个合适的哈希函数。一个好的哈希函数应该具备以下特点:
    确定性: 对于相同的输入,哈希函数应始终产生相同的输出。
    高效性: 计算哈希值的过程应该是高效的,不论输入的大小如何。
    均匀性: 哈希函数应该尽可能均匀地分布键,以减少冲突的可能性。

  • 2、数组(Array): 哈希表内部通常使用一个数组来存储键值对。每个索引位置对应一个桶(Bucket),每个桶可以存储一个或多个键值对。

  • 3、冲突处理: 由于哈希函数的输出空间可能小于键的实际空间,不同的键可能被映射到相同的索引位置,这就是冲突。哈希表需要解决冲突的方法,常见的有两种:链表法(Separate Chaining): 每个桶使用一个链表来存储具有相同哈希值的键值对。开放地址法(Open Addressing): 当发生冲突时,线性地探查下一个空闲的桶,或者通过其他探查方法找到下一个可用的位置。

  • 4、装载因子(Load Factor): 为了避免哈希表变得过满,我们引入了装载因子(Load Factor),它是哈希表中已存储键值对的数量与桶的总数之比。当装载因子超过某个阈值时,我们可以选择调整哈希表的大小,例如,通过重新哈希,增加数组的大小,从而减小装载因子。也就是说:装载因子表示哈希表中已存储键值对的数量与桶的总数之比。装载因子越大,冲突可能越多,性能可能下降。为了保持性能,通常需要在装载因子达到某个阈值时,对哈希表进行调整(例如,重新哈希,增加桶的数量)。

  • 5、查找、插入和删除: 通过哈希函数计算键的哈希值,找到对应的索引位置,然后执行相应的操作。由于哈希表的平均时间复杂度是常数级别的,这些操作通常非常高效。

总体而言,哈希表是一种强大的数据结构,适用于需要快速查找、插入和删除的场景。在实际应用中,选择合适的哈希函数和解决冲突的方法对于哈希表的性能至关重要。

哈希表常见操作

当使用哈希表时,通常涉及以下几种常见操作:插入(Insertion)、查找(Search)、删除(Deletion)。在哈希表中,通常没有专门的“修改”一说,因为哈希表的设计更加注重于快速的插入、查找和删除操作。这些操作的详细介绍如下:

插入(Insertion)

首先,计算键的哈希值,通过哈希函数找到对应的索引位置。如果该位置上没有其他键值对,直接将键值对存储在该位置;如果发生冲突,根据解决冲突的方法(如链表法或开放地址法),找到下一个可用的位置并存储。

查找(Search)

首先,计算键的哈希值,通过哈希函数找到对应的索引位置。如果该位置上有键值对,则根据解决冲突的方法在链表或探查路径上查找键,找到则返回相应的值;如果没有找到,说明键不存在于哈希表中。

删除(Deletion)

首先,计算键的哈希值,通过哈希函数找到对应的索引位置。如果该位置上有键值对,则根据解决冲突的方法在链表或探查路径上查找键。如果找到,删除该键值对;如果没有找到,说明键不存在于哈希表中。

哈希表常见操作的代码实现

在此,分别用C语言、Java语言、Python语言实现哈希表的插入(Insertion)、查找(Search)、删除(Deletion)。

C语言版

在这里插入图片描述

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

// 定义哈希表中每个节点的结构
typedef struct Node {
    char *key;
    int value;
    struct Node *next;  // 链表用于处理冲突
} Node;

// 定义哈希表的结构
typedef struct HashTable {
    int size;
    Node **table;  // 数组用于存储链表的头节点
} HashTable;

// 哈希函数,简单地使用字符串的ASCII码之和模哈希表的大小
int hash(char *key, int size) {
    int sum = 0;
    for (int i = 0; key[i] != '\0'; i++) {
        sum += key[i];
    }
    return sum % size;
}

// 初始化哈希表
HashTable *initHashTable(int size) {
    HashTable *hashTable = (HashTable *)malloc(sizeof(HashTable));
    hashTable->size = size;
    
    // 为哈希表的每个桶分配内存
    hashTable->table = (Node **)malloc(sizeof(Node *) * size);
    for (int i = 0; i < size; i++) {
        hashTable->table[i] = NULL;
    }
    
    return hashTable;
}

// 插入操作
void insert(HashTable *hashTable, char *key, int value) {
    // 计算哈希值
    int index = hash(key, hashTable->size);
    
    // 创建新节点
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->key = strdup(key);  // 复制字符串
    newNode->value = value;
    newNode->next = NULL;
    
    // 如果该位置为空,直接插入
    if (hashTable->table[index] == NULL) {
        hashTable->table[index] = newNode;
    } else {
        // 否则,将新节点插入链表的头部
        newNode->next = hashTable->table[index];
        hashTable->table[index] = newNode;
    }
}

// 查找操作
int search(HashTable *hashTable, char *key) {
    // 计算哈希值
    int index = hash(key, hashTable->size);
    
    // 在链表中查找键值对
    Node *current = hashTable->table[index];
    while (current != NULL) {
        if (strcmp(current->key, key) == 0) {
            return current->value;  // 找到键,返回对应的值
        }
        current = current->next;
    }
    
    return -1;  // 未找到键
}

// 删除操作
void delete(HashTable *hashTable, char *key) {
    // 计算哈希值
    int index = hash(key, hashTable->size);
    
    // 在链表中查找键值对
    Node *current = hashTable->table[index];
    Node *prev = NULL;
    
    while (current != NULL) {
        if (strcmp(current->key, key) == 0) {
            // 找到键,删除节点
            if (prev == NULL) {
                // 如果是链表的头节点
                hashTable->table[index] = current->next;
            } else {
                prev->next = current->next;
            }
            
            // 释放内存
            free(current->key);
            free(current);
            return;
        }
        
        prev = current;
        current = current->next;
    }
}

// 释放哈希表的内存
void destroyHashTable(HashTable *hashTable) {
    for (int i = 0; i < hashTable->size; i++) {
        Node *current = hashTable->table[i];
        while (current != NULL) {
            Node *next = current->next;
            free(current->key);
            free(current);
            current = next;
        }
    }
    free(hashTable->table);
    free(hashTable);
}

int main() {
    // 初始化哈希表
    HashTable *hashTable = initHashTable(10);

    // 插入键值对
    insert(hashTable, "apple", 5);
    insert(hashTable, "banana", 8);
    insert(hashTable, "orange", 12);

    // 查找键值对
    printf("Value for 'apple': %d\n", search(hashTable, "apple"));
    printf("Value for 'banana': %d\n", search(hashTable, "banana"));
    printf("Value for 'orange': %d\n", search(hashTable, "orange"));
    printf("Value for 'grape': %d\n", search(hashTable, "grape"));  // 不存在的键

    // 删除键值对
    delete(hashTable, "banana");

    // 再次查找键值对
    printf("Value for 'banana' after deletion: %d\n", search(hashTable, "banana"));

    // 释放哈希表的内存
    destroyHashTable(hashTable);

    return 0;
}

Java语言版

在这里插入图片描述

import java.util.LinkedList;

// 定义哈希表的键值对节点
class HashNode {
    String key;
    int value;

    public HashNode(String key, int value) {
        this.key = key;
        this.value = value;
    }
}

// 定义哈希表
class HashTable {
    private static final int DEFAULT_SIZE = 10;
    private LinkedList<HashNode>[] table;

    public HashTable() {
        // 初始化哈希表数组,每个桶用链表存储
        table = new LinkedList[DEFAULT_SIZE];
        for (int i = 0; i < DEFAULT_SIZE; i++) {
            table[i] = new LinkedList<>();
        }
    }

    // 哈希函数,简单地使用字符串的hashCode并取余
    private int hash(String key) {
        return key.hashCode() % DEFAULT_SIZE;
    }

    // 插入操作
    public void insert(String key, int value) {
        int index = hash(key);
        LinkedList<HashNode> list = table[index];

        // 检查是否已存在相同的键,如果是则更新值
        for (HashNode node : list) {
            if (node.key.equals(key)) {
                node.value = value;
                return;
            }
        }

        // 否则,将新键值对加入链表
        list.add(new HashNode(key, value));
    }

    // 查找操作
    public int search(String key) {
        int index = hash(key);
        LinkedList<HashNode> list = table[index];

        // 在链表中查找键值对
        for (HashNode node : list) {
            if (node.key.equals(key)) {
                return node.value;  // 找到键,返回对应的值
            }
        }

        return -1;  // 未找到键
    }

    // 删除操作
    public void delete(String key) {
        int index = hash(key);
        LinkedList<HashNode> list = table[index];

        // 在链表中查找键值对并删除
        for (HashNode node : list) {
            if (node.key.equals(key)) {
                list.remove(node);
                return;
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // 初始化哈希表
        HashTable hashTable = new HashTable();

        // 插入键值对
        hashTable.insert("apple", 5);
        hashTable.insert("banana", 8);
        hashTable.insert("orange", 12);

        // 查找键值对
        System.out.println("Value for 'apple': " + hashTable.search("apple"));
        System.out.println("Value for 'banana': " + hashTable.search("banana"));
        System.out.println("Value for 'orange': " + hashTable.search("orange"));
        System.out.println("Value for 'grape': " + hashTable.search("grape"));  // 不存在的键

        // 删除键值对
        hashTable.delete("banana");

        // 再次查找键值对
        System.out.println("Value for 'banana' after deletion: " + hashTable.search("banana"));
    }
}

Python语言版

在这里插入图片描述

class HashNode:
    def __init__(self, key, value):
        self.key = key
        self.value = value

class HashTable:
    def __init__(self, size=10):
        # 初始化哈希表,每个桶用链表存储
        self.size = size
        self.table = [[] for _ in range(size)]

    # 哈希函数,简单地使用字符串的哈希值并取余
    def _hash(self, key):
        return hash(key) % self.size

    # 插入操作
    def insert(self, key, value):
        index = self._hash(key)
        bucket = self.table[index]

        # 检查是否已存在相同的键,如果是则更新值
        for node in bucket:
            if node.key == key:
                node.value = value
                return

        # 否则,将新键值对加入链表
        bucket.append(HashNode(key, value))

    # 查找操作
    def search(self, key):
        index = self._hash(key)
        bucket = self.table[index]

        # 在链表中查找键值对
        for node in bucket:
            if node.key == key:
                return node.value  # 找到键,返回对应的值

        return -1  # 未找到键

    # 删除操作
    def delete(self, key):
        index = self._hash(key)
        bucket = self.table[index]

        # 在链表中查找键值对并删除
        for node in bucket:
            if node.key == key:
                bucket.remove(node)
                return

if __name__ == "__main__":
    # 初始化哈希表
    hash_table = HashTable()

    # 插入键值对
    hash_table.insert("apple", 5)
    hash_table.insert("banana", 8)
    hash_table.insert("orange", 12)

    # 查找键值对
    print("Value for 'apple':", hash_table.search("apple"))
    print("Value for 'banana':", hash_table.search("banana"))
    print("Value for 'orange':", hash_table.search("orange"))
    print("Value for 'grape':", hash_table.search("grape"))  # 不存在的键

    # 删除键值对
    hash_table.delete("banana")

    # 再次查找键值对
    print("Value for 'banana' after deletion:", hash_table.search("banana"))

性能比较与小结

以上使用了三种不同编程语言实现哈希表的插入(Insertion)、查找(Search)、删除(Deletion)的相同点与不同点并分析它们的执行效率。

相同点

  • 基本思想相同: 无论是用 C、Java 还是 Python 实现,它们都采用了哈希表这一数据结构,并使用了相似的基本思想,即通过哈希函数将键映射到哈希表中的位置,并处理冲突。

  • 处理冲突方式相似: 三种实现都使用了链表法来处理冲突,即在哈希表的每个桶中使用链表来存储相同哈希值的键值对。这是一种简单而常见的冲突解决方式。

  • 包含插入、查找、删除操作: 无论使用哪种编程语言,这三种实现都包含了插入、查找和删除这三种基本操作,这是哈希表的核心功能。

不同点

  • 语法和代码结构

C 语言使用了显式的内存管理,包括 malloc 和 free,而且数组索引从 0 开始。C 代码通常更接近底层,需要程序员手动管理内存。

Java 使用了面向对象的风格,对内存管理有自动的垃圾回收机制。它的语法结构更加高级,使用了类和面向对象的概念。
Python 具有简洁的语法和动态类型,对于链表的操作更加方便,同时也不需要显式地处理内存。

  • 哈希函数实现

C 语言的哈希函数使用了简单的 ASCII 码之和取余的方式。
Java 使用了字符串的 hashCode 方法,并取余哈希表大小。
Python 使用了内置的 hash 函数,并取余哈希表大小。

  • 数据结构的表示

C 使用了数组和指针表示哈希表,链表的节点也是手动分配的内存。
Java 使用了类表示哈希表和链表,利用 Java 的面向对象特性。
Python 使用了类似于 Java 的面向对象表示法,但在语法上更为简洁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1298414.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot 3 整合 Mybatis-Plus 实现动态数据源切换实战

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

银河麒麟本地软件源配置方法

软件源介绍 软件源可以理解为软件仓库&#xff0c;当需要安装软件时则会根据源配置去相应的软件源下载软件包&#xff0c;此方法的优点是可以自动解决软件包的依赖关系。常见的软件源有光盘源、硬盘源、FTP源、HTTP源&#xff0c;本文档主要介绍本地软件源的配置方法&#xff…

专注抖音短视频账号矩阵系统源头开发---saas工具

抖音账号|短视频矩阵分发系统 | 多账号管理发布 |MVC架 短视频矩阵分发系统是一种可以帮助企业、机构和个人高效分发短视频的工具。随着社交媒体的不断普及&#xff0c;短视频的使用越来越广泛&#xff0c;因此如何快速而准确地将短视频传播到不同的平台和账号上已经成为了一个…

短剧分销平台搭建:短剧变现新模式

短剧作为今年大热的行业&#xff0c;深受大众追捧&#xff01;短剧剧情紧凑&#xff0c;几乎每一集都有高潮剧情&#xff0c;精准击中了当下网友的碎片化时间。 短剧的形式较为灵活&#xff0c;可以轻松融入各种的元素&#xff0c;比如喜剧、悬疑、爱情等&#xff0c;可以满足…

一加 12 Pop-up快闪活动来袭,十城联动火爆开启

12 月 9 日&#xff0c;一加 12 Pop-up 快闪活动在北京、深圳、上海、广州等十城联动开启&#xff0c;各地加油欢聚快闪现场&#xff0c;抢先体验与购买一加 12。作为一加十年超越之作&#xff0c;一加 12 全球首发拥有医疗级护眼方案和行业第一 4500nit 峰值亮度的 2K 东方屏、…

postman常用脚本

一、在参数中动态添加开始时间和结束时间的时间戳 1.先在collection中添加参数&#xff0c;这里的作用域是collection&#xff0c;也可以是其他的任何scope 2.在Pre-request Script 中设定开始时间和结束时间参数&#xff0c;比如昨天和今天的时间戳&#xff0c;下面是js代码 …

彻底搞懂零拷贝技术( DMA、PageCache)

DMA 直接内存访问&#xff08;Direct Memory Access&#xff09; 什么是DMA&#xff1f; 在进行数据传输的时候&#xff0c;数据搬运的工作全部交给 DMA 控制器&#xff0c;而 CPU 不再参与&#xff0c;可以去干别的事情。 传统I/O 在没有 DMA 技术前&#xff0c;全程数据…

【图论笔记】克鲁斯卡尔算法(Kruskal)求最小生成树

【图论笔记】克鲁斯卡尔算法&#xff08;Kruskal&#xff09;求最小生成树 适用于 克鲁斯卡尔适合用来求边比较稀疏的图的最小生成树 简记&#xff1a; 将边按照升序排序&#xff0c;选取n-1条边&#xff0c;连通n个顶点。 添加一条边的时候&#xff0c;如何判断能不能添加…

链表OJ—相交链表

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 1、相交链表的题目&#xff1a; 方法讲解&#xff1a; 图文解析&#xff1a; 代码实现&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升…

《PySpark大数据分析实战》图书上线啦

《PySpark大数据分析实战》图书上线啦 《PySpark大数据分析实战》图书上线啦特殊的日子关于创作关于数据关于Spark关于PySpark关于图书/专栏 《PySpark大数据分析实战》图书上线啦 特殊的日子 不知不觉一转眼入驻CSDN已经满一年了&#xff0c;这真是一个充满意义的特殊的日子&…

SystemUI下拉通知菜单栏定时自动隐藏

前言 在系统应用开发过程中&#xff0c;常常遇到一些特殊的需求&#xff0c;Android原生的应用并无此适配&#xff0c;此时需要对系统应用进行定制化开发。 目前遇到的这样一个需求&#xff1a;下拉通知菜单栏时&#xff0c;定时8秒后自动关闭通知菜单栏。通知菜单栏为Sytstem…

如何用Python编写俄罗斯方块Tetris游戏?

在本文中&#xff0c;我们将用Python代码构建一个令人惊叹的项目&#xff1a;俄罗斯方块游戏。在这个项目中&#xff0c;我们将使用pygame库来构建游戏。要创建此项目&#xff0c;请确保您的系统中安装了最新版本的Python。让我们开始吧&#xff01; Pygame是一组跨平台的Pyth…

Mysql研学-认识与安装

一 数据库 1 Java的数据存储技术 ① 变量:一个数据存储空间的表示 ② 数组:存储一组相同数据类型的"容器" ③ 集合:存储一组任意引用数据类型的"容器" ④ 配置文件: .properties:基于Properties集合存储(Map集合的具体实例) .xml文件:基于标签存储数据…

centos7 安装 mysql8 详细步骤记录

下载 mysql 8 更新系统&#xff1a; sudo yum update 添加 MySQL Yum存储库&#xff1a; sudo rpm -Uvh https://repo.mysql.com/mysql80-community-release-el7-3.noarch.rpm 安装 MySQL 8&#xff1a; sudo yum install mysql-server 重置密码 查看初始密码&#xff1…

三种入耳检测光感芯片驱动开发比较

三种入耳检测光感芯片驱动开发比较 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;可加我微信hezkz17, 本群提供音频技术答疑服务&#xff0c;群赠送语音信号处理降噪算法&#xff0c;蓝牙耳机音频&#xff0c;DSP音频项目核心开发资料, 重要的寄存器…

应用层之应用层的网络应用模型————C/S和P2P、域名解析系统DNS、文件传输协议FTP

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

Ubuntu上svn基本使用(gitee提交下载)

目录 环境准备 1. 获取代码到本地 直接获取 获取代码时加入用户名密码 指定版本更新 2. 提交代码 3. 展示代码列表 4. 添加代码文件(目录) 5. 删除gitee仓库中的文件 参考文档链接 环境准备 当前操作系统为Ubuntu22.04LTS gitee 创建仓库时 需要打开svn的支持 sudo…

54.grpc实现文件上传和下载

文章目录 一&#xff1a;简介1. 什么是grpc2. 为什么我们要用grpc 二&#xff1a;grpc的hello world1、 定义hello.proto文件2、生成xxx_grpc.pb.go文件3、生成xxx.pb.go结构体文件4、编写服务代码service.go5、编写客户端代码client.go 三、服务端流式传输&#xff1a;文件下载…

短视频无人实景直播源码技术开发=抖去推saas直播源码

开发无人直播源码技术需要具备一定的编程和网络知识。以下是一些基本的步骤和资源&#xff0c;帮助你进行无人直播源码的开发搭建&#xff1a; 1. 选择编程语言和开发环境&#xff1a;根据你的个人喜好和技术熟练程度&#xff0c;可以选择一些流行的编程语言&#xff0c;如Pyth…

根据应聘者的姓名和所学专业判断是否需要这样的程序设计人员

一、程序分析 导入Scanner函数&#xff0c;分别输入应聘者的姓名和应聘者所学的程序设计语言。 二、具体代码 import java.util.Scanner; public class Recruitment {public static void main(String[] args){try (Scanner scan new Scanner(System.in)) {System.out.prin…