散列表(四):冲突处理的方法之开地址法(二次探测再散列的实现)

前面的文章分析了开地址法的其中一种:线性探测再散列,这篇文章来讲开地址法的第二种:二次探测再散列

(二)、二次探测再散列

为改善“堆积”问题,减少为完成搜索所需的平均探查次数,可使用二次探测法。

通过某一个散列函数对表项的关键码 x 进行计算,得到桶号,它是一个非负整数。 

若设表的长度为TableSize = 23,则在线性探测再散列 举的例子中利用二次探查法所得到的散列结果如图所示。

比如轮到放置Blum 的时候,本来应该是位置1,已经被Burke 占据,接着探测 H0 + 1 = 2,,发现被Broad 占据,接着探测 H0 - 1 = 

0,发现空位于是放进去,探测次数为3。

下面来看具体代码实现,跟前面讲过的线性探测再散列 差不多,只是探测的方法不同,但使用的数据结构也有点不一样,此外还实

现了开裂,如果装载因子 a > 1/2; 则建立新表,将旧表内容拷贝过去,所以hash_t 结构体需要再保存一个size 成员,同样的原因,

为了将旧表内容拷贝过去,hash_node_t 结构体需要再保存 *key 和 *value 的size。

common.h:

#ifndef _COMMON_H_
#define _COMMON_H_

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


#define ERR_EXIT(m) \
  do \
  { \
    perror(m); \
    exit(EXIT_FAILURE); \
  } \
  while (0)

#endif

hash.h:

#ifndef _HASH_H_
#define _HASH_H_

typedef struct hash hash_t;
typedef unsigned int (*hashfunc_t)(unsigned int, void *);

hash_t *hash_alloc(unsigned int buckets, hashfunc_t hash_func);
void hash_free(hash_t *hash);
void *hash_lookup_entry(hash_t *hash, void *key, unsigned int key_size);
void hash_add_entry(hash_t *hash, void *key, unsigned int key_size,
                    void *value, unsigned int value_size);
void hash_free_entry(hash_t *hash, void *key, unsigned int key_size);


#endif /* _HASH_H_ */

hash.c:

#include "hash.h"
#include "common.h"
#include <assert.h>


typedef enum entry_status
{
    EMPTY,
    ACTIVE,
    DELETED
} entry_status_t;

typedef struct hash_node
{
    enum entry_status status;
    void *key;
    unsigned int key_size; //在拷贝进新的哈希表时有用
    void *value;
    unsigned int value_size; //在拷贝进新的哈希表时有用
} hash_node_t;


struct hash
{
    unsigned int buckets;
    unsigned int size; //累加,如果size > buckets / 2 ,则需要开裂建立新表
    hashfunc_t hash_func;
    hash_node_t *nodes;
};

unsigned int next_prime(unsigned int n);
int is_prime(unsigned int n);

unsigned int hash_get_bucket(hash_t *hash, void *key);
hash_node_t *hash_get_node_by_key(hash_t *hash, void *key, unsigned int key_size);


hash_t *hash_alloc(unsigned int buckets, hashfunc_t hash_func)
{
    hash_t *hash = (hash_t *)malloc(sizeof(hash_t));
    //assert(hash != NULL);
    hash->buckets = buckets;
    hash->hash_func = hash_func;
    int size = buckets * sizeof(hash_node_t);
    hash->nodes = (hash_node_t *)malloc(size);
    memset(hash->nodes, 0, size);
    printf("The hash table has allocate.\n");
    return hash;
}

void hash_free(hash_t *hash)
{
    unsigned int buckets = hash->buckets;
    int i;
    for (i = 0; i < buckets; i++)
    {
        if (hash->nodes[i].status != EMPTY)
        {
            free(hash->nodes[i].key);
            free(hash->nodes[i].value);
        }
    }

    free(hash->nodes);

    printf("The hash table has free.\n");
}

void *hash_lookup_entry(hash_t *hash, void *key, unsigned int key_size)
{
    hash_node_t *node = hash_get_node_by_key(hash, key, key_size);
    if (node == NULL)
    {
        return NULL;
    }

    return node->value;
}

