1. 哈希是一种算法,哈希表是用哈希算法构造出来的一种数据结构
2. 哈希算方法的几种方法
- 直接定值法
这里有一个例题,就是我们想判断某一字符串中,某一个字符出现的个数,我们可以使用哈希的思想,就是可以遍历一遍字符串,然后开辟一个拥有26数据的整型数组,然后初始化全部为0,然后统计利用一种映射的思想,遍历字符串的时候,就把相应的位置++,每次我们查找某一个字符的时候,一下子就可以定值到那个字符的位置
还有一个例子就是,比如让我们存储1-10这个区间的不重复的数据,这个时候我么也是开辟一个拥有十个元素的整型数组,然后每个数据对应给位置,这样我们在查找的时候,就可以直接查找到这个数据是否存在。
第三个例子是,这里有一些 数据,比如是1001,1006,1009,1007这个时候我们知道数据的范围,我们也是可以开辟一个存储空间来存放这个数据,只不过这个时候我们不需要开辟1000多个大小的数组,我么只需要开辟十个即可,上面的每个数据都减去1000,然后在存放在这个位置,然后我们读取的时候也是按照这个规则读取
分析:上面的几种都是特殊的简单的情况,太具有局限性,还有就是如果我们的数据跨度很大的话,要开辟很大的存储空间,这显然是不合适的,但是一种解题的思路
- 除留取余法
这里有一部分数据然后我们不能开辟所有的大小的空间,这个时候我呢就出现了一个除留取余法,我们的可以把数据都取余数组的长度,然后放置在相应的位置上面去。
致命缺点:哈希冲突,即是我们的数据可能映射的位置是同一个位置。
法一:闭散列法--开放地址法
(1)第一种方式是线性探测
其他的发生冲突的数据直接往后面走,直到有一个位置上面的数据为空,这个时候把数据放置上去。
比如我么的数据,有89,18,49,58,9,我们开辟的整型数组的空间空间时十个,然后我们放置89,89%10 = 9,然后把89放置在了第九个位置上面去,接着是放置18,18被放置在了8这个位置上面。然后拿到49的时候,我们的数据9这个位置已经后数据,这个时候,我们依次往后走,知道扎到了0号位置是空的,于是49被放置在了0这个位置上面去了。同样 的方法放置其他的数据。
数据放置好了之后,就是查找数据了,这个是时候,如果找9,首先定位到9这个位置上面上,然后没有找到,然后就是依次往后面找,知道找到9,或者就是找到一个空位置停止,这个时候 是没有找到相关的数据。
但是这种方式留下了一个问题就是,如果我们冲突的数据都是集中在了9这个位置了,那每次查找的时候是不是很麻烦,这个时候我我们采用的方法及时二次探测。
(2)第二种方式是二次探测
二次探测的方式值这样的,89进来的时候还是直接放置数据,但是当49进来的时候,不是直接往后面放置了,而是往后移动1的平方个位置,如果是空的就是直接放置,如果不是空的,就是相对于起始位置移动2的 平方个,依次类推下去,直到有一个位置为空。
第一个问题:我们既然已经知道了一次探测的冲突比较大,所以我们这里直接采用的是二次探测
第二个问题:我们的考虑使用数组,但是因为后面的数据可能会增加的很大,所以我们还会考虑增容的操作,这个时候我们不妨直接使用vector。
第三个问题:我们一开始的时候,初始化的时候,初始值应该是多少呢,大家可能和自然的想到了是0,但是有一个问题就是,但是如果我要找的数据就是0的,那不是GG了,所以我们在设计类的成员变量的时候的时候,vector里面的内容 可以放置的是一个结构体,结构体的一部分是数据,一部分是标识位,标识我们的这个位置上面有没有数据
第四个问题:我们既然有增加数据,就一定有删除数据,那个如果是上面的例子,我现在删除了一个数据是49,然后我接下来可能是要找9,这个时候首先定位到下标为9的这个位置,然后往后面二次探测1的平方,这个时候找到了49这个 位置,发现是空,就不往下找了,显然这是不符合 我们的要求的,于是我们想设计结构体的时候,这个标识位有三个值,一个标识有数据,一个标识没有数据,一个标识删除数据,于是我们想到了枚举类型。
第五个问题:我们需要考虑的什么时候增容呢,一开始的想法是当vector满了之后再增容 ,可是这种增容的话,当我们的vector满的 时候是不是数据的冲突就可能大呢,于是我们想的是当已经 放置的数据个数的大小时vector的容量大小的0.7的时候开始增容。
第六个问题:我们的数据在放入的时候,可能在二次探测的时候,可能会出现一只循环在写某些位置,这个 问题下一讲在解决。
代码:hashtable.h
#include<iostream>
using namespace std;
#include<vector>
//作为一个标记位
enum State
{
EMPTY,
EXIT,
DEL
};
//哈希节点
template<class K,class V>
struct HashNode
{
pair<K, V> _kv;
State _state;
HashNode()
:_state(EMPTY)
{
}
};
template<class K,class V>
ostream& operator<<(ostream &out, HashNode<K,V> node)
{
out << " "<< " " << node._state;
return out;
}
template<class K,class V>
class HashTable
{
//friend ostream& operator<<(ostream &out, HashNode<K, V> node);
public:
HashTable()
:_n(0)
{
_table.resize(10); //预留的数租空间大小是
}
//插入函数
bool Insert(const pair<K,V> node)
{
Capacity();
int m = HanshFunc(node); //HanshFunc,找到我需要插入的一个下标
size_t index = m;
size_t i = 0;
while (_table[index]._state == EXIT)
{
index = m;
i++;
index = m + i*i;
while (index >= _table.capacity())
{
index = index - _table.capacity();
}
}
_table[index]._kv = node;
_table[index]._state = EXIT;
_n++;
return true;
}
int Search(const pair<K, V> node)
{
size_t m = HanshFunc(node);
size_t index = m;
size_t i = 0;
while (_table[index]._kv.first != node.first)
{
if (_table[index]._state == EMPTY)
{
return -1;
}
index = m;
i++;
index = m + i*i;
while (index >= _table.capacity())
{
index = index - _table.capacity();
}
}
return index;
}
private:
void Capacity()
{
//if (_n / _table.capacity() >= 0.7) //这类需要改进的一个地方 就是,我们不能使用0.7,因为除以了之后,不是一个小数
//{
// printf("增容\n");
//}
if (_n * 10 / _table.capacity() >= 7)
{
printf("增容\n");
}
}
int HanshFunc(const pair<K, V> node)
{
size_t index = node.first % _table.capacity();
return index;
}
private:
vector<HashNode<K,V>> _table; //这个是数组
size_t _n; //当前放置的数据的个数是多少
};HashTable.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"hashtable.h"
int main()
{
HashTable<int, int> a;
pair<int, int> p(5,6);
pair<int, int> p1(15,7);
pair<int, int> p2(25, 6);
pair<int, int> p3(85, 6);
pair<int, int> p4(75, 6);
pair<int, int> p5(95, 6);
pair<int, int> p6(2, 6);
pair<int, int> p7(4, 6);
a.Insert(p);
a.Insert(p1);
a.Insert(p2);
a.Insert(p3);
a.Insert(p4);
a.Insert(p5);
a.Insert(p6);
a.Insert(p7);
cout << a.Search(p);
return 0;
}
本文介绍了哈希表的基本概念,包括哈希算法及其构造的数据结构,并详细探讨了几种哈希方法,如直接定值法和除留取余法。通过实例展示了如何处理哈希冲突,特别是闭散列法中的线性探测和二次探测方法。
7976

被折叠的 条评论
为什么被折叠?



