基于散列表(线性探查的哈希表和链式散列)的字典实现

本文介绍了基于散列表的字典实现,包括线性探查的哈希表和链式散列。讨论了散列函数、冲突和溢出的处理,以及具体的C++实现,如哈希表满异常和有序链表节点的管理。
摘要由CSDN通过智能技术生成

字典的另一种表示是散列。它用一个散列函数(也称哈希函数)把字典的数对映射到一个散列表(也称哈希表)的具体位置。与跳表相比,它把操作的时间提高到渐近等于1,但最坏情况下依然是渐近等于N。但是如果经常输出所有元素或按照顺序查找元素(如查找第10个最小元素之类),跳表的执行效率将优于散列。相较于线性表,一个良好的散列函数得到的散列表,其查找、插入、删除的平均时间复杂度渐近等于1,即使是最坏情况下(所有元素都哈希到同一个桶),它的性能也和线性表相同。
基于跳表的字典实现

散列函数和散列表
1.桶和起始桶

当关键字范围太大,不能用理想方法表示时,可以采用并不理想的散列表和散列函数:散列表位置的数量比关键字的个数少,散列函数把若干个不同的关键字映射到散列表的同一个位置,也叫哈希冲突,将带来哈希溢出。散列表的每个位置叫作一个桶(bucket);对关键字k的数对,f(k)是起始桶;桶的数量等于散列表的长度或大小。本文考虑两种极端情况:每个桶只能存储一个数对和每个桶都是可以容纳全部数对的线性表(哈希链表)。

2.除法散列函数

在多种散列函数中,最常用的是散列函数:f(k)=k%D 其中k是关键字,D是散列的长度(桶的数量)。

3.冲突和溢出

当两个不同的关键字通过散列函数后,对应的起始桶相同时,就发生了冲突。当该桶没有空间多存储一个元素时,将带来溢出。当映射到散列表中任何一个桶里的关键字数量大致相等时,冲突和溢出的平均数最少。均匀散列函数就是这样的函数。我们需要选择一个良好的散列函数,那么就需要选择一个好的散列函数的除数D。这也就是为什么一般哈希桶数(散列函数除数)一般都是良好的素数,如果是合数,当我们的数据满足一定特点时(如偶数居多、元素多为2的幂等待情况),这时的D不是一个好的散列函数除数,得到的也是一个不良的散列函数。

4.解决冲突和溢出

1.线性探查
最简单的方法就是找到下一个可用的桶,本文第一种情况就选择这种方法。因此,这种方法的搜索过程如下:首先搜索起始桶f(k),然后把散列表当作环表继续搜索下一个桶,直到以下情况之一发生:1.存在关键字k的桶已找到;2.到达要给空桶;3.又回到起始桶。后两种情况说明关键字k不存在。因此这种情况下,删除操作除了需要删除指定的元素,还要从该元素的下一个桶开始,逐个检查每个桶,以确定要移动的元素,直到到达一个空桶或回到删除位置为止,然后依次将这些桶的元素向前移动。
2.使用链式哈希表
如果散列表的每一个桶都可以容纳无限多个记录,那么溢出问题就不存在了。实现这个目标的一个方法就是给散列表的每一个位置配置一个线性表。本文的第二种情况就是使用这种方法,为每个桶配置一个有序链表。
3.其他解决方法
如再哈希、设置公共溢出区等方法。

具体的C++实现
字典的ADT描述
#pragma once
//字典结构的抽象描述
template <typename K,typename V>
class dictionary {
   
public:
	virtual ~dictionary(){
    }
	virtual bool empty() const = 0;
	//返回字典中数对的数目
	virtual size_t size() const = 0;
	//返回匹配数对的指针
	virtual std::pair<const K, V>* find(const K& _key) const = 0;
	virtual void erase(const K& _key) = 0;
	virtual void insert(const std::pair<const K, V>& _pair) = 0;
};
1.线性探查的哈希表

哈希表满异常

#pragma once
#include <string>
using std::string;
#include <iostream>
using std::cin; using std::cout; using std::ends; using std::endl;
class fullHashTable
{
   
public:
	fullHashTable(const std::string& _msg="The hash table is full!"):msg(_msg){
    }
	std::string what()const {
    return msg; }
	void output()const {
    cout << msg << endl; }
private:
	std::string msg;
};

