查找算法系列之复杂算法:哈希查找

眼下为止已经介绍了顺序查找、二分查找、分块查找、二叉排序树。见作者之前的文章:

http://blog.csdn.net/u010025211/article/details/46635325

http://blog.csdn.net/u010025211/article/details/46635183

今天这篇博文将介绍哈希查找。

1.为什么要用哈希查找

之前所讲的查找方法都是须要比較待查找元素与线性表或者树中的元素才干实现。

这种时间复杂度为O(n)或者O(log n),那么有没有可能当给定一个待查找元素x。我们通过一种特殊的计算,计算出该元素在数组A的位置i,那么就能够直接找到该元素A[i]

哈希函数就是这样的特殊的计算,可以减少时间复杂度。

2.特殊计算的定义——哈希函数(散列函数)

STL中就有hashtable这样的类能够直接使用,那么哈希查找是怎么实现的呢?

哈希函数的构造方法:

   2.1 直接定址法:

取keyword或keyword的某个线性函数值为哈希地址。即: 地址H(key) = key 或 H(key) = a*key+b 实例:某大学从1960年開始招生。有历届招生人数统计表A,当中以年份为keyword。则哈希函数可设计为:H(key) = key - 1959 直接定址法因为keyword与存储地址存在一一相应关系,因此,不会 发生冲突现象。


Key


1959


1960


1961


1962


H(key)


0


1


2


3

若此时须要查询1961年得招生人数。那么得到其地址 H(1961) = 2,然后直接取A[2],A[2]中存的就是1961年的招生人数。

2.2 除余法:

选择一个适当的正整数P(P≤表长),用P 去除keyword。取所得余数作为哈希地址。即:H(key) = key % P (P ≤ 表长) 除余法的关键是选取适当的P,一般选P为小于或等于哈希表的长 度m的某个素数为好

例: m = 8,16,32,128。256。512 P = 7,13,31,127,251,503 除余法不仅能够直接对keyword取模,也可在折叠、平方取中等运算 之后取模。

  2.3 平方取中法:

取keyword平方后的中间几位为哈希地址。因为一个数的平方的中间几位与这个数的每一位都有关。因而。平方取中法产生冲突的机会相对较小。平方取中法中所取的位数由表长决定。

例: K = 456 , K2 = 207936 若哈希表的长度m=102,则可取79(中间两位)作为哈希函数值。

   2.4 折叠法:

把一个关键码分成位数同样的几段(最后一段的位数能够, 不同),段的长度取决于哈希表的地址位数,然后将各段的 叠加和(舍去进位)作为哈希地址。

折叠法又分为移位叠加和边界叠加两种。当中,移位叠加是将 各段的最低位对齐,然后相加。而边界叠加则是两个相邻的段沿边界来回折叠,然后对齐相加。

例:keywordK=58242324169,哈希表长度为1000。则将此keyword分成三位一段。两种叠加结果例如以下:582+ 423+ 241+69=315,582+324+ 241+96= 243

当keyword位数非常多,并且keyword中每一位上数字分布大致均匀时,能够使用折叠法。

 2.5 数字分析法:

如果keyword是以r为基的数。而且哈希表中可能出现的keyword都是事先知道的,则可取keyword中的若干位组成哈希地址。

可是在以上的方法中可能存在冲突,比方用取余法时,15%13 =2, 28%13=2。有两个keyword相应同一地址,就会发生冲突。

3.冲突的解决的方法

对不同keyword可能得到同一哈希地址,这一现象称为"冲突",而发生冲突的keyword对于该哈希函数来说。称为"同义词"。因keyword集合比哈希表长度大,故冲突不可避免。

3.1 开放定址法

基本做法:当冲突发生时,使用某种方法在哈希表中形成一探查序列,然后沿着此探查序列逐个单元地查找,直到碰到一个开放的地址(即该地址单元为空)为止。

在哈希表中形成一探查序列时,可有三种不同的方法:

⑴ 线性探測法:

基本思想:将散列看成是一个环形表,探測序列是(如果表长为m):

H(k),H(k)+1,H(k)+2,…,m-1,0,1,…,H(k)-1

用线性探法解决冲突时。求下一个开放地址的公式为:

Hi = (H(k)+i) MOD m

⑵ 二次探測法:

二次探測法的探測序列依次是12,-12,22,-22,…等,当发生冲突时。求下一个开放地址的公式为:

