散列表的开放寻址法

开放寻址法(open addressing)中,所有元素都存放在槽中,在链表法散列表中,每个槽中保存的是相应链表的指针,为了维护一个链表,链表的每个结点必须有一个额外的域来保存它的前戏和后继结点。开放寻址法不在槽外保存元素,不使用指针,也不必须为了维护一个数据结构使用额外的域,所有可以不用存储指针而节省的空间,使得可以用同样的空间来提供更多的槽,也潜在地减少了冲突,提高了检索速度。

为了使用开放寻址法插入一个元素,需要连续地检查散列表,或称为探查(probe),直到找到一个空槽来放置待插入的关键字为止。

有三种常用技术来计算开放寻址法中的探查序列:线性探查、二将探查和双重探查。

1.线性探查:给定个一个普通的散列函数 h‘:U->{0, 1, ..., m-1},称之为辅助散列函数,线性探查方法采用的散列函数为:

h(k, i) = (h‘(k) + 1) mode n,i = 0, 1, ...,m-1

给定一个一个关键字k,首先探查 T[h‘(k)],即由辅助散列函数所给出的槽位,再探查槽 T[h‘(k) + 1], 依次类推,直至槽 T[m - 1],然后,又绕到 T[0], T[1],...,直到最后探查到槽 T[h‘(k) - 1],在线性探查方法中,初始探查位置决定了整个序列,故只有 m 种不同的探查序列。

2.二次探查:采用如下列式的散列函数:h(k, i) = (h‘(k) + c1i + c2i^2) mod m

其中 h‘ 是一个辅助散列波函数,c1 和 c2 为正的辅助常数,i = 0, 1, ... , m - 1。

象线性探查一样,二次探查的初始探查位置决定了整个序列。

3.双重散列:双重散列是用于开放寻址法的最好方法之一,因为它所产生的排列具有随机选择排列的这么多特性。双重散列采用以下形式的散列函数:h(k, i) = (h1(k) + ih2(k)) mod m

其中 h1 和 h2 均为辅助散列函数。初始探查位置为 T[h1(k)],后续的探查位置是前一位置加上偏移量 h2(k) 模 m。为了能查找整个散列表,值 h2(k) 必须要与表的大小 m 互素。

以下是开放寻址法的一个类的定义的例子:

#ifndef _OPEN_ADDRESSING_HASH_H_
#define _OPEN_ADDRESSING_HASH_H_

/************************************************************************
 算法导论

 开放寻址法散列表,本全程采用双重散列的散列函数,其中 h1(k) = k % m,
 h2(k) = 1 + k % (m - 1)
 ************************************************************************/

#include <stdexcept>

template <class T>
class OpenAddressingHash{
public:
	// 定义一个散列元素类型
	struct Node {
		friend class OpenAddressingHash < T > ;
		// 散列元素键值,key 必须 >= 0,当 key == -1 时,表示槽是空的,
		// 当 key == -2 时表示槽内元素已删除
		int key;
		T value;
	private:
		Node() :key(-1){}
		Node(int k, const T& v) :key(k), value(v){}
	};

	// 插入一个元素
	Node* insert(size_t key, const T& value);

	// 查找一个元素
	Node* search(size_t key);

	// 删除一个散列元素
	void remove(size_t key);

private:
	// 散列表大小
	static const size_t _table_size = 11;
	// 散列表
	Node _table[_table_size];

	// 散列函数
	size_t hash(size_t k, size_t);
	// 辅助散列函数 h1 h2
	inline size_t hash1(size_t k);
	inline size_t hash2(size_t k);
};

template <class T>
typename OpenAddressingHash<T>::Node* OpenAddressingHash<T>::insert(size_t key, const T& value){
	size_t i = 0;
	while (i != _table_size) {
		auto hashCode = hash(key, i);
		auto node = &_table[hashCode];
		// 如果槽中关键字与要插入的关键字相同,则修改元素的值
		if (node->key == key || node->key == -2 || node->key == -1){
			node->key = static_cast<int>(key);
			node->value = value;
			return node;
		}
		++i;
	}
	throw std::overflow_error("hash table overflow");
}

template <class T>
typename OpenAddressingHash<T>::Node* OpenAddressingHash<T>::search(size_t key){
	size_t i = 0;
	while (i != _table_size)
	{
		auto hashCode = hash(key, i++);
		if (_table[hashCode].key == key)
			return &_table[hashCode];
	}
	return nullptr;
}

