C++数据结构哈希表详解

目录
  • 实现
  • 散列函数
  • 开散列方法
  • 闭散列方法(开地址方法)
  • 删除*

实现

哈希表,即散列表,可以快速地存储和查询记录。理想哈希表的存储和查询时间都是 O(1)。

本《资料》中哈希表分以下几部分:散列函数、存储和查找时的元素定位、存储、查找。删除操作因为不常用,所以只给出思想,不给出代码。

根据实际情况,可选择不同的散列方法。

以下代码假设哈希表不会溢出。

// N表示哈希表长度,是一个素数,M表示额外空间的大小,empty代表“没有元素”。
const int N=9997, M=10000, empty=-1;
int a[N];
void init() // 初始化哈希表
{
memset(a,empty,sizeof(a)); // 注意,只有empty等于0或-1时才可以这样做!
memset(bucket,empty,sizeof(bucket));
memset(first,0,sizeof(first));
}
inline int h(int); // 散列函数
int *locate(int, bool); // 用于存储和查找的定位函数,并返回对应位置。
// 如果用于存储,则第二个参数为true,否则为false①。
void save(int x) // 存储数据
{
int *p = locate(x, true);
if (p!=NULL) *p=x;
}
bool isexist(int x) // 查找数据
{
int *p = locate(x,false);
return (p!=NULL && *p==x);
}

散列函数

为了达到快速存储和查找的目的,就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系 h。

这个关系 h 叫做哈希函数。

哈希表存取方便但存储时容易冲突:即不同的关键字可以对应同一哈希地址。如何确定哈希函数和解决冲突是关键。以下是几种常见的哈希函数的构造方法:

1. 取余数法:h(x) = x%p(p≤N,且最好是素数)

2. 直接定址法:h(x)=x 或 h(x)=a*x+b

3. 数字分析法:取关键字的若干数位(如中间两位数)组成哈希地址。

4. 平方取中法:关键字平方后取中间几位数组成哈希地址。

5. 折叠法:将关键数字分割成位数相同的几部分(最后一部分的位数可以不同)然后取几部分的叠加和(舍去进位)作为哈希地址。

6. 伪随机数法:事先产生一个随机数序列 r[],然后令 h(x)=r[x]。

设计哈希函数时,要注意

对关键码值的分布并不了解——希望选择的散列函数在关键码范围内能够产生一个大致平均的关键码值随机分布,同时避免明显的聚集可能性,如对关键码值的高位或低位敏感的散列函数。

对关键码值的分布有所了解——应该使用一个依赖于分布的散列函数,避免把一组相关的关键码值映射到散列表的同一个槽中。

开散列方法

哈希表中难免会发生冲突。使用开散列方法可以解决这个问题。常用操作方法是“拉链法”,即相同的地址的关键字值均链入对应的链表中。

如果散列函数很差,就容易形成长长的链表,从而影响查找的效率。

下面是用“拉链法”处理冲突时的定位函数:

int size=-1;
struct node {int v; node * next;} *first[N], mem[M];
#define NEW(p) p=&mem[++size]; p->next=NULL
int * locate(int x, bool ins=false)
{
int p=h(x);
if (a[p]==x && !ins) return &a[p];
// 处理冲突
node *q = first[p];
if (ins)
if (q==NULL)
{
NEW(q);
first[p]=q;
return &q->v;
}
else
{
while (q->next!=NULL) q=q->next;
node *r; NEW(r);
q->next=r;
return &r->v;
}
else
while (q!=NULL)
{
if (q->v == x) return &q->v;
q=q->next;
}
return NULL;
}

闭散列方法(开地址方法)

处理冲突的另一种方法是为该关键字的记录找到另一个“空”的哈希地址。在处理中可能得到一个地址序列 g(i)(i=1,2,…,k;0≤g(i)≤n-1),即在处理冲突时若得到的另一个哈希地址 g(1)仍发生冲突,再

求下一地址 g(2),若仍冲突,再求 g(3)……怎样得到 g(i)呢?

溢出桶法:设一个溢出桶,不管得到的哈希地址如何,一旦发生冲突,都填入溢出桶。

再哈希法:使用另外一种哈希函数来定位。

