哈希表及其常用算法

本文转自 https://www.cnblogs.com/big-devil/p/8590242.html
在此仅仅将原文粘贴到这里,做一下代码的笔记,以便于以后理解

<hash表的特性>

Hash 表是使用 O(1) 时间进行数据的插入删除和查找,但是 hash 表不保证表中数据的有序性,
这样在 hash 表中查找最大数据或者最小数据的时间是 O(N) 。

<寻址和 hash 函数>
理想状态下 hash 足够大,每一数据保存在一个 hash 存储单元内,这样对于插入删除和查找某一个数据就可以直接得到。但是现实情况下 hash 表不可能无限大,而且理论上保存的数据的个数是没有限制的,这样保存的数据的数量就远远大于 hash 表的存储单元的数量。

为了实现在 O(1) 内对数据进行插入删除和查找,就必须将一个数据映射到 hash 表中的固定位置,
这个映射函数就是 hash 函数。 Hash 函数通过对数据进行计算得到一个在 hash 表中的位置地址。


图 1.1 理想的 hash 表

  要选择较好的 hash 函数,以及 hash 表存储单元的数量,这样才能使保存在 hash 表中的
  数据均匀分布。理想状态不太可能实现,由于存储的数据数量远远大于 hash 表存储单元的数量,
  所以再好的 hash 函数也可能使不同的数据得到相同的映射位置,这就造成了冲突。
  但是好的 hash 函数可以将这种冲突降到最低。

<分离链接>
解决这种冲突的第一种方法是借助链表来实现,就是将数据实际存放在与 hash 表存储单元相链接的链表中,而不是 hash 的存储单元中。


图 2.1 分离链表

 当产生冲突的时候,将两个数据都链接在同一 hash 存储单元保存的链表中。
 当一个存储单元保存的链表中有多个数据的时候,对于链表后面的数据的查找添加和删除
 就是不是严格意义上的 O(1) 了。一个好的 hash 函数可以使得这个链表很短。最坏情况下,
 当所有的数据都保存在一个 hash 单元指定的链表中的时候,那么这个 hash 就和链表一样了。

<开放地址>
使用开放地址方法解决冲突的时候,数据仍然保存在 hash 表的存储单元中,但是当冲突发生的时候,要再次计算新的地址。

     常用的开放地址法是线性探查,就是当对一个数据进行插入删除或者查找的时候,
     通过 hash 函数计算,发现这个位置不是要找的数据,这时候就检查下一个存储单元,
     一直找到要操作的数据为止。

     除了线性探查外还有二次探查,再 hash 等等方法,都是当一次计算得到的位置
     不是要找到的数据的时候,怎样再次确定新的位置。

<完全 hash 表>
采用分离链表的方式解决冲突的时候,当多个数据被映射到一个地址的时候,它们就形成了一个链表,要操作这其中的一个数据,那么就必须在这个链表中进行操作。如果 hash 函数选择的好的话,链表会很短,这样的操作近似 O(1) ,但并不是精确的等于 O(1) ,它与链表的长度有关。对于数据的访问的最坏情况的访问也是 O(1) 的 hash 叫做完全 hash 表。

     这样的 hash 表是一个两级的 hash 表,第一级的 hash 与使用分离链接方法的 hash 一样,
     但是 hash 存储单元中指向的不是一个链表,而是另一个 hash 表。

在这里插入图片描述
图 4.1 完全 hash 表

     要小心的选择一级以及二级的 hash 函数可以完全保证在二级 hash 表中不会出现冲突。

➤常用算法

➣直接定址法 :地址集合 和 关键字集合大小相同

➣数字分析法 :根据需要hash的 关键字的特点选择合适hash算法,尽量寻找每个关键字的 不同点

➣平方取中法:取关键字平方之后的中间极为作为哈希地址,一个数平方之后中间几位数字与数的每一位都相关,取得位数由表长决定。比如:表长为512,=2^9,可以取平方之后中间9位二进制数作为哈希地址。

➣折叠法:关键字位数很多,而且关键字中每一位上的数字分布大致均匀的时候,可以采用折叠法得到哈希地址,除留取余法除P取余,可以选P为质数,或者不含有小于20的质因子的合数

➣随机数法:通常关键字不等的时候采用此法构造哈希函数较恰当。