void hash_add_entry(hash_t *hash, void *key, unsigned int key_size,
                    void *value, unsigned int value_size)
{
    if (hash_lookup_entry(hash, key, key_size))
    {
        fprintf(stderr, "duplicate hash key\n");
        return;
    }

    unsigned int bucket = hash_get_bucket(hash, key);
    unsigned int i = bucket;
    unsigned int j = i;
    int k  = 1;
    int odd = 1;

    while (hash->nodes[i].status == ACTIVE)
    {
        if (odd)
        {
            i = j + k * k;

            odd = 0;

            // i % hash->buckets;
            while (i >= hash->buckets)
            {
                i -= hash->buckets;
            }
        }
        else
        {
            i = j - k * k;
            odd = 1;

            while (i < 0)
            {
                i += hash->buckets;
            }

            ++k;
        }
    }

    hash->nodes[i].status = ACTIVE;
    if (hash->nodes[i].key) ////释放原来被逻辑删除的项的内存
    {
        free(hash->nodes[i].key);
    }
    hash->nodes[i].key = malloc(key_size);
    hash->nodes[i].key_size = key_size; //保存key_size;
    memcpy(hash->nodes[i].key, key, key_size);
    if (hash->nodes[i].value) //释放原来被逻辑删除的项的内存
    {
        free(hash->nodes[i].value);
    }
    hash->nodes[i].value = malloc(value_size);
    hash->nodes[i].value_size = value_size; //保存value_size;
    memcpy(hash->nodes[i].value, value, value_size);

    if (++(hash->size) < hash->buckets / 2)
        return;


    //在搜索时可以不考虑表装满的情况;
    //但在插入时必须确保表的装填因子不超过0.5。
    //如果超出,必须将表长度扩充一倍,进行表的分裂。

    unsigned int old_buckets = hash->buckets;

    hash->buckets = next_prime(2 * old_buckets);

    hash_node_t *p = hash->nodes;
    unsigned int size;
    hash->size = 0;  //从0 开始计算
    size = sizeof(hash_node_t) * hash->buckets;
    hash->nodes = (hash_node_t *)malloc(size);
    memset(hash->nodes, 0, size);

    for (i = 0; i < old_buckets; i++)
    {
        if (p[i].status == ACTIVE)
        {
            hash_add_entry(hash, p[i].key, p[i].key_size, p[i].value, p[i].value_size);
        }
    }

    for (i = 0; i < old_buckets; i++)
    {
// active or deleted
        if (p[i].key)
        {
            free(p[i].key);
        }
        if (p[i].value)
        {
            free(p[i].value);
        }
    }

    free(p); //释放旧表

}

void hash_free_entry(hash_t *hash, void *key, unsigned int key_size)
{
    hash_node_t *node = hash_get_node_by_key(hash, key, key_size);
    if (node == NULL)
        return;

    // 逻辑删除
    node->status = DELETED;
}

unsigned int hash_get_bucket(hash_t *hash, void *key)
{
    unsigned int bucket = hash->hash_func(hash->buckets, key);
    if (bucket >= hash->buckets)
    {
        fprintf(stderr, "bad bucket lookup\n");
        exit(EXIT_FAILURE);
    }

    return bucket;
}

hash_node_t *hash_get_node_by_key(hash_t *hash, void *key, unsigned int key_size)
{
    unsigned int bucket = hash_get_bucket(hash, key);
    unsigned int i = 1;
    unsigned int pos = bucket;
    int odd = 1;
    unsigned int tmp = pos;
    while (hash->nodes[pos].status != EMPTY && memcmp(key, hash->nodes[pos].key, key_size) != 0)
    {
        if (odd)
        {
            pos = tmp + i * i;

            odd = 0;

            // pos % hash->buckets;
            while (pos >= hash->buckets)
            {
                pos -= hash->buckets;
            }
        }
        else
        {
            pos = tmp - i * i;
            odd = 1;

            while (pos < 0)
            {
                pos += hash->buckets;
            }

            i++;
        }

    }

    if (hash->nodes[pos].status == ACTIVE)
    {
        return &(hash->nodes[pos]);
    }

    // 如果运行到这里,说明pos为空位或者被逻辑删除

    // 可以证明,当表的长度hash->buckets为质数且表的装填因子不超过0.5时,
    // 新的表项 x 一定能够插入,而且任何一个位置不会被探查两次。
    // 因此,只要表中至少有一半空的,就不会有表满问题。

    return NULL;
}

unsigned int next_prime(unsigned int n)
{
    // 偶数不是质数
    if (n % 2 == 0)
    {
        n++;
    }

    for (; !is_prime(n); n += 2); // 不是质数,继续求
    return n;
}

int is_prime(unsigned int n)
{
    unsigned int i;
    for (i = 3; i * i <= n; i += 2)
    {
        if (n % i == 0)
        {
            // 不是,返回0
            return 0;
        }
    }

    // 是,返回1
    return 1;
}

main.c:

#include "hash.h"
#include "common.h"