线性探查:g(i)=(h(x)+di) % N,其中 h(x)为哈希函数,N 为哈希表长,di 为增量序列。

1. 线性探测再散列:di=1,2,3,…,m-1

2. 二次探测再散列:

3. 伪随机探测序列:事先产生一个随机数序列 random[],令 di=random[i]。

下面是用溢出桶处理冲突时的定位函数:

int bucket[M], top=-1; // 用于闭散列方法(溢出桶)
int * locate(int x, bool ins=false)
{
int p=h(x);
if (a[p]==x && !ins) // 在查找模式下碰到了所需的元素
return &a[p];
else if (ins)
{
if (a[p]==empty) // 可以插入
return &a[p];
else // 处理冲突
return &bucket[++top];
}
else // 到溢出桶中寻找元素
for (int i=0; i<=top; i++)
if (bucket[i]==x) return &bucket[i];
return NULL;
}

下面是用线性探查处理冲突的定位函数,当然,它也可以用于再哈希法处理冲突

inline int g(int p, int i) {return (p+i)%N;} // 根据需要来设计
int * locate(int x, bool ins=false)
{
int p=h(x);
int p2, c=0;
if (a[p]==x && !ins)
return &a[p];
else if (ins)
{
do
{
p2 = g(p, c++);
} while (a[p2]!=empty);
return &a[p2];
} else {
do
{
p2 = g(p, c++);
} while (a[p2]!=x && a[p2]!=empty);
if (a[p2]==x) return &a[p2];
}
return NULL;
}

闭散列方法的优点是节省空间。不过,无论是溢出桶,还是线性探查,都会在寻址过程中浪费时间。线性

探查的探查序列如果太长,就会使一些其他元素被迫散列在其他位置,从而影响了其他元素的查找效率。

删除*

如果使用开散列方法,那么可以直接删除元素。然而,使用闭散列方法,是不可以直接删除元素的。假如

直接删除,很有可能会影响其他元素的查找。

在这种情况下,有两种删除方法:一种是交换法,另一种是标记法。

交换法:在删除某元素时,不要立刻把它清除。按照线性探查函数继续寻找,直到没有数值为止。将遇到

的最后一个数值与它交换。当然,交换之前还要进行类似的操作,可谓“牵一发而动全身”。

标记法:开一个标记数组 flag[]。如果第 i 个元素被删除了,就将 flag[i]设为 true。

1. 插入元素时,如果所在位置有标记,就把元素放到这里,并把标记清除。

2. 查找元素时,如果经过标记,就跳过去继续查找。

3. 为了哈希表的效率,应该定期清理表中的标记(或重新散列所有元素)。