➤实际工作中需要视不同的情况采用不同的hash函数

➣考虑因素:计算哈希函数所需要的时间,硬件指令等因素。

➣关键字长度

➣哈希表大小

➣关键字分布情况

➣记录查找的频率。(huffeman树)

示例代码:
/* hash.h */

#ifndef HASH_H
#define HASH_H

	struct HashTable;
	struct ListNote;
	typedef struct HashTable *HashTbl;
	typedef struct ListNote *Position;
	typedef Position List;
	int Hash(int key, int tablesize);
	int NextPrime(int x);
	HashTbl InitalizeTable(int TableSize);
	void DestroyTable(HashTbl H);
	Position Find(int key, HashTbl H);
	void Insert(int key, HashTbl H);
	void Delete(int key, HashTbl H);

	struct HashTable{
		int TableSize;
		Position *TheList;  // = ListNote **TheList;
	};

	struct ListNote{
		int element;
		Position next;  // = ListNote *next;
	};

#endif

/********************************************************************/
/* hash.cpp */

#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include "hash.h"

int Hash(int key, int tablesize)
{
	return key%tablesize;
}

int NextPrime(int x)
{
	int flag;
	int i;
	int n;
	while(1)
	{
		flag = 0;
		n = sqrt((float)x);
		for(i=2; i<=n; i++)
		{
			if(x%i == 0)
			{
				flag = 1;
				break;
			}
		}
		if(flag == 0)
			return x;
		else 
			x++;
	}
}

HashTbl InitalizeTable(int TableSize)
{
	if(TableSize <= 0)
	{
		printf("散列大小有问题\n");
		return NULL;
	}

	HashTbl table = (HashTbl)malloc(sizeof(struct HashTable));  //分配 HashTable
	if(table == NULL)
		printf("分配失败\n");

	table->TableSize = NextPrime(TableSize);
	//为 HashTable 分配一堆能放二级指针的空间
	table->TheList = (Position*)malloc(sizeof(List) * table->TableSize);
	if(table->TheList == NULL)
		printf("分配失败\n");

	//为第一个二级指针指向的一级指针分配内存空间,并将分配到的内存空间地址写入到二级指针中
	//这里并没有仅仅分配一个空间,而是分配了好多,以供后面使用
	table->TheList[0] = (Position)malloc(table->TableSize * sizeof(struct ListNote));
	if(table->TheList[0] == NULL)
		printf("分配失败\n");

	int i;
	for(i=0;  i<table->TableSize; i++)
	{
		//为存在的二级指针赋值它们应当指向的一级指针,注意 +i 并不是加1或者加2,而是加的类型长度
		table->TheList[i] = table->TheList[0]+i; 
		table->TheList[i]->next = NULL; //一级指针 next 指向空
	}
	return table;
}

Position Find(int key, HashTbl H)
{
	Position p;
	List L = H->TheList[Hash(key, H->TableSize)];
	p = L->next; //二级指针指向的第一个一级指针并没有被使用,而是使用的它的next
	while(p != NULL && p->element != key)
		p = p->next;
	if(p == NULL) //没有找到则返回二级指针指向的第一个一级指针的数据
		return L;
	else
		return p;
}

void Insert(int key, HashTbl H)
{
	Position p, NewCell;
	p = Find(key, H);
	if(p->element != key) //这个空间没有被占用,或者说该值不存在
	{
		NewCell = (Position)malloc(sizeof(struct ListNote));
		if(NewCell == NULL)
			printf("分配失败\n");
		else
		{
			p = H->TheList[Hash(key, H->TableSize)];
			NewCell->next = p->next; //表头没有被使用,相当于在第一个链(表头)和第二个链之间插入一个链
			p->next = NewCell;
			NewCell->element = key;
		}
	}
	else
		printf("已存在该值\n");
}

void Delete(int key, HashTbl H)
{
	Position p, NewCell;
	p = Find(key, H);
	if(p->element == key)
	{
		NewCell = H->TheList[Hash(key, H->TableSize)]; //找到二级指针指向的一级指针(表头)
		while(NewCell->next != p) //遍历整个链表找到key值正确的那个
			NewCell = NewCell->next;
		NewCell->next = p->next;  //当前链表指向要删除的链的下一个,删除链
		free(p);
	}
	else
		printf("没有该值\n");
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值