线性探查的哈希表实现

#pragma once
#ifndef hashTable_H
#define hashTable_H

#include "dictionaryADT.h"
#include "fullHashTable.h"

//使用先行探查来解决移出 每个桶只装一个元素
//散列表的平均性能远优于线性表(如查找、插入、删除) 即使在最坏情况(也就是所有元素hash到所有同一个起始桶)也和线性表相同
template <typename K, typename V>
class hashTable {
   
public:
	hashTable(int _divisor=11);
	~hashTable();

	bool empty()const {
    return dict_size == 0; }
	size_t size()const {
    return dict_size; }
	std::pair<const K, V>* find(const K& _Key)const;
	void insert(const std::pair<const K, V>& _pair);
	void erase(const K& _Key);
	void output(std::ostream& os)const;
private:
	std::pair<const K, V>** table;	//保存指向堆中pair对象的数组
	size_t dict_size;				//字典大小
	int divisor;					//散列函数除数

	int search(const K& _Key)const;	//查找给定的key所在的位置
};


template <typename K, typename V>
hashTable<K, V>::hashTable(int _divisor){
   
	divisor = _divisor;
	dict_size = 0;
	//分配并初始化table
	table = new std::pair<const K, V>* [divisor]();
}


template <typename K
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
/* * 基于散列表实现的(无序)词典结构 * 采用分离链策略解决冲突 */ package dsa; public class Dictionary_HashTable implements Dictionary { private Dictionary[] A;//桶数组,每个桶本身也是一个(基于实现的)词典结构 private int N;//散列表长 private final double maxLemda = 0.75;//装填因子上限 private int size;//词典结构的规模 private EqualityTester T;//判等器 //默认构造方法 public Dictionary_HashTable() { this(0, new EqualityTesterDefault()); } //构造方法 public Dictionary_HashTable(int n, EqualityTester t) { T = t; N = p(n);//桶数组容量取为不小于n的最小素数 A = new Dictionary[N]; for (int i=0; i<N; i++) A[i] = new Dictionary_DLNode(T); size = 0; } /***************************** 辅助方法 *****************************/ //定址函数(采用模余法) private int h(Object key) { return key.hashCode() % N; } //判断n是否为素数 private static boolean prime(int n) { for (int i=3; i<1+Math.sqrt(n); i++) if (n/i*i == n) return false; return true; } //取不小于n的最小素数 private static int p(int n) { if (3>n) n = 3; n = n | 1;//奇数化 while (!prime(n)) n += 2; return n; } /***************************** ADT方法 *****************************/ //查询词典结构当前的规模 public int getSize() { return size; } //判断词典结构是否为空 public boolean isEmpty() { return 0==size; } //若词典中存在以key为关键码的条目,则返回其中的一个条目;否则,返回null public Entry find(Object key) { return A[h(key)].find(key); } //返回由关键码为key的条目组成的迭代器 public Iterator findAll(Object key) { return A[h(key)].findAll(key); } //插入条目(key, value),并返回该条目 public Entry insert(Object key, Object value) { Entry entry = A[h(key)].insert(key, value);//将新条目插至桶A[h(key)]对应的子词典 size ++;//更新规模记录 if (size > N * maxLemda) rehash();//若装填因子过大,则重 return entry;//返回null标志 } //若词典中存在以key为关键码的条目,则将其摘除并返回;否则,返回null public Entry remove(Object key) { Entry oldEntry = A[h(key)].remove(key); if (null!=oldEntry) size--; return oldEntry; } //返回词典中所有条目的一个迭代器 public Iterator entries() { List L = new List_DLNode(); for (int i=0; i<N; i++) { Iterator it = A[i].entries(); while (it.hasNext()) L.insertLast(it.getNext()); } return new IteratorElement(L); } //重 private void rehash() { Iterator it = this.entries(); N = p(N<<1); A = new Dictionary[N];//桶数组容量至少加倍 for (int i=0; i<N; i++) A[i] = new Dictionary_DLNode(T);//为每个桶分配一个子词典 while (it.hasNext()) {//将其对应的词典结构中的 Entry e = (Entry)it.getNext();//各条目逐一取出,将其 Object k = e.getKey();//关键码和 Object v = e.getValue();//数据对象 A[h(k)].insert(k, v);//整合为新的条目,插入对应的子词典中 } } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值