C语言实现通用数据结构之通用集合(HashSet)

C 语言实现通用数据结构之通用集合(HashSet)

什么是 HashSet

HashSet 是一种常用的数据结构,其实质就是一个无序不重复的元素集合。在 C 语言中,你可以使用 HashSet 存储任何类型的数据。

HashSet 的优点在于:

  1. 独立性,只关心数据的存储和操作,而不必关心数据类型;
  2. 方便性,对于处理过程,比起普通数组无需考虑顺序问题。

实现过程

下面是一个简化的实现步骤,通俗易懂。

基本原理

HashSet 包含以下基本操作:

  1. 初始化 HashSet 尺寸和加载因子。
  2. 插入元素。
  3. 判断元素是否存在。
  4. 删除元素。
  5. 清空 HashSet。

尽管 HashSet 的实现方法因平台而异,但如同任何数据结构一样,有许多共性。通过以下的操作,你可以大致了解 HashSet 的实现。

/** 元素类型 */
typedef struct HashSetType_s {
    uint32_t hash;   // 哈希值
    void *value;     // 元素值
} HashSetType;

/** 哈希表 */
typedef struct HashSet_s {
    uint32_t size;    // 哈希表大小
    float load_factor;// 哈希表负载因子

    uint32_t count;   // 哈希表元素总数
    HashSetType *map; // 元素映射表,数组

    /** 函数指针 **/
    uint32_t (*hash_func)(const void *value);         // 元素哈希函数
    int (*keycmp_func)(const void *a, const void *b);// 元素比较函数
    void (*destroy_func)(void *value);               // 元素销毁函数
} HashSet;

/** 创建 HashSet 对象 **/
HashSet *HashSet_create(int size, float load_factor,
                        uint32_t (*hash_func)(const void *value),
                        int (*keycmp_func)(const void *a, const void *b),
                        void (*destroy_func)(void *value));

/** 销毁 HashSet 对象 **/
void HashSet_destroy(HashSet **set);

/** 插入元素 **/
int HashSet_insert(HashSet *set, const void *value);

/** 查询元素是否存在 **/
int HashSet_exists(HashSet *set, const void *value);

/** 删除元素 **/
int HashSet_remove(HashSet *set, const void *value);

/** 元素数量 **/
uint32_t HashSet_count(HashSet *set);

/** 清空 HashSet **/
void HashSet_clear(HashSet *set);

插入元素

HashSet 的插入元素操作十分简单明了。只需要通过哈希函数求出元素的哈希值,并将元素存储在哈希表中即可。

int HashSet_insert(HashSet *set, const void *value) {
    if (value == NULL) return -1;

    if (set->count >= (uint32_t) (set->size * set->load_factor)) {
        // 哈希表负载因子过大,需要扩容。
        int new_size = 2 * set->size;
        HashSetType *new_map = (HashSetType *)calloc(new_size, sizeof(HashSetType));
        if (new_map == NULL) return -1;

        for (uint32_t i = 0; i < set->size; i++) {
            HashSetType *old_elem = &set->map[i];
            if (old_elem->value == NULL) continue;
            /** 计算哈希值 **/
            uint32_t idx = old_elem->hash % new_size;
            HashSetType *new_elem = &new_map[idx];

            new_elem->hash = old_elem->hash;
            new_elem->value = old_elem->value;
        }

        set->size = new_size;
        free(set->map);
        set->map = new_map;
    }

    HashSetType elem = {0};
    elem.hash = set->hash_func(value);
    elem.value = (void *)value;

    uint32_t idx = elem.hash % set->size;
    HashSetType *old_elem = &set->map[idx];
    if (old_elem->value != NULL) {
        // 如果元素已经存在,那么不再重复插入。
        if (set->keycmp_func(old_elem->value, value) == 0) return -1;
        // 如果被新元素 hash 到了原槽上,那么发生冲突或碰撞,采用线性探测法解决。
        uint32_t i = 1;
        while (1) {
            idx = (elem.hash + i) % set->size;
            old_elem = &set->map[idx];
            if (old_elem->value != NULL) {
                if (set->keycmp_func(old_elem->value, value) == 0) return -1;
            } else {
                break;
            }
            i++;
        }
    }

    *old_elem = elem;
    set->count++;
    return 0;
}

判断元素是否存在

