【数据结构与算法】哈希表

哈希表是一种通过哈希函数将数据元素映射到内存存储位置的结构。本文介绍了哈希表的构造方法,如除留余数法、直接定址法和数字分析法,并讨论了哈希冲突的解决策略,包括开放定址法(线性探查、平方探查和伪随机数法)和链表法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 哈希表主要是构造一个映射函数,该函数以数据元素为自变量,函数值即为数据元素在内存中的存储位置。通常把这样的映射函数称为哈希函数h(x)。因此可以说,哈希表是通过哈希函数h(x)来确定数据元素x存放位置h(x)的一种特殊存储结构。

11.1.1哈希表的基本构造方法
           构造哈希表的方法是:设要存储的数据元素个数为   n,设置一个长度为m(m≥n)的连续内存单元(即数组),分别以每个数据元素的关键字Ki(0≤i≤n-1)为自变量,以哈希函数h(Ki)值为该数据元素在数组中的下标值存储该数据元素。
            把构造哈希表时Ki≠Kj(i≠j),但h(Ki)=h(Kj)的现象称作哈希冲突。 

11.1.2  建立哈希表的关键问题
    设计一个好的哈希函数,使得尽量避免哈希冲突,以及哈希冲突发生后,如何解决哈希冲突,就成了建立哈希表的两个关键问题。
     哈希冲突主要与三个因素有关:
   (1)与装填因子a有关。
   (2)与所采用的哈希函数有关。 
   (3)与解决哈希冲突的哈希冲突函数有关。  

11.2 哈希函数构造方法 

设要存放的数据元素有n个,存放数据元素的数组个数为m,哈希函数的设计目标,就是要使通过哈希函数得到的n个数据元素的哈希地址 。
    1 除留余数法
           除留余数法是用数据元素的关键字K除以哈希表长度m所得的余数作为哈希地址的方法。除留余数法的哈希函数h(K)为:
             h(K) = K mod m

2 直接定址法
     直接定址法是以数据元素的关键字K本身或关键字加上某个数值常量C作为哈希函数的方法。直接定址法的哈希函数h(K)为:
                          h(K) = K + C
3 数字分析法
    数字分析法是取数据元素关键字中某些取值较均匀的数字位构造哈希函数的方法。它只适合于所有关键字值已知的情况。 


11.3 哈希冲突解决方法

解决哈希冲突的方法主要有开放定址法和链表法两大类。
11.3.1 开放定址法
         开放定址法是一类以发生哈希冲突的哈希地址为自变量、通过某种哈希冲突函数得到一个新的空闲的哈希地址的方法。开放定址法的哈希冲突函数通常是一组。

1 线性探查法
           线性探查法是从发生哈希冲突的地址d开始,依次探查d的下一个地址,直到找到一个空闲单元为止。线性探查法的数学递推描述公式为:


2 平方探查法
          设发生哈希冲突的地址为d,则平方探查法的探查序列为:d+2, d+21, d+22, …。平方探查法的数学递推描述公式为:


3 伪随机数法
    设发生哈希冲突的地址为d,则伪随机数法的探查序列为d+Ri。Ri为一伪随机数序列的第i个数值。伪随机数法的数学递推描述公式为:


11.3.2 链表法
      链表法解决哈希冲突的基本思想是:如果没有发生哈希冲突,则直接在该地址保存该数据元素;如果发生了哈希冲突,则把发生哈希冲突的数据元素保存在另外的单链表中。
     用链表法解决哈希冲突通常有两种方法:
      第一种方法是为发生哈希冲突的不同的同义词建立不同的单链表,
      第二种方法是为所有发生哈希冲突的数据元素建立一个单链表。

用链表法解决冲突的哈希表 示例:


如下哈希表类的哈希函数采用除留余数法,   哈希冲突函数采用开放定址法中的线性探查法。

package cn.ls.hash;
/**
 * 
 *数据条目
 */
public class HashItem {
	int data=0;
	int info=0;//标志:0表示空闲状态,1表示占用状态.

	HashItem(int i) {
		info = i;
	}

	HashItem(int d, int i) {
		data = d;
		info = i;
	}
}

package cn.ls.hash;
/**
 * 
 *哈希表类
 */
public class HashTable {
	private HashItem[] ht; // 哈希表数组
	private int tableSize; // 哈希表的长度
	private int currentSize; // 当前的表项个数

	HashTable(int m) { // 构造函数
		tableSize = m;
		ht = new HashItem[tableSize];
		currentSize = 0;
	}

