搜索结构之哈希

  • 哈希概念

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。例如:给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。


  • 哈希冲突

对于两个数据元素的关键字Ki 和Kj(i != j),有Ki != Kj,但有:

Hash(Ki)== Hash(Kj)

即不同关键字通过相同哈希函数计算出相同地址,该现象称为哈希冲突或哈希碰撞。具有相同函数值的关键字对该散列函数来说称做同义词。


  • 哈希函数

哈希函数设计原则:

  • 哈希函数定义域必须包括需要存储的全部关键码,如果散列表允许有m个地址时,其值域必须在0~m-1之间
  • 哈希函数计算出来的地址能均匀的分布在整个空间
  • 哈希函数应该比较简单

常见哈希函数:

取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。

 数字分析法

分析一组数据,比如一组员工的出生年月日,这时发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

取关键字平方后的中间几位作为散列地址。

  •  折叠法

将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。

选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。

  •  除留余数法

取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。


  • 处理哈希冲突

闭散列:开放地址法

  • 线性探测
  • 二次探测
  • 哈希表
#pragma once

#include <stdlib.h>
#include <stdio.h>

typedef int Key;

typedef int(*HashFunc)(Key, int);


// 无法直接删除,需要利用状态标记
typedef enum State
{
	EMPTY,
	DELETED,
	EXIST
}  State;

typedef struct Element
{
	Key key;
	State state;
} Element;

typedef struct HashTable
{
	Element *array;
	int size;
	int capacity;
	HashFunc hashFunc;
}   HashTable;


//初始化/销毁
void HashTableInit(HashTable* pHT, int capacity, HashFunc hashFunc)
{
	pHT->array = (Element *)malloc(sizeof(Element)*capacity);
	pHT->size = 0;
	pHT->capacity = capacity;
	pHT->hashFunc = hashFunc;

	//所有状态置为空 EMPTY
	for (int i = 0; i < capacity; i++)
	{
		pHT->array[i].state = EMPTY;
	}
}


void HashTableDestroy(HashTable* pHT)
{
	free(pHT->array);
	pHT->capacity = pHT->size = 0;
	pHT->hashFunc = NULL;
}


// 查找 / 删除 / 插入
// 找到,返回 0
// 没找返回 -1
int HashTableSearch(HashTable* pHT, Key key)
{
	int index = pHT->hashFunc(key, pHT->capacity);
	int i = index;
	int c = 1;
	while (pHT->array[i].state != EMPTY)
	{
		if (pHT->array[i].state == EXIST && key == pHT->array[i].key)
		{
			return 0;
		}
		//i = (index + 1) % pHT->capacity;
		i = (index + (c * c)) % pHT->capacity;//二次探测
		c++;
	}
	// TODO: 看起来,会死循环,因为不会把所有的位置都用掉
	return -1;
}


// 找到,删除成功,返回 0
// 没找到,返回 -1
int HashTableRemove(HashTable* pHT, Key key)
{
	int index = pHT->hashFunc(key, pHT->capacity);
	int i = index;
	int c = 1;
	while (pHT->array[i].state != EMPTY)
	{
		if (pHT->array[i].state == EXIST && key == pHT->array[i].key)
		{
			pHT->array[i].state = DELETED;
			pHT->size--;
			return 0;
		}
		//i = (index + 1) % pHT->capacity;
		i = (index + (c * c)) % pHT->capacity;//二次探测
		c++;
	}
	// TODO: 看起来,会死循环,因为不会把所有的位置都用掉
	return -1;
}

int HashTableInsert(HashTable *pHT, Key key);