HashSet 的元素查找操作通过哈希函数依次寻找元素所在的桶,即哈希值为 key 的元素所在的位置——桶,如果该桶是空桶,说明查找失败,则返回 FALSE。如果该桶不是空桶,则说明有哈希冲突的元素存放于该位置,此时,我们需要采用线性探测法的无序表解决来寻找被查找元素所在的位置,如果最终没有找到,说明查找失败,则返回 FALSE。

int HashSet_exists(HashSet *set, const void *value) {
    if (value == NULL) return 0;

    uint32_t hash = set->hash_func(value);
    uint32_t idx = hash % set->size;

    HashSetType *elem = &set->map[idx];
    if (elem->value == NULL) return 0;
    if (set->keycmp_func(elem->value, value) == 0) return 1;

    uint32_t i = 1;
    while (1) {
        idx = (hash + i) % set->size;
        elem = &set->map[idx];
        if (elem->value == NULL) return 0;
        if (set->keycmp_func(elem->value, value) == 0) return 1;
        i++;
    }
}

删除元素

HashSet 的元素删除操作十分简单。只需要通过哈希函数在哈希 map 中找到对应位置的元素,然后把该元素从 map 中删除即可。

int HashSet_remove(HashSet *set, const void *value) {
    if (value == NULL) return -1;

    uint32_t hash = set->hash_func(value);
    uint32_t idx = hash % set->size;

    HashSetType *elem = &set->map[idx];
    if (elem->value == NULL) return -1;
    if (set->keycmp_func(elem->value, value) == 0) {
        set->destroy_func(elem->value);
        elem->value = NULL;
        set->count--;
        return 0;
    }

    uint32_t i = 1;
    while (1) {
        idx = (hash + i) % set->size;
        elem = &set->map[idx];
        if (elem->value == NULL) return -1;
        if (set->keycmp_func(elem->value, value) == 0) {
            set->destroy_func(elem->value);
            elem->value = NULL;
            set->count--;
            return 0;
        }
        i++;
    }
    return 0;
}

示例说明

示例 1:使用 int类型实现 HashSet

假设需要声明一个 HashSet 类型的 set 变量,存储整型数据,读者可以采用如下方式进行初始化:

#include "HashSet.h"

uint32_t int_hash(const void *key) {
   return *((uint32_t *) key);
}

int int_keycmp(const void *a, const void *b) {
    return *(uint32_t *)a - *(uint32_t *)b;
}

void int_destroy(void *value) {
    free(value);
}

// 初始化 set 集合
static HashSet *set;
uint32_t hash_set_size = 3;
float hash_set_load_factor = 0.7;