H2i-1 = (H(k)+i2) MOD m

H2i = (H(k)-i2) MOD m (1=< i <= (m-1)/2 )

长处:降低了堆集发生的可能性。

缺点:不easy探測到整个哈希表空间。

⑶ 伪随机探測法:

採用随机探查法解决冲突时,求下一个开放地址的公式为:

Hi = (H(k)+Ri) MOD m

当中:R1,R2,…,Rm-1是1。2。…。m-1的一个随机排列。怎样得随机排列,涉及到随机数的产生问题。

  3.2 再哈希法:

基本做法:当冲突发生时。使用还有一个哈希函数计算得到一个新的哈希地址。直到冲突不再发生时为止,即

Hi = RHi(key) i = 1,2,…,k

当中,RHi均是不同的哈希函数。

这样的方法的长处是不易产生"堆集",但缺点是添加了计算时间。

 3.3 链地址法:

基本做法:将全部keyword为同义词的结点链接在同一个单链表中。若选定的哈希函数所产生的哈希地址为0~m-1,则可将哈希表定义成一个由m个链表头指针组成的指针数组。

这样的方法的长处是:

① 不产生"堆集"。

② 因为结点空间是动态申请的,故更适合于造表前无法确定表长的情况。

③ 从表中删除结点easy。

 3.4 公共溢出区法:

基本做法:如果哈希函数的值域为[0..m-1]。则设向量HashTable[0..m-1]为基本表。每一个分量存放一个记录,另设立向量OverTable[0..v]为溢出表。全部keyword和基本表中keyword为同义词的记录,无论它们由哈希函数得到的哈希地址是什么,一旦发生冲突。都被填入溢出表中。

在哈希表上进行查找的过程和建表的过程基本一致。如果给定的值为K。依据建表时设定的哈希函数H。计算出哈希地址H(K)。若表中该地址相应的空间未被占用,则查找失败。否则将该地址中的结点与给定值K比較,若相等则查找成功,否则按建表时设定的处理冲突方法找下一个地址,如此重复下去,直到找到某个地址空间未被占用(查找失败)或者keyword比較相等(查找成功)为止。

4.哈希查找的实现

hash是一种典型以空间换时间的算法

在哈希表上进行查找的过程和建表的过程基本一致。如果给定的值为K,依据建表时设定的哈希函数H,计算出哈希地址H(K)。若表中该地址相应的空间未被占用,则查找失败,否则将该地址中的结点与给定值K比較,若相等则查找成功,否则按建表时设定的处理冲突方法找下一个地址,如此重复下去,直到找到某个地址空间未被占用(查找失败)或者keyword比較相等(查找成功)为止。

尽管哈希表是在keyword和存储位置之间建立了相应关系,可是因为冲突的发生,哈希表的查找仍然是一个和keyword比較的过程。只是哈希表平均查找长度比顺序查找要小得多。比二分查找也小。

查找过程中需和给定值进行比較的keyword个数取决于下列三个因素:哈希函数、处理冲突的方法和哈希表的装填因子。

哈希函数的"好坏"首先影响出现冲突的频繁程度,但假设哈希函数是均匀的。则一般不考虑它对平均查找长度的影响。

对同一组keyword。设定同样的哈希函数,但使用不同的冲突处理方法,会得到不同的哈希表。它们的平均查找长度也不同。

普通情况下,处理冲突方法同样的哈希表,其平均查找长度依赖于哈希表的装填因子α。

显然。α越小,产生冲突的机会就越小,但α过小,空间的浪费就过多。通过选择一个合适的装填因子α,能够将平均查找长度限定在一个范围内。

兴许将给出參考代码

-----------------------改动于20150626-----------------------------

几种解决冲突方法的详解

对于开放定址法:仅仅能存储和hash表相同长度的数据。因此具有非常大的局限性,并且hash表一旦满了则不能再存。

对于链地址法:事实上就是一个链表数组,不同的关键词相应与不同的链表头,将全部keyword为同义词的结点链接在同一个单链表中。

左边非常明显是个数组,数组的每一个成员包含一个指针,指向一个链表的头。当然这个链表可能为空。也可能元素非常多。我们依据元素的一些特征把元素分配到不同的链表中去,也是依据这些特征。找到正确的链表,再从链表中找出这个元素。

元素特征转变为数组下标的方法就是散列法。也就是hash函数。