void ExpandIfNeed(HashTable* pHT)
{
	if (pHT->size * 10 / pHT->capacity < 7)
	{
		return;
	}

	//两倍扩容
	// 申请新空间
	// 搬移老数据
	// 释放老空间
	int newCapacity = pHT->capacity * 2;
	HashTable tempHT;
	HashTableInit(&tempHT, newCapacity, pHT->hashFunc);
	for (int i = 0; i < pHT->capacity; i++)
	{
		if (pHT->array[i].state==EXIST)
		{
			HashTableInsert(&tempHT, pHT->array[i].key);
		}
	}
	free(pHT->array);
	pHT->array = tempHT.array;
	pHT->capacity = tempHT.capacity;
}

// 插入 key 不允许重复,空间优先
// 先查找一次,如果找到,插入失败 返回 -1
// 没找到,再次遍历,遇到 EMPTY | DELETED 都可以插入了
int HashTableInsert(HashTable *pHT, Key key)
{
	// 进行扩容判断
	ExpandIfNeed(pHT);

	if (HashTableSearch(pHT, key) == 0) {
		return -1;
	}

	int index = pHT->hashFunc(key, pHT->capacity);
	int i = index;
	int c = 1;
	while (pHT->array[i].state == EXIST)
	{
		i = (index + (c * c)) % pHT->capacity;
		c++;
	}
	pHT->array[i].key = key;
	pHT->array[i].state = EXIST;
	pHT->size++;

	return 0;
}

// 实际用到的 hash 函数
int Hash(Key key, int capacity)
{
	return key % capacity;
}


void test()
{
	HashTable ht;
	HashTableInit(&ht, 7, Hash);

	HashTableInsert(&ht, 0);
	HashTableInsert(&ht, 15);
	HashTableInsert(&ht, 1);
	HashTableInsert(&ht, 3);
	HashTableInsert(&ht, 9);
	HashTableInsert(&ht, 10);

	printf("%d\n", HashTableSearch(&ht, 0));
	HashTableRemove(&ht, 0);
	printf("%d\n", HashTableSearch(&ht, 0));

	HashTableDestroy(&ht);
}

开散列:链地址

  • 哈希桶
#pragma once

#include <stdio.h>
#include <stdlib.h>

typedef int Key;

typedef int(*HashFunc)(Key, int);

typedef struct Node 
{
	Key key;
	struct Node* next;
}  Node;


typedef struct HashBucket 
{
	Node** array;
	int size;
	int capacity;
	HashFunc hashFunc;
}   HashBucket;


void HashBucketInit(HashBucket* pHB, int capacity, HashFunc hashFunc)
{
	// 申请 array 的空间
	pHB->array = (Node**)malloc(sizeof(Node)*capacity);
	//初始化array的值
	for (int i = 0; i < capacity; i++)
	{
		pHB->array[i] = NULL;//空链表
	}
	
	pHB->capacity = capacity;
	pHB->size = 0;
	pHB->hashFunc = hashFunc;
}


void HashBucketDestroy(HashBucket* pHB)
{
	// 从堆上申请的空间的释放
	for (int i = 0; i < pHB->capacity; i++)
	{
		Node*cur;
		Node* next;
		for (cur = pHB->array[i]; cur != NULL; cur=next)
		{
			next = cur->next;
			free(cur);
		}
	}
	free(pHB->array);
}



int HashBucketSearch(HashBucket* pHB, Key key)
{
	int index = pHB->hashFunc(key, pHB->capacity);
	// pHB->array[index] 链表的第一个结点
	for (Node* cur = pHB->array[index]; cur != NULL; cur = cur->next)
	{
		if (key == cur->key)
		{
			return 0;
		}
	}
	return -1;
}


int HashBucketRemove(HashBucket* pHB, Key key)
{
	int index = pHB->hashFunc(key, pHB->capacity);
	// pHB->array[index] 链表的第一个结点
	Node* prev = NULL;
	for (Node* cur = pHB->array[index]; cur != NULL; cur = cur->next)
	{
		if (key == cur->key)
		{
			if (prev == NULL)
			{
				pHB->array[index] = cur->next;
			}
			else
			{
				prev->next = cur->next;
			}
			free(cur);
			pHB->size--;
			return 0;
		}
		prev = cur;
	}
	return -1;
}


