数据结构(Java语言)——HashTable(开放定址法)简单实现

分离链接散列算法的缺点是使用一些链表。由于给新单元分配地址需要时间,因此这就导致算法的速度有些减慢,同时算法实际上还要求对第二种数据结构的实现。另有一种不用链表解决冲突的方法是尝试另外一些单元,直到找出空的单元为止。更常见的是,单元h0(x),h1(x),h2(x),...相继被试选,其中hi(x)=(hash(x)+f(i)) mod TableSize,且f(0)=0。函数f是冲突解决方法,因为所有的数据都要置于表内,所以这种解决方案所需要的表要比分离链接散列的表大。一般来说,对于不使用分离链接的散列表来说,其装填因子λ应该低于
0.5。我们把这样的表叫做探测散列表(probing hash table)。现在我们就来考察三中通常的冲突解决方案。

  • 线性探测法

在线性探测法中,函数f是i的线性函数,典型情况是f(i)=i。这相当于相继探测逐个单元(必要时可回绕)以查找出一个空单元。只要表足够大,总能够找到一个自由单元,但是如此花费的时间是相当多的。而且即使表相对较空,这样占据的单元也会开始形成一些区块,其结果次称为一次聚集,就是说,散列到区块中的任何关键字都需要多次试选单元才能够解决冲突,然后该关键字被添加到相应的区块中。

可以证明,使用线性探测的预期探测次数对于插入和不成功的查找来说大约为0.5(1+1/(1-λ)2),而对于成功的查找来说则是0.5(1+1/(1-λ))。从程序中容易看出,插入和不成功查找需要相同次数的探测,成功查找应该比不成功查找平均花费较少的时间。

  • 平方探测法

平方探测是消除线性探测中一次聚集问题的冲突解决方法。平方探测就是冲突函数为二次的探测方法。流行的选择是f(i)=i2。对于线性探测,让散列表几乎填满元素并不是个好主意,因为此时表的性能会降低。对于平方探测情况甚至更糟:一旦表被填充超过一半,当表的大小不是素数时甚至在表被填充一半之前,就不能保证一次找到空的单元了。这是因为最多有表的一半可以用作解决冲突的备选位置。

可以证明,即使表被填充的位置仅仅比一半多一个,那么插入都有可能失败。另外,表的大小是素数也很重要,如果表的大小不是素数,那么备选单元的个数可能会锐减。

在探测散列表中标准的删除操作不能自行,因为相应的单元可能已经引起过冲突,元素绕过它存在了别处。因此,探测散列表需要懒惰删除。

虽然平方散列排除了一次聚集,但是散列到同一位置上的那些元素将探测相同的备选单元,这叫做二次聚集。二次聚集是理论上的一个小缺憾,模拟结果指出,对每次查找,它一般要引起另外的少于一半的探测。下面的技术将会排除这个缺憾,不过这要付出计算一个附加的散列函数的代价。

  • 双散列

对于双散列,一种流行的选择是f(i)=i*hash?(x)。这个公式是说,我们将第二个散列函数应用到x并在距离hash?(x),2hash?(x),...等处探测。hash?(x)选择得不好将会是灾难性的。函数一定不要算得0值,另外保证所有的单元都能被探测到也是很重要的。诸如hash?(x)=R-(x mod R)这样的函数将起到良好的作用,其中R为小于TableSize的素数。

在使用双散列时保证表的大小为素数时很重要的,如果表的大小不是素数,那么备选单元就有可能提前用完。然而,如果双散列正确实现,则模拟表明,预期的探测次数几乎和随机冲突解决方法的情况相同,这使得双散列理论上很有吸引力。不过,平方探测不需要使用第二个散列函数,从而在实践中使用可能更简单并且更快,特别对于像串这样的关键字,他们的散列函数计算起来相当耗时。

以下是一个平方探测法的散列表实现:

import java.util.Random;

public class QuadraticProbingHashTable<AnyType> {
	private static final int DEFAULT_TBALE_SIZE = 11;
	private HashEntry<AnyType>[] array;
	private int currentSize;

	/*
	 * 三种情况:null,非null且该项为活动的,非null且该项被标记删除
	 */
	private static class HashEntry<AnyType> {
		public AnyType element;
		public boolean isActive;

		public HashEntry(AnyType e) {
			this(e, true);
		}

		public HashEntry(AnyType e, boolean i) {
			element = e;
			isActive = i;
		}
	}

	public QuadraticProbingHashTable() {
		this(DEFAULT_TBALE_SIZE);
	}

	public QuadraticProbingHashTable(int size) {
		array = new HashEntry[size];
		makeEmpty();
	}

	public void makeEmpty() {
		for (int i = 0; i < array.length; i++) {
			array[i] = null;
		}
		currentSize = 0;
	}

	/**
	 * 哈希表是否包含某元素
	 *
	 * @param x
	 *            查询元素
	 * @return 查询结果
	 */
	public boolean contains(AnyType x) {
		int currentPos = findPos(x);
		return isActive(currentPos);
	}