适用范围:高速查找,删除的基本数据结构,通常须要总数据量能够放入内存。

 公共溢出区法也是相同的道理。仅仅是将链表改为了一个溢出数组。

/*开放定址法*/
#define tablesize 10//定义hash表的长度

typedef int  HashTable[10];
typedef int  KeyType;

#include <iostream>
using namespace std;

//Search
int Search_HashTable(HashTable ht, KeyType key)
{
	int address = key % tablesize;
	int comparetimer = 0; //aviod the loop of death. 

	while(comparetimer < tablesize && ht[address] != key && ht[address] != -1)
	{
		comparetimer ++;
		address = (address + 1) % tablesize;  // *****sequence probing. *****
	}
	if(comparetimer == tablesize)
		return -1;
	return address;   // no match if ht[address] = -1.
}

//Insert
int Insert_HashTable(HashTable ht, KeyType key)
{
	int address;
	address = Search_HashTable(ht, key);
	if(ht[address] == -1)
	{
		ht[address] = key;
		return 1; //insert success.
	}
	else
		return -1;  //the key has been insert into the hashtable yet,or the HashTable is full.
}

//initialization
void Initial_HashTable(HashTable ht)
{
	for(int i = 0; i < tablesize; i++)
	{
		ht[i] = -1;
	}
}
/* 链地址法*/
#include<stdio.h>
#include<stdlib.h>

#define tablesize 5
typedef int ElemType;
typedef struct HashNode
{
	ElemType elem;
	struct HashNode *next;
}HashNode;  

typedef struct
{
	HashNode ChainHash[tablesize];
	int  count;
}HashTable;  

int hash_mod(ElemType key)
{
	return key % tablesize;
}  

void InsertHash(HashTable *h, int key)
{
	HashNode *p;
	int index;
	p = (HashNode*)malloc(sizeof(HashNode));
	p->elem = key;
	index = hash_mod(key);
	p->next = h->ChainHash[index].next;
	h->ChainHash[index].next = p;
	h->count++;
}  

void CreateHashTable(HashTable *h, int n)
{
	int key;
	int i;
	for(i = 0; i < n; i++)
	{
		printf("Input the  %d key :", i+1);
		scanf_s("%d", &key);
		InsertHash(h, key);
	}
}  

void PrintHashTable(HashTable *h)
{
	int i;
	HashNode *p;
	for(i = 0;i <= tablesize; i++)
	{
		p = h->ChainHash[i].next;
		while(p)
		{
			printf("%-5d", p->elem);
			p = p->next;
		}
	}
}  

int SearchHash(HashTable *h, int key)
{
	HashNode *p;
	int index;
	int counter = 0;
	index = hash_mod(key);
	p = h->ChainHash[index].next;
	while(p)
	{
		if(p->elem == key)
			return 1;
		else
			p = p->next;
	}
	return 0;
}
void main()
{
	int n ,key;
	int i;
	HashTable H;
	printf("input the length of the Hash that we want to build:");
	scanf_s("%d", &n);
	for(i = 0;i <= tablesize; i++)
		H.ChainHash[i].next = NULL;
	H.count = 0;
	CreateHashTable(&H,n);  

	printf("The hash table that we build is:");
	PrintHashTable(&H);  

	printf("\nInput the key that we want to search(-1 for exit):");
	scanf_s("%d", &key);
	while(key != -1)
	{
		if(SearchHash(&H, key))
			printf("There is a %d record in the Hash Table!\n", key);
		else
			printf("There is not a %d record in the Hash Table!\n", key);  

		printf("\nInput the key that we want to search(-1 for exit):");
		scanf_s("%d", &key);
	}  

	free(&H);
	return;
}  

完整代码下载点击:

查找算法代码C++——包含顺序、二分、BST、哈希

点击打开链接

參考文章:http://www.cnblogs.com/li-hao/archive/2011/10/16/2214017.html

完整代码下载点击:

查找算法代码C++——包含顺序、二分、BST、哈希

http://download.csdn.net/detail/u010025211/8841123

时间: 2024-10-13 11:54:47

查找算法系列之复杂算法:哈希查找的相关文章

【从零学习经典算法系列】分治策略实例——二分查找

1.二分查找算法简介 二分查找算法是一种在有序数组中查找某一特定元素的搜索算法.搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束:如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较.如果在某一步骤数组 为空,则代表找不到.这种搜索算法每一次比较都使搜索范围缩小一半.折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn). 二分查找的优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且