int main() {
    set = HashSet_create(hash_set_size, hash_set_load_factor,
                          int_hash, int_keycmp, int_destroy);

    uint32_t *elem;
    elem = (uint32_t *)malloc(sizeof(uint32_t));
    *elem = 3;
    HashSet_insert(set, elem);

    if (HashSet_exists(set, &elem)) {
        printf("Element exists.\n");
    } else {
        printf("Element does not exist.\n");
    };
    HashSet_destroy(&set);
    return 0;

示例 2:使用 struct类型实现 HashSet

接下来我们以结构体类型为例,实现一个完整的 demo。

// 数组大小,元素数量
#define SET_SIZE    100
#define VALUE_NUM   20

typedef struct {
    int a;
    float b;
} value_t;

// Hash Function
uint32_t value_hash(const void *key) {
    int a = ((value_t *) key)->a;
    return a * a;
}

// Compare Function
int value_cmp(const void *a, const void *b) {
    value_t *data_a = (value_t *)a;
    value_t *data_b = (value_t *)b;
    return (data_a->a != data_b->a || data_a->b != data_b->b);
}

// Destroy Function
void value_freed(void *key) { free(key); }

static HashSet *set;

int main() {
    set = HashSet_create(SET_SIZE, 0.7, value_hash, value_cmp, value_freed);

    value_t *arr[VALUE_NUM];
    for (size_t i = 0; i < VALUE_NUM; i++) {
        arr[i] = (value_t *) malloc(sizeof(value_t));
        arr[i]->a = (int) rand() % 10;
        arr[i]->b = (float) rand() / RAND_MAX;
        HashSet_insert(set, arr[i]);
    }

    // 若元素存在,就删除它
    for (size_t i = 0; i < VALUE_NUM; i++) {
        if (HashSet_exists(set, arr[i])) {
            HashSet_remove(set, arr[i]);
        }
    }
    HashSet_destroy(&set);
    return 0;
}

总结

HashSet 是一个十分简单的数据结构,通过使用哈希表实现,可以非常方便地存储完整元素,不论元素的数据类型如何,都可以使用 HashSet 进行存储。本文通过一个简单的通用 HashSet,用 C 语言的方式实现了哈希表的常见操作,使读者能够大致了解 HashSet 的实现过程。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C语言实现通用数据结构之通用集合(HashSet) - Python技术站

(0)
上一篇 2023年5月23日
下一篇 2023年5月23日

相关文章

  • C语言实现三子棋程序

    C语言实现三子棋程序的完整攻略如下所述: 1. 程序框架设计 首先需要设计程序的框架,包括定义所需变量,确定函数和主函数的执行顺序等。具体步骤如下: 1.1 定义变量 定义三个变量表示棋盘上的状态,分别用0,1,2表示,0表示空白位置,1表示玩家1落子,2表示玩家2落子。因此需要定义一个二维数组表示棋盘,再定义一个变量表示当前轮到哪个玩家。 1.2 定义函数…

    C 2023年5月23日
    00
  • C语言实现简单扫雷源码

    C语言实现简单扫雷源码 在本文中,我们将讲解如何使用C语言实现简单的扫雷游戏。我们将介绍如何实现游戏的逻辑和界面,包括雷区生成、雷的布置、格子点击、游戏结束等功能,并会提供两个例子进行说明。 1. 准备工作 在开始编写代码前,我们需要先了解一些基础知识:如何使用C语言创建GUI应用程序,如何处理按键、鼠标事件等。 我们使用C语言的图形库SDL来实现游戏的界面…

    C 2023年5月23日
    00
  • C++实现加减乘除计算器

    C++实现加减乘除计算器 本文将展示如何使用C++实现加减乘除计算器。 示例代码 #include <iostream> using namespace std; int main() { char op; double a, b; cout << "请输入两个数字: "; cin >> a >&…

    C 2023年5月24日
    00
  • jQuery简单验证上传文件大小及类型的方法

    下面就是对于“jQuery简单验证上传文件大小及类型的方法”的详细攻略。 什么是文件验证? 文件上传是Web开发中常用的功能,但是常常需要验证上传文件的大小、类型等信息。通过对文件进行验证,可以避免上传恶意或者不支持的文件类型,也可以限制文件的大小,避免系统资源浪费,提高系统的安全性和稳定性。 如何使用jQuery验证上传文件大小及类型? 在jQuery中,…

    C 2023年5月23日
    00
  • C++编译器Clion的使用详解(总结)

    C++编译器Clion的使用详解(总结) 1. Clion简介 Clion是一款由JetBrains公司开发的跨平台C++开发工具。Clion具有强大的代码编辑和代码分析功能,还能够集成多个版本控制系统和调试器。它还提供了丰富的自动化功能,包括代码完成、调试、自动重构等等。 2. Clion的安装与配置 2.1. 安装Clion 首先,到JetBrains公…

    C 2023年5月23日
    00
  • C++程序的执行顺序结构以及关系和逻辑运算符讲解

    让我来为你详细讲解一下C++程序的执行顺序结构以及关系和逻辑运算符讲解的攻略。 C++程序的执行顺序结构 在C++程序中,程序的执行顺序遵循自上而下的顺序结构。也就是说,程序会首先执行第一条语句,然后接着执行第二条语句,以此类推,直到程序执行完所有语句为止。 下面是一个简单的示例,说明C++程序的执行顺序结构: #include <iostream&g…

    C 2023年5月23日
    00
  • jsoneditor二次封装实时预览json编辑器组件react版

    为了方便大家使用 JSON 编辑器组件,可以对 jsoneditor 进行二次封装。下面是关于如何实现 jsoneditor 的二次封装的详细攻略。 准备工作 在开始实现之前,我们需要做一些准备工作: 安装依赖:在项目根目录下运行以下命令安装所需依赖: npm install jsoneditor react 引入样式:在index.js 文件中引入样式 i…

    C 2023年5月23日
    00
  • C++实现歌手比赛评分系统

    C++实现歌手比赛评分系统攻略 1. 系统概述 歌手比赛评分系统是通过为参赛歌手评分,来评选出优胜者的系统。系统主要由以下功能模块组成: 参赛选手管理 评委管理 评分操作 成绩计算 排名显示 2. 系统设计 2.1 参赛选手管理 参赛选手信息包含选手编号、选手姓名等字段,可通过键盘输入或从文件中读取。可以使用结构体或类来表示选手信息,并使用数组、链表等数据结…

    C 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部