	/**
	 * 向哈希表中插入某元素,若存在则不操作
	 *
	 * @param x
	 *            插入元素
	 */
	public void insert(AnyType x) {
		int currentPos = findPos(x);
		System.out.println(x + " hash-> " + currentPos);
		if (isActive(currentPos)) {
			return;
		}
		array[currentPos] = new HashEntry<AnyType>(x, true);
		if (++currentSize > array.length / 2) {
			rehash();
		}
	}

	/**
	 * 向哈希表中懒惰删除某元素
	 *
	 * @param x
	 *            删除元素
	 */
	public void remove(AnyType x) {
		int currentPos = findPos(x);
		if (isActive(currentPos)) {
			array[currentPos].isActive = false;
		}
	}

	/**
	 * 通过哈希算法找到某元素下标(平方探测法):f(i)=f(i-1)+2i-1
	 *
	 * @param x
	 *            查找元素
	 * @return 该元素在数组中下标
	 */
	private int findPos(AnyType x) {
		int offset = 1;
		int currentPos = myhash(x);
		while (array[currentPos] != null
				&& !array[currentPos].element.equals(x)) {
			currentPos += offset;
			offset += 2;
			if (currentPos >= array.length) {
				currentPos -= array.length;
			}
		}
		return currentPos;
	}

	/**
	 * 检查指定下标是否存在活动项
	 *
	 * @param currentPos
	 *            下标
	 * @return 是否有活动项
	 */
	private boolean isActive(int currentPos) {
		return array[currentPos] != null && array[currentPos].isActive;
	}

	/**
	 * 简单的哈希算法
	 *
	 * @param x
	 *            元素
	 * @return 哈希值
	 */
	private int myhash(AnyType x) {
		int hashVal = x.hashCode();
		hashVal %= array.length;
		if (hashVal < 0) {
			hashVal += array.length;
		}
		return hashVal;
	}

	/**
	 * 再散列函数,插入空间过半时执行
	 */
	@SuppressWarnings("unchecked")
	private void rehash() {
		HashEntry<AnyType>[] oldArray = array;
		// 创建一个容量翻倍的空表
		array = new HashEntry[nextPrime(2 * array.length)];
		currentSize = 0;
		// 将数据复制到新表
		for (int i = 0; i < oldArray.length; i++) {
			if (oldArray[i] != null && oldArray[i].isActive) {
				insert(oldArray[i].element);
			}
		}
		System.out.println("\nrehash by length of: " + array.length);
	}