趣写算法系列之--匈牙利算法(真的很好理解)

[书本上的算法往往讲得非常复杂,我和我的朋友计划用一些简单通俗的例子来描述算法的流程] 匈牙利算法是由匈牙利数学家Edmonds于1965年提出,因而得名.匈牙利算法是基于Hall定理中充分性证明的思想,它是部图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法. -------等等,看得头大?那么请看下面的版本: 通过数代人的努力,你终于赶上了剩男剩女的大潮,假设你是一位光荣的新世纪媒人,在你的手上有N个剩男,M个剩女,每个人都可能对多名异性有好感(-_-

【数据结构&amp;&amp;算法系列】KMP算法介绍及实现(c++ &amp;&amp; java)

KMP算法如果理解原理的话,其实很简单. KMP算法简介 这里根据自己的理解简单介绍下. KMP算法的名称由三位发明者(Knuth.Morris.Pratt)的首字母组成,又称字符串查找算法. 个人觉得可以理解为最小回溯算法,即匹配失效的时候,尽量少回溯,从而缩短时间复杂度. KMP算法有两个关键的地方,1)求解next数组,2)利用next数组进行最小回溯. 1)求解next数组 next数组的取值只与模式串有关,next数组用于失配时回溯使用. 在简单版本的KMP算法中,每个位置 j 的 n

底层算法系列:Paxos算法

关于算法,面太广.本系列只研究实际应用中遇到的核心算法.了解这些算法和应用,对java码农进阶是很有必要的. 对于Paxos学习论证过程中,证实一句话:有史以来学习paxos最好的地方wiki:Paxos (computer science) 目录 1.背景 2.Paxos算法 3.Muti-Paxos算法 4.Muti-Paxos在google chubby中的应用 ===============正文分割线============================ 一.背景 Paxos 协议是一

算法系列之常用算法之一----分治算法

一.基本概念 在计算机科学中,分治法是一种很重要的算法.分治算法,字面上的解释是"分而治之",分治算法主要是三点: 1.将一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题----"分" 2.将最后子问题可以简单的直接求解----"治" 3.将所有子问题的解合并起来就是原问题打得解----"合" 这三点是分治算法的主要特点,只要是符合这三个特点的问题都可以使用分治算法进行解决(注意用词,是"

查找算法系列之复杂算法:二叉排序树BST

前面总结了顺序查找,二分查找,分块查找算法,此篇博文将详解介绍二叉排序算法(Binary Sort Tree). 在介绍二叉排序算法之前,首先介绍什么事二叉排序树(BST). 首先从二叉树讲起: 1.二叉树的概念 二叉树是每个结点最多有两个子树的有序树.通常子树的根被称作"左子树"(leftsubtree)和"右子树"(rightsubtree).二叉树常被用作二叉查找树和二叉堆或是二叉排序树.二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有

排序算法系列——八大排序算法对比分析

本系列最后一篇,综合分析下前面介绍的八种排序算法的效率,以及各自的适用情况. 下面先看看八种排序算法的时间复杂度表格: 图中八种排序被分成了两组,一组时间复杂度为O(n^2),另一组相对高效些. 下面先对第一组O(n^2)的四种排序算法进行对比,分别取数组长度为100,1000,10000,100000四个数量级,各个元素在0-10000000之间随机获取.下面看下结果的分析. 排序算法 长度=100 长度=1000 长度=10000 长度=100000 直接插入排序 535 2,198 135

排序算法系列:插入排序算法

概述 直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的.记录数增1的有序表. – <大话数据结构> 版权说明 著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 本文作者:Coding-Naga 发表日期: 2016年3月24日 原文链接:http://blog.csdn.net/lemon_tree12138/article/details/50968422 来源:CSDN 更多内容:分类 &

排序算法系列:快速排序算法

概述 在前面说到了两个关于交换排序的算法:冒泡排序与奇偶排序. 本文就来说说交换排序的最后一拍:快速排序算法.之所以说它是快速的原因,不是因为它比其他的排序算法都要快.而是从实践中证明了快速排序在平均性能上的确是比其他算法要快一些,不然快速一说岂不是在乱说? 本文就其原理.过程及实现几个方面讲解一下快速排序算法. 版权声明 著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:Coding-Naga 发表日期:2016年3月1日 链接:http://blog.csdn.n