int HashBucketInsert(HashBucket* pHB, Key key);

void ExpandIfRequired(HashBucket* pHB)
{
	if (pHB->size * 10 / pHB->capacity < 9) 
	{
		return;
	}

	int newCapacity = pHB->capacity * 2;
	HashBucket tempHB;
	HashBucketInit(&tempHB, newCapacity, pHB->hashFunc);

	for (int i = 0; i < pHB->capacity; i++)
	{
		for (Node* cur = pHB->array[i]; cur != NULL; cur = cur->next)
		{
			HashBucketInsert(&tempHB, cur->key);
		}
	}

	HashBucketDestroy(pHB);
	pHB->array = tempHB.array;
	pHB->capacity = tempHB.capacity;
}


int HashBucketInsert(HashBucket* pHB, Key key)
{
	// 扩容
	ExpandIfRequired(pHB);

	// 不允许重复
	if (HashBucketSearch(pHB, key) == 0) 
	{
		return -1;
	}

	int index = pHB->hashFunc(key, pHB->capacity);
	// 头插,时间节省
	Node *node = (Node *)malloc(sizeof(Node));
	node->key = key;
	node->next = pHB->array[index];
	pHB->array[index] = node;
	pHB->size++;

	return 0;
}



int hash(Key key, int capacity)
{
	return key % capacity;
}


void test2()
{
	HashBucket hb;
	HashBucketInit(&hb, 7, hash);

	HashBucketInsert(&hb, 0);
	HashBucketInsert(&hb, 7);
	HashBucketInsert(&hb, 14);
	HashBucketInsert(&hb, 21);
	HashBucketInsert(&hb, 28);
	HashBucketInsert(&hb, 1);
	HashBucketInsert(&hb, 8);
	HashBucketInsert(&hb, 5);


	printf("%d\n", HashBucketSearch(&hb, 14));
	HashBucketRemove(&hb, 14);
	printf("%d\n", HashBucketSearch(&hb, 14));

	printf("成功\n");
}

  • 哈希变形位图

#pragma once
#include<stdio.h>
#include <stdlib.h>
// 32 个房间的宿舍楼
typedef unsigned int uint32_t;

typedef struct {
	uint32_t *array;
	int size;		//	里面住了多少同学
	int capacity;	//	有多少楼
}	BitMap;


void BitMapInit(BitMap *pBM, int capacity)
{
	pBM->array = (uint32_t *)malloc(sizeof(uint32_t)*capacity);
	pBM->size = 0;
	pBM->capacity = capacity;
}

void BitMapDestroy(BitMap *pBM)
{
	free(pBM);
}



void BitMapSetOne(BitMap* pBM, unsigned int n)
{
	int index = n / 32;
	int bit = n % 32;
	pBM->size++;

	pBM->array[index] = pBM->array[index] | (1 << bit);
}


void BitMapSetZero(BitMap *pBM, unsigned int n)
{
	int index = n / 32;
	int bit = n % 32;
	pBM->size--;

	pBM->array[index] = pBM->array[index] & ~(1 << bit);
}


int BitMapIsSetOne(BitMap* pBM, unsigned int n)
{
	int index = n / 32;
	int bit = n % 32;

	return pBM->array[index] >> bit & 1;
}


void test3()
{
	BitMap bm;
	BitMapInit(&bm, 5);

	BitMapSetOne(&bm, 1);
	BitMapSetOne(&bm, 10);
	BitMapSetOne(&bm, 100);
	BitMapSetOne(&bm, 28);
	BitMapSetOne(&bm, 66);
	BitMapSetOne(&bm, 99);
	BitMapSetOne(&bm, 101);

	printf("%d\n", BitMapIsSetOne(&bm, 1));
	BitMapSetZero(&bm, 1);
	printf("%d\n", BitMapIsSetOne(&bm, 1));
	printf("成功\n");
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值