散列表
散列也称哈希,直接寻址
散列查找
将参与检索的数据与散列值(哈希值)关联起来,生成便于搜索的数据结构。将一堆数据进行哈希函数计算,下次查找数据时,再次计算哈希值即可查找到对应的元素。
散列函数
哈希函数对目标计算一个其对应的哈希值,哈希函数对同一个目标计算的结果是一样的。
散列表
利用哈希值的特性,设计一个全新表结构,哈希表(散列表)。对应元素的关键字(整数)提供给哈希函数可进行计算。
一般有进行取模操作,哈希表的长度是多少,模就是多少。
代码实现
用数组功能实现哈希表
初始化
#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...
12、−12、22、−22...。左右反复查找可提高利用率
链地址法
哈希冲突,链表形式。但链表变得长时,查找效率也会变低,考虑转换为平衡二叉树或者红黑树。
#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));
散列表习题
- 设有一组记录的关键字wei{19,14,23,1,68,20,84,27,55,11,10,79},用链地址法构造散列表,散列函数为H(key)=key MOD 13,散列地址为1的链中有多少个记录
- 设哈希表长为14,哈希函数为H(key)=key%11,表中已有数据的关键字为15,38,61,84.现将关键字为49元素加入到散列表中,二次探测再散列解决冲突,则放入的位置是
按照二次散列的增量序列计算下一个位置 - 选取哈希函数H(key)=(key*3)%11,用线性探测散列法和二次探测再散列处理哈希冲突。试在0~10的散列地址空间中,对关键字序列{22,41,53,46,30,13,1,67}构建哈希表,求等概率情况下查找成功的平均查找长度。其中平均查找长度为表中每一个元素需要查找次数之和的平均值。
线性探测散列的平均查找次数
ASL = 2
二次探测再散列的平均查找次数
ASL=1.875
算法实战
- 两数之和
给定一个整数数组 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;
}