到此这篇关于C++数据结构哈希表详解的文章就介绍到这了,更多相关C++哈希表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++基础算法基于哈希表的索引堆变形

    目录 问题来源 问题简述 问题分析 代码展示 问题来源 此题来自于Hackerrank中的QHEAP1问题,考查了对堆结构的充分理解.成功完成此题,对最大堆或者最小堆的基本操作实现就没什么太大问题了. 问题简述 实现一个最小堆,对3种类型的输入能给出正确的操作: "1 v" - 表示往堆中增加一个值为v的元素 "2 v" - 表示删去堆中值为v的元素 "3" - 表示打印出堆中最小的那个元素 注意:题目保证了要删的元素必然是在堆中的,并且在任何时

  • C++中使用哈希表(unordered_map)的一些常用操作方法

    目录 1.建立基本数据类型的哈希表 2.向哈希表中添加元素 1).insert函数 2).用数组方法直接添加 3.成员函数 begin(),end()函数 find()查找函数 count()查找函数 size()函数 empty()函数 clear()函数 swap()函数 哈希表的遍历 第一种遍历 第二种遍历 补充:实际应用 总结 1.建立基本数据类型的哈希表 unordered_map<int,int> m; //<string,string>,<char,char&g

  • 基于一致性hash算法 C++语言的实现详解

    一致性hash算法实现有两个关键问题需要解决,一个是用于结点存储和查找的数据结构的选择,另一个是结点hash算法的选择. 首先来谈一下一致性hash算法中用于存储结点的数据结构.通过了解一致性hash的原理,我们知道结点可以想象为是存储在一个环形的数据结构上(如下图),结点A.B.C.D按hash值在环形分布上是有序的,也就是说结点可以按hash值存储在一个有序的队列里.如下图所示,当一个hash值为-2^20的请求点P查找路由结点时,一致性hash算法会按hash值的顺时针方向路由到第一个结点

  • C++深入探究哈希表如何封装出unordered_set和unordered_map

    目录 封装前的哈希代码 泛型 获取key 自定义哈希规则 哈希表模板参数解释 迭代器 结构 operator++() 构造函数 重载运算符 小问题 代码汇总 Hash.h MyUnordered_map.h MyUnordered_set.h 默认你已经实现了哈希表(开散列) 封装前的哈希代码 namespace HashBucket { template<class K,class V> struct HashNode { pair<K, V> _kv; HashNode* _n

  • C++哈希表之线性探测法实现详解

    目录 1.哈希表-线性探测法理论 1.1.哈希表的增加元素 1.2.哈希表的查询操作 1.3.哈希表的删除操作 2.哈希表-线性探测法代码实现 2.1.素数表中的素数 1.哈希表-线性探测法理论 线性探测法的理论我们在上一篇博客已经阐述了. 现在我们来看看线性探测法的增删查的代码思想: 1.1.哈希表的增加元素 注意: 往后遍历寻找空闲位置的时候,要注意是环形遍历哦!不然访问数组就越界了. 在添加元素,发生位置被占用,即发生哈希冲突后,在向后遍历寻找空闲位置的时候,我们要知道,这个空闲的位置是有

  • C++ 实现哈希表的实例

    C++ 实现哈希表的实例 该散列表的散列函数采用了除法散列函数.乘法散列函数.全域散列函数,每一个槽都是使用有序单向链表实现. 实现代码: LinkNode.h #include<iostream> using namespace std; class Link; class LinkNode { private: int key; LinkNode* next; friend Link; public: LinkNode():key(-1),next(NULL){} LinkNode(int

  • C++语言实现hash表详解及实例代码

    C++语言实现hash表详解 概要: hash表,有时候也被称为散列表.个人认为,hash表是介于链表和二叉树之间的一种中间结构.链表使用十分方便,但是数据查找十分麻烦:二叉树中的数据严格有序,但是这是以多一个指针作为代价的结果.hash表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便. 打个比方来说,所有的数据就好像许许多多的书本.如果这些书本是一本一本堆起来的,就好像链表或者线性表一样,整个数据会显得非常的无序和凌乱,在你找到自己需要的书之前,你要经历许多的查询过程:而如果

  • C语言数据结构哈希表详解

    /* * 程序名:hash.c,此程序演示哈希表的实现,数据元素单链表带头结点. * */ #include <stdio.h> #include <stdlib.h> #include <string.h> // 哈希表中数据元素的结构体. typedef struct Element { unsigned int key; // 关键字. int value; // 数据元素其它数据项,可以是任意数据类型. // char value[1001]; // 数据元素其

  • C++数据结构哈希表详解

    目录 实现 散列函数 开散列方法 闭散列方法(开地址方法) 删除* 实现 哈希表,即散列表,可以快速地存储和查询记录.理想哈希表的存储和查询时间都是 O(1). 本<资料>中哈希表分以下几部分:散列函数.存储和查找时的元素定位.存储.查找.删除操作因为不常用,所以只给出思想,不给出代码. 根据实际情况,可选择不同的散列方法. 以下代码假设哈希表不会溢出. // N表示哈希表长度,是一个素数,M表示额外空间的大小,empty代表"没有元素". const int N=9997

  • Java 哈希表详解(google 公司的上机题)

    1 哈希表(散列)-Google 上机题 1) 看一个实际需求,google 公司的一个上机题: 2) 有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址..),当输入该员工的 id 时,要求查 找到该员工的 所有信息. 3) 要求: 不使用数据库,尽量节省内存,速度越快越好=>哈希表(散列) 2 哈希表的基本介绍 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通 过把关键码值映射到表中一个位置

  • C++实现数据结构的顺序表详解

    目录 前言: 代码 1.SeqList.h 2.SeqList.cpp 3.test.cpp 总结 前言: hello,大家好,这篇文章博主来分享一下C++实现数据结构中的顺序表的代码.希望对大家有所帮助. 在博主之前的文章中,已经详细地写过顺序表,读者可以点击查看C语言如何建立链表并实现增删查改,在之前的文章中,是用C语言来实现的,这篇文章中,我们用C++来实现. 代码 1.SeqList.h #ifndef SEQLIST_H #define SEQLIST_H #include<iostr

  • Python数据结构与算法之跳表详解

    目录 0. 学习目标 1. 跳表的基本概念 1.1 跳表介绍 1.2 跳表的性能 1.3 跳表与普通链表的异同 2. 跳表的实现 2.1 跳表结点类 2.2 跳表的初始化 2.3 获取跳表长度 2.4 读取指定位置元素 2.5 查找指定元素 2.6 在跳表中插入新元素 2.7 删除跳表中指定元素 2.8 其它一些有用的操作 3. 跳表应用 3.1 跳表应用示例 0. 学习目标 在诸如单链表.双线链表等普通链表中,查找.插入和删除操作由于必须从头结点遍历链表才能找到相关链表,因此时间复杂度均为O(

  • Java数据结构之散列表详解

    目录 介绍 1 散列表概述 1.1 散列表概述 1.2 散列冲突(hash collision) 2 散列函数的选择 2.1 散列函数的要求 2.2 散列函数构造方法 3 散列冲突的解决 3.1 分离链接法 3.2 开放定址法 3.3 再散列法 4 散列表的简单实现 4.1 测试 介绍 本文详细介绍了散列表的概念.散列函数的选择.散列冲突的解决办法,并且最后提供了一种散列表的Java代码实现. 数组的特点是寻址容易,插入和删除困难:而链表的特点是寻址困难,插入和删除容易.而对于tree结构,它们

  • C语言数据结构之单向链表详解分析

    链表的概念:链表是一种动态存储分布的数据结构,由若干个同一结构类型的结点依次串连而成. 链表分为单向链表和双向链表. 链表变量一般用指针head表示,用来存放链表首结点的地址. 每个结点由数据部分和下一个结点的地址部分组成,即每个结点都指向下一个结点.最后一个结点称为表尾,其下一个结点的地址部分的值为NULL(表示为空地址). 特别注意:链表中的各个结点在内存中是可以不连续存放的,具体存放位置由系统分配. 例如:int *ptr ; 因此不可以用ptr++的方式来寻找下一个结点. 使用链表的优点

  • Java数据结构之KMP算法详解以及代码实现

    目录 暴力匹配算法(Brute-Force,BF) 概念和原理 next数组 KMP匹配 KMP全匹配 总结 我们此前学了前缀树Trie的实现原理以及Java代码的实现.Trie树很好,但是它只能基于前缀匹配实现功能.但是如果我们的需求是:一个已知字符串中查找子串,并且子串并不一定符合前缀匹配,那么此时Trie树就无能为力了. 实际上这种字符串匹配的需求,在开发中非常常见,例如判断一个字符串是否包括某些子串,然后进行分别的处理. 暴力匹配算法(Brute-Force,BF) 这是最常见的算法字符

  • ES6新增数据结构WeakSet的用法详解

    WeakSet和Set类似,同样是元素不重复的集合,它们的区别是WeakSet内的元素必须是对象,不能是其它类型. 特性: 1.元素必须是对象. 添加一个number类型的元素. const ws = new WeakSet() ws.add(1) 结果是报类型错误. TypeError: Invalid value used in weak set 添加一个对象. const ws = new WeakSet() var a = {p1:'1', p2:'2'} ws.add(a) conso

  • 数据结构 红黑树的详解

    数据结构 红黑树的详解 红黑树是具有下列着色性质的二叉查找树: 1.每一个节点或者着红色,或者着黑色. 2.根是黑色的. 3.如果一个节点是红色的,那么它的子节点必须是黑色. 4.从一个节点到一个NULL指针的每一条路径必须包含相同数目的黑色节点. 下面是一棵红黑树. 1.自底向上插入 通常把新项作为树叶放到树中.如果我们把该项涂成黑色,那么违反条件4,因为将会建立一条更长的黑节点路径.因此这一项必须涂成红色.如果它的父节点是黑色的,插入完成.如果父节点是红色的,那么违反条件3.在这种情况下我们

随机推荐