template <class T>
void OpenAddressingHash<T>::remove(size_t key){
	auto node = search(key);
	if (node)
		// 将 key 设置为 -2,表示当前槽元素已删除
		// 不要将 key 设置为 -1,如果这样可导致之后具有相同散列值的元素不可访问
		node->key = -2;
}

template <class T>
size_t OpenAddressingHash<T>::hash(size_t key, size_t i){
	return (hash1(key) + i * hash2(key)) % _table_size;
}

template <class T>
size_t OpenAddressingHash<T>::hash1(size_t key){
	return key % _table_size;
}

template <class T>
size_t OpenAddressingHash<T>::hash2(size_t key){
	return key % (_table_size - 1) + 1;
}

#endif
时间: 2024-11-10 14:53:45

散列表的开放寻址法的相关文章

开放寻址法

//开放寻址法 //散列函数包括线性探测.二次探测.双重探测 #include<iostream> using namespace std; //除法散列法 int h1(int k,int m) {         return k%m;  } //乘法散列法 int h2(int k,int m,float A) {         float fnum=(float)k;         float re=((fnum*A)-(int)(fnum*A))*m;         retur

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

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++]实现散列表的分离链接法的数据结构

散列表,英文叫做Hash Table,因此也叫哈希表,是一种根据关键字值来确定主存中存储位置的数据结构.通过一个散列函数(关于键值的函数),来确定存储该关键字的位置. 主要的方法有: 1.分离链接法(拉链法) 分离链接法的散列函数为 position = key % n. 即关键字的存储位置为关键字的值对表项的数量取模.若表项大小为13,对于关键值为27的项,存储在1(27 % 13 = 1)的表项处.为了减少冲突,n往往取素数.对于同余的关键字,采用 队列(链表) 的方式连接在一起,新放入的元

散列碰撞的解决方法——线性探测法(开放寻址法的一种)

function HashTable() { this.table = new Array(137);//137——官方比较好的设置数组大小的值 this.betterHash = betterHash; this.showDistro = showDistro; this.put = put; //this.get=get; } function betterHash(data) { var cons = 31;//此参数的设置是为了避免碰撞 var total = 0; for ( var

【算法设计-散列表】散列表的直接定址法与位向量

位向量(bit vector)是一个仅包含0和1的数组.长度为m的位向量所占空间要比包含m个指针的数组少的多.用一个位向量来表示一个包含不同元素的动态集合.字典操作的运行时间为0(1) 代码: #include <stdio.h> #include <stdlib.h> #define INT_BIT 32 typedef struct { unsigned int *table; int size; } BitMap; BitMap * bitmap_create(int max

算法导论11.4开放寻址法(除法哈希函数开放寻址处理冲突)

/* * IA_11.4OpenAddressing.cpp * * Created on: Feb 12, 2015 * Author: sunyj */ #include <stdint.h> #include <string.h> #include <iostream> class Node { public: Node() { } Node(int64_t const k, int64_t const d) : key(k), data(d) { } int64

分离链接法实现散列表

散列表是一种用于查找的数据结构.其基本思想来自于索引,也可以看成是数组的一种扩展.对于一些数据信息,比如说图片文件名,如果我们要查找某张图片,通常将图片名作为关键字进行搜索.这个时候是不可能把图片名直接当成数组下标的,因此可以将图片名关键字通过某个函数映射为某个地址,或地址偏移量.那么每次要查找图片的时候直接输入关键字就能直接计算得出存储地址.其定义 T=h(k) 其中k为关键字,h为映射函数,T为得到的散列表,得到的函数值为地址或地址偏移量. 如果不同关键字通过某函数得到的散列值(地址偏移量)

算法导论-散列表(Hash Table)

目录 引言 直接寻址 散列寻址 散列函数 除法散列 乘法散列 全域散列 完全散列 碰撞处理方法 链表法 开放寻址法 线性探查 二次探查 双重散列 随机散列 再散列问题 完整源码(C++) 参考资料 内容 1.引言 如果想在一个n个元素的列表中,查询元素x是否存在于列表中,首先想到的就是从头到尾遍历一遍列表,逐个进行比较,这种方法效率是Θ(n):当然,如果列表是已经排好序的话,可以采用二分查找算法进行查找,这时效率提升到Θ(logn);  本文中,我们介绍散列表(HashTable),能使查找效率

查找算法总结(二)散列表

时间复杂度上,红黑树在平均情况下插入,查找以及删除上都达到了lgN的时间复杂度. 那么有没有查找效率更高的数据结构呢,答案就是本文接下来要介绍了散列表,也叫哈希表(Hash Table) 什么是哈希表 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值. 哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值.这是对于简单的键的情况,我们将其