C语言数据结构与算法笔记(散列表)

散列表

散列也称哈希,直接寻址

散列查找

将参与检索的数据与散列值(哈希值)关联起来,生成便于搜索的数据结构。将一堆数据进行哈希函数计算,下次查找数据时,再次计算哈希值即可查找到对应的元素。

散列函数

哈希函数对目标计算一个其对应的哈希值,哈希函数对同一个目标计算的结果是一样的。

散列表

利用哈希值的特性,设计一个全新表结构,哈希表(散列表)。对应元素的关键字(整数)提供给哈希函数可进行计算。
一般有进行取模操作,哈希表的长度是多少,模就是多少。
在这里插入图片描述
在这里插入图片描述
代码实现
用数组功能实现哈希表
初始化

#define SIZE 9

typedef struct Element
{
    int key;
} * Element;

typedef struct HashTable
{
    Element * table; 
} * HashTable;

void init(HashTable hashTable)
{
    hashTable->table = malloc(sizeof(struct Element) * SIZE);
    for(int i = 0; i < SIZE; ++i)
    {
        hashTable->table[i] = NULL; // 将哈希表的每个元素初始为空
    }
}
// 哈希函数
int hash(int key) 
{
    return key % SIZE;
}
// 插入
void insert(HashTable hashTable, Element element)
{
    int hashCode = hash(element->key);
    hashTable->table[hashCode] = element; // 将哈希值放入到哈希表中
}
// 查找
_Bool find(HashTable hashTable, int key)
{
    int hashCode = hash(key);
    if(hashTable->table == NULL)
    {
        return 0;
    }
    else
    {   // 计算hash表上的元素值是否等于
        return hashTable->table[hashCode]->key == key;
    }
}
// 创建元素
Element create(int key)
{
    Element element = malloc(sizeof(struct Element));
    element->key = key;
    return element;
}

测试,main函数中,查看哈希表,检查是否找到元素

    struct HashTable table;
    init(&table);
    insert(&table, create(10));
    insert(&table, create(7));
    insert(&table, create(13));
    insert(&table, create(19));

    for(int i = 0 ; i < SIZE;++i)
    {
        if(table.table[i])
        {
            printf("%d ", table.table[i]->key);
        }
        else
        {
            printf("NULL ");
        }
    }
    printf("%d\n", find(&table,1));
    printf("%d\n", find(&table,13));

哈希冲突

哈希函数计算得到一个目标的哈希值,但可能出现哈希值相同的情况,称为哈希碰撞

线性探测法

第一次发生哈希冲突,找空位。 h i ( k e y ) = ( h ( k e y ) + d i ) h_i(key)=(h(key)+d_i)%TableSize hi(key)=(h(key)+di),其中 d i d_i di是随着哈希冲突次数增加而增加的量。出现哈希冲突, d i d_i di自增,继续寻找下一个空位,再次计算哈希值,得到对应位置。 d i d_i di默认为0

// 插入(考虑哈希冲突) 线性探测法
void insert(HashTable hashTable, Element element)
{
    int hashCode = hash(element->key);
    int cnt = 0;
    while(hashTable->table[hashCode] && cnt < SIZE) // 如果发生哈希冲突,继续寻找下一个位置
    {
        hashCode = hash(element->key + +cnt);
    }
    hashTable->table[hashCode] = element; // 将哈希值放入到哈希表中
}
// 查找
_Bool find(HashTable hashTable, int key)
{
    int hashCode = hash(key), cnt = 0; // 计算哈希值
    const int startIndex = hashCode; // 记录起始位置,转一圈回来就结束
    do
    {
        if(hashTable->table[hashCode]->key == key)
        {
            return 1;
        }
        hashCode = hash(key + ++cnt);
    }while(startIndex != hashCode && hashTable->table[hashCode]); // 没找到继续找
    return 0;
}

考虑哈希冲突,进行线性观测法,且考虑当哈希表满时,退出

但线性观测法删除元素时比较麻烦,可能会影响前面的查找操作。
在这里插入图片描述
考虑删除一个元素可能会导致原有结构意外截断,需要对位置进行标记,表示之前有过的元素被删除了。在查找时,发现被删除的位置曾经有过元素,依然需继续往后寻找
在这里插入图片描述
也可采用二次探测再散列法处理哈希冲突。可以向两个方向去寻找。查找的增量序列为: 1 2 、 − 1 2 、 2 2 、 − 2 2 . . . 1^2、-1^2、2^2、-2^2... 12122222...。左右反复查找可提高利用率

链地址法

哈希冲突,链表形式。但链表变得长时,查找效率也会变低,考虑转换为平衡二叉树或者红黑树。
在这里插入图片描述

#define SIZE 9

typedef struct LNode // 节点定义
{
    int key;
    struct LNode * next;
} * Node;

typedef struct HashTable
{
    struct LNode * table; // 数组存放头结点
} * HashTable;
// 初始化
void init(HashTable hashTable)
{
    hashTable->table = malloc(sizeof(struct LNode) * SIZE);
    for(int i = 0 ; i < SIZE; ++i)
    {
        hashTable->table[i].key = -1; // 将头结点设为-1
        hashTable->table[i].next = NULL;
    }
}
// 哈希函数
int hash(int key) 
{
    return key % SIZE;
}
// 创建节点
Node createNode(int key)
{
    Node node = malloc(sizeof(struct LNode));
    node->key = key;
    node->next = NULL;
    return node;
}