typedef struct stu
{
    char sno[5];
    char name[32];
    int age;
} stu_t;

typedef struct stu2
{
    int sno;
    char name[32];
    int age;
} stu2_t;


unsigned int hash_str(unsigned int buckets, void *key)
{
    char *sno = (char *)key;
    unsigned int index = 0;

    while (*sno)
    {
        index = *sno + 4 * index;
        sno++;
    }

    return index % buckets;
}

unsigned int hash_int(unsigned int buckets, void *key)
{
    int *sno = (int *)key;
    return (*sno) % buckets;
}

int main(void)
{

    stu2_t stu_arr[] =
    {
        { 1234, "AAAA", 20 },
        { 4568, "BBBB", 23 },
        { 6729, "AAAA", 19 }
    };

    hash_t *hash = hash_alloc(256, hash_int);

    int size = sizeof(stu_arr) / sizeof(stu_arr[0]);
    int i;
    for (i = 0; i < size; i++)
    {
        hash_add_entry(hash, &(stu_arr[i].sno), sizeof(stu_arr[i].sno),
                       &stu_arr[i], sizeof(stu_arr[i]));
    }

    int sno = 4568;
    stu2_t *s = (stu2_t *)hash_lookup_entry(hash, &sno, sizeof(sno));
    if (s)
    {
        printf("%d %s %d\n", s->sno, s->name, s->age);
    }
    else
    {
        printf("not found\n");
    }

    sno = 1234;
    hash_free_entry(hash, &sno, sizeof(sno));
    s = (stu2_t *)hash_lookup_entry(hash, &sno, sizeof(sno));
    if (s)
    {
        printf("%d %s %d\n", s->sno, s->name, s->age);
    }
    else
    {
        printf("not found\n");
    }

    hash_free(hash);

    return 0;
}

simba@ubuntu:~/Documents/code/struct_algorithm/search/hash_table/quardratic_probing$ ./main  The hash table has allocate. 4568 BBBB 23 not found The hash table has free.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码神联盟

红包 | 教你使用 JAVA 抢红包,次次都是运气王

522
来自专栏数据之美

Hive 基础(1):分区、桶、Sort Merge Bucket Join

Hive 已是目前业界最为通用、廉价的构建大数据时代数据仓库的解决方案了,虽然也有 Impala 等后起之秀,但目前从功能、稳定性等方面来说,Hive 的地位...

28110
来自专栏PHP技术

MongoDB数据结构设计中6条重要的经验法则

很多初学者认为在MongoDB中针对一对多建模唯一的方案就是在父文档中内嵌一个数组子文档,但是这是不准确的。因为你可以在MongoDB内嵌一个文档不代表你就必须...

2847
来自专栏函数式编程语言及工具

细谈Slick(5)- 学习体会和将来实际应用的一些想法

   通过一段时间的学习和了解以及前面几篇关于Slick的讨论后对Slick这个函数式数据库编程工具有了些具体的了解。回顾我学习Slick的目的,产生了许多想法...

1718
来自专栏大数据架构

Spark SQL / Catalyst 内部原理 与 RBO

从上图可见,无论是直接使用 SQL 语句还是使用 DataFrame,都会经过如下步骤转换成 DAG 对 RDD 的操作

1126
来自专栏瓜大三哥

HLS综合策略

Loop:rolled00 Array: BRAM Struct:被分解为成员变量 操作符:硬件核 优化策略 The Initial Optimization...

1957
来自专栏大数据架构

Spark SQL / Catalyst 内部原理 与 RBO

从上图可见,无论是直接使用 SQL 语句还是使用 DataFrame,都会经过如下步骤转换成 DAG 对 RDD 的操作

432
来自专栏熊二哥

SQL优化快速入门

最近遇到一个专门进行SQL技术优化的项目,对很多既有的老存储过程进行调优(现在已经不再新增任何存储过程),因此系统的对SQL语句编写进行一次科学的学习变得很有必...

1909
来自专栏PingCAP的专栏

TiDB 源码阅读系列文章(十五)Sort Merge Join

在开始阅读源码之前, 我们来看看什么是 Sort Merge Join (SMJ),定义可以看 wikipedia。简单说来就是将 Join 的两个表,首先根据...

1080
来自专栏美团技术团队

美团点评SQL优化工具SQLAdvisor开源

介绍 在数据库运维过程中,优化 SQL 是 DBA 团队的日常任务。例行 SQL 优化,不仅可以提升程序性能,还能够降低线上故障的概率。 目前常用的 SQL 优...

3176

扫码关注云+社区