	public boolean isIn(int x) { // x是否已存在
		int i = find(x);
		if (i >= 0)
			return true;
		else
			return false;
	}

	public int getValue(int i) { // 取数据元素值
		return ht[i].data;
	}

	public int find(int x) { // 查找
		int i = x % tableSize;
		int j = i;

		if (ht[j] == null)
			ht[j] = new HashItem(0);//设置info标记为0
		//注意要加上非空判断.
		while (ht[j] != null && ht[j].info == 1 && ht[j].data != x) { // 说明存在冲突
			j = (j + 1) % tableSize; // 得到下一个哈希地址
			if (j == i)
				return -tableSize;
			// 若j == i表示已查找完了整个哈希表的数组,返回- tableSize
		}
		if (ht[j] != null && ht[j].info == 1) //此条件成立表示查找到
			return j; // 返回该数据元素的下标
		else
			//此时表示没有查找到  返回该数据元素哈希地址的负值
			return -j; 
	}

	public void insert(int x) throws Exception { // 插入
		int i = find(x); // 查找x 是否已存在,并返回数组下标

		if (i > 0) { // 如果x存在
			throw new Exception("该数据已存在");
		}

		else if (i != -tableSize) { // 如果x不存在
			ht[-i] = new HashItem(x, 1); //插入数据元素x,并设置info标记为1
			currentSize++; // 当前元素个数加1
		} else { // 如果i等于-tableSize,表示哈希表已满
			throw new Exception("哈希表已满无法插入");
		}
	}

	public void delete(int x) throws Exception { // 删除
		int i = find(x); // 查找x 是否已存在,并返回数组下标

		if (i >= 0) { // 如果x存在
			ht[i].info = 0; // 置为空闲状态
			currentSize--; // 当前元素个数减1
		} else { // 如果x不存在
			throw new Exception("该数据不存在");
		}
	}
}

package cn.ls.hash;
/**
 * 
 *测试类。
 */
public class Exam11_3 {
	public static void main(String[] args) {
		//7个数据元素,哈希表数组个数取11进行测试。
		HashTable myHashTable = new HashTable(11);
		int[] a = { 180, 750, 600, 430, 541, 900, 460 };
		int i, j, n = 7, item;

		try {
			for (i = 0; i < n; i++)
				myHashTable.insert(a[i]);

			for (i = 0; i < n; i++) {
				j = myHashTable.find(a[i]);
				if (j > 0) {
					item = myHashTable.getValue(j);
					System.out.println("j = " + j + "  ht[] = " + item);
				}
			}

			if (myHashTable.isIn(430))
				System.out.println("数据元素430在哈希表中");
			else
				System.out.println("数据元素430不在哈希表中");

			myHashTable.delete(430);
			if (myHashTable.isIn(430))
				System.out.println("数据元素430在哈希表中");
			else
				System.out.println("数据元素430不在哈希表中");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}
}

package cn.ls.hash;
/**
 * 
 *测试类。
 */
public class Exam11_3 {
	public static void main(String[] args) {
		//7个数据元素,哈希表数组个数取11进行测试。
		//根据前面的装填因子 = n/m的定义和其取值最好在0.6-0.9间的实践经验,可得出m最好取1.1n-1.7n之间的一个素数。
		//例如当n=7时m最好取11,13等素数。n=100时m最好取113,127,139,143等素数。
		HashTable myHashTable = new HashTable(11);
		int[] a = { 180, 750, 600, 430, 541, 900, 460 };
		int i, j, n = 7, item;

		try {
			for (i = 0; i < n; i++)
				myHashTable.insert(a[i]);

			for (i = 0; i < n; i++) {
				j = myHashTable.find(a[i]);
				if (j > 0) {
					item = myHashTable.getValue(j);
					System.out.println("j = " + j + "  ht[] = " + item);
				}
			}

			if (myHashTable.isIn(430))
				System.out.println("数据元素430在哈希表中");
			else
				System.out.println("数据元素430不在哈希表中");

			myHashTable.delete(430);
			if (myHashTable.isIn(430))
				System.out.println("数据元素430在哈希表中");
			else
				System.out.println("数据元素430不在哈希表中");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}
}

测试结果:

j = 4  ht[] = 180
j = 2  ht[] = 750
j = 6  ht[] = 600
j = 1  ht[] = 430
j = 3  ht[] = 541
j = 9  ht[] = 900
j = 10  ht[] = 460
数据元素430在哈希表中
数据元素430不在哈希表中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值