插入

// 插入
void insert(HashTable hashTable, int key)
{
    int hashCode = hash(key);
    Node head = hashTable->table + hashCode;
    // Node head = &hashTable->table[hashCode];
    while(head->next)
    {
        head = head->next; // 找到下一个为空的
    }
    head->next = createNode(key); // 插入节点
}
// 查找元素
_Bool find(HashTable hashTable, int key)
{
    int hashCode = hash(key);
    Node head = hashTable->table + hashCode;
    while(head->next && head->key != key)
    {
        head = head->next;
    }
    return head->key == key; // 直接找对应的
}

测试

    struct HashTable table;
    init(&table);
    insert(&table, 10);
    insert(&table, 19);
    insert(&table, 20);
    printf("%d\n", find(&table,20));
    printf("%d\n", find(&table,17));
    printf("%d\n", find(&table,19));
散列表习题
  1. 设有一组记录的关键字wei{19,14,23,1,68,20,84,27,55,11,10,79},用链地址法构造散列表,散列函数为H(key)=key MOD 13,散列地址为1的链中有多少个记录
  2. 设哈希表长为14,哈希函数为H(key)=key%11,表中已有数据的关键字为15,38,61,84.现将关键字为49元素加入到散列表中,二次探测再散列解决冲突,则放入的位置是
    按照二次散列的增量序列计算下一个位置
  3. 选取哈希函数H(key)=(key*3)%11,用线性探测散列法和二次探测再散列处理哈希冲突。试在0~10的散列地址空间中,对关键字序列{22,41,53,46,30,13,1,67}构建哈希表,求等概率情况下查找成功的平均查找长度。其中平均查找长度为表中每一个元素需要查找次数之和的平均值。
    线性探测散列的平均查找次数
    在这里插入图片描述
    ASL = 2
    二次探测再散列的平均查找次数
    在这里插入图片描述
    ASL=1.875
算法实战
  1. 两数之和
    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
    假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
    可以按任意顺序返回答案。

在这里插入图片描述
暴力枚举,直接让每个数取寻找与之匹配的数

// 返回计算两数之和
int * result(int i, int j, int * returnSize)
{
    *returnSize = 2;
    int * result = malloc(sizeof(int) * 2);
    result[0] = i;
    result[1] = j;
    return result;
}

int* twoSum(int* nums, int numSize, int target, int* returnSize)
{
    for(int i = 0; i < numSize;++i) // 遍历数组每一个数
    {
        for(int j = 0; j < numSize; ++j) // 二次遍历找目标元素
        {
            if(i == j) // 不找自身,跳过循环
            {
                continue;
            }
            else
            {
                if(nums[i] + nums[j] == target)
                {
                    return result(i, j, returnSize);
                }
            }
        }
    }
    return NULL;
}

散列表,每遍历一个数都将其存放到散列表中,遇到与匹配的数时,只需要遍历一次就行。

// 散列表
#define SIZE 128

typedef int K;
typedef int V;

typedef struct LNode // 节点定义
{
    K key; // 存入关键字和下标
    V value;
    struct LNode * next;
} * Node;

typedef struct HashTable
{
    struct LNode * table; // 数组存放头结点
} * HashTable;

// 初始化
void init(HashTable hashTable)
{
    hashTable->table = malloc(sizeof(struct LNode) * SIZE);
    for(int i = 0 ; i < SIZE; ++i)
    {
        hashTable->table[i].key = -1; // 将头结点设为-1
        hashTable->table[i].value = -1;
        hashTable->table[i].next = NULL;
    }
}
// 创建节点
Node create(K key, V value)
{
    Node node = malloc(sizeof(struct LNode));
    node->key = key;
    node->value = value;
    node->next = NULL;
    return node;
}

将插入的元素放入散列表,(插入节点),查找下一个元素是否匹配,匹配则直接返回结果,否则放入散列表,下一个元素

// 哈希函数
int hash(unsigned int key) // 遇到负数将其转化为无符号数
{
    return key % SIZE;
}

// 插入
void insert(HashTable hashTable, K key, V value)
{
        int hashCode = hash(key);
    Node head = hashTable->table + hashCode;
    // Node head = &hashTable->table[hashCode];
    while(head->next)
    {
        head = head->next; // 找到下一个为空的
    }
    head->next = createNode(key, value); // 插入节点
}
// 查找元素
_Bool find(HashTable hashTable, int key)
{
    int hashCode = hash(key);
    Node head = hashTable->table + hashCode; // 定位到对应位置
    while(head->next && head->next->key != key) // 有没有下一个节点,并且下一个节点不是key
    {
        head = head->next;
    }
    return head->next;
}
int * result(int i, int j, int * returnSize)
{
    *returnSize = 2;
    int * result = malloc(sizeof(int) * 2);
    result[0] = i;
    result[1] = j;
    return result;
}

int* twoSum(int* nums, int numSize, int target, int* returnSize)
{
    struct HashTable table;
    init(&table);

    for(int i = 0 ; i < numSize; ++i)
    {
        Node node = find(&table, target - nums[i]); // 直接在哈希表中寻找匹配的,
        if(node) // 如果该数匹配,则直接返回结果
        {
            return result(i, node->value, returnSize);
        }
        else // 未找到则将该数放进散列表,继续找下一个是否匹配
        {
            insert(&table, nums[i], i);
        }
    }
    return NULL;
}
  • 29
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值