	/**
	 * 检查某整数是否为素数
	 *
	 * @param num
	 *            检查整数
	 * @return 检查结果
	 */
	private static boolean isPrime(int num) {
		if (num == 2 || num == 3) {
			return true;
		}
		if (num == 1 || num % 2 == 0) {
			return false;
		}
		for (int i = 3; i * i <= num; i += 2) {
			if (num % i == 0) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 返回不小于某个整数的素数
	 *
	 * @param num
	 *            整数
	 * @return 下一个素数(可以相等)
	 */
	private static int nextPrime(int num) {
		if (num == 0 || num == 1 || num == 2) {
			return 2;
		}
		if (num % 2 == 0) {
			num++;
		}
		while (!isPrime(num)) {
			num += 2;
		}
		return num;
	}

	public void printTable() {
		for (int i = 0; i < array.length; i++) {
			System.out.println("-----");
			if (array[i] != null) {
				System.out.println(array[i].element + " ");
			} else {
				System.out.println();
			}
		}
	}

	public static void main(String[] args) {
		QuadraticProbingHashTable<Integer> hashTable = new QuadraticProbingHashTable<Integer>();
		Random random = new Random();
		for (int i = 0; i < 20; i++) {
			hashTable.insert(random.nextInt(60));
		}
		hashTable.printTable();
	}
}

执行结果:

22 hash-> 0

15 hash-> 4

45 hash-> 1

33 hash-> 9

45 hash-> 1

17 hash-> 6

32 hash-> 10

22 hash-> 22

45 hash-> 0

15 hash-> 15

17 hash-> 17

33 hash-> 10

32 hash-> 9

rehash by length of: 23

19 hash-> 19

3 hash-> 3

9 hash-> 13

17 hash-> 17

12 hash-> 12

48 hash-> 2

45 hash-> 0

58 hash-> 16

45 hash-> 45

48 hash-> 1

3 hash-> 3

32 hash-> 32

33 hash-> 33

12 hash-> 12

9 hash-> 9

15 hash-> 15

58 hash-> 11

17 hash-> 17

19 hash-> 19

22 hash-> 22

rehash by length of: 47

3 hash-> 3

44 hash-> 44

27 hash-> 27

57 hash-> 10

44 hash-> 44

-----

-----

48

-----

-----

3

-----

-----

-----

-----

-----

-----

9

-----

57

-----

58

-----

12

-----

-----

-----

15

-----

-----

17

-----

-----

19

-----

-----

-----

22

-----

-----

-----

-----

-----

27

-----

-----

-----

-----

-----

32

-----

33

-----

-----

-----

-----

-----

-----

-----

-----

-----

-----

-----

44

-----

45

-----

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-14 03:41:31

数据结构(Java语言)——HashTable(开放定址法)简单实现的相关文章

散列表的C语言实现-开放定址法

头文件: #ifndef __HASHTABLE_H #define __HASHTABLE_H /*********************(平方)开放定址散列法***************/ //如果有冲突发生,那么就尝试另外的单元,直到找到空的单元为止 typedef unsigned int index; typedef index position; typedef int ElementType; #define MINTABLESIZE 5 struct hashTable; t

数据结构--开放定址法解决散列冲突时几种探测法的比较

开放定址法解决散列冲突时主要有线性探测法,平方探测法和双散列法,以下代码通过插入大量随机数,来统计几种探测法产生冲突的次数. #include<iostream> using namespace std; #define MinTablesize 10 #define Num 3000 typedef unsigned int Index; typedef Index Position; struct Hashtal; typedef struct Hashtal *Hashtable; in

数据结构之散列(开放定址法)

1 // OHash 2 // 关键字:int 3 // Hash函数:hash(X) = X mod TableSize 4 // 冲突解决方法:开放定址法.Index(X, i) = (hash(X) + f(i)) mod TableSize, f(0) = 0 5 // 其中f(i)为: 6 // 1. 线性, f(i) = i 7 // 2. 平方, f(i) = i ^ 2 8 // 3. 双散列, f(i) = i * hash2(X), hash2(X, R) = R - (X

C# Dictionary源码剖析---哈希处理冲突的方法有:开放定址法、再哈希法、链地址法、建立一个公共溢出区等

参考:https://blog.csdn.net/exiaojiu/article/details/51252515 http://www.cnblogs.com/wangjun1234/p/3719635.html 源代码版本为 .NET Framework 4.6.1 Dictionary是Hashtable的一种泛型实现(也是一种哈希表)实现了IDictionary泛型接口和非泛型接口等,将键映射到相应的值.任何非 null 对象都可以用作键.使用与Hashtable不同的冲突解决方法,D

哈希表(开放定址法处理冲突)(1013)

Description 采用除留余数法(H(key)=key %n)建立长度为n的哈希表,处理冲突用开放定址法的线性探测. Input 第一行为哈希表的长度n: 第二行为关键字的个数: 第三行为关键字集合: 第三行为要查找的数据. Output 如果查找成功,输出关键字所哈希表中的地址和比较次数:如果查找不成功,输出-1. 如果查找成功,输出关键字所哈希表中的地址和比较次数:如果查找不成功,输出-1. Sample Input   1 2 3 4 5 13 11 16 74 60 43 54 9

java语言及其垃圾回收机制简单概述

 一.java 语言概述 Java 语言是一门纯粹的面向对象编程语言,它吸收了c++语言的各种优点.又摈弃了c++里难以理解的多继承,指针等概念因此Java语言具有功能强大和简单易用两个特征. Java语言的几个重要概念如下: J2ME:主要用于控制移动设备和信息家电等有限存储设备 J2SE:整个java技术的核心和基础, J2EE:java技术中应用最最广泛的部分,它提供了企业应用开发相关的完整的解决方案. API: 核心类库 JRE:运行Java程序所必须的环境的集合,包含JVM标准实现及J

算法学习 - HashTable开放地址法解决哈希冲突

开放地址法解决哈希冲突 线性开放地址法 线性开放地址法就是在hash之后,当发现在位置上已经存在了一个变量之后,放到它下一个位置,假如下一个位置也冲突,则继续向下,依次类推,直到找到没有变量的位置,放进去. 平方开放地址法 平方地址法就是在hash之后,当正确位置上存在冲突,不放到挨着的下一个位置,而是放到第2^0位置,假如继续冲突放到2^1的位置,依次2^3... 直到遇到不冲突的位置放进去. 双散列开放地址法 双散列同上,不过不是放到2^的位置,而是放到key - hash(key, tab

java语言实现创建型模式——简单工厂模式

一.描述 简单工厂模式又名静态工厂方法模式,是所有工厂模式中最简单的一个,它定义一个具体的工厂类来负责创建所有类的对象并初始化创建的对象,它主要由3部分组成:工厂类.抽象类.实现抽象类的具体类,然后通过客户端调用工厂类创建相应的对象. 注意:简单工厂模式不属于GoF总结的23种设计模式,它是相当于初学java的HelloWorld案例,帮助初学者了解设计模式,增长信心的一个设计模式. 二.简单工厂模式的优缺点 优点:相对于直接在客户端创建对象并初始化对象的值来说,简单工厂模式将创建对象和初始化任

数据结构(java语言描述)顺序栈的使用(两个大数相加)

利用http://www.cnblogs.com/xleer/p/5289661.html中对顺序栈以及栈的进本方法的定义,实现超过整数上限的两个数的加法计算. 算法: package stack;/********************************************************************** * @author sch ********利用栈,计算两个大数的和.大数的值超过int存储的范围******************************