【算法导论】简单哈希表的除法实现

哈希表,又名散列表,hashtable。。。云云,看似很高大上,其实不过是直接寻址的延伸而已。直接寻址为何物,看一个数组:a[10],那么取其中一个元素a[1],这就是直接寻址,直接去这个a+1的地址上,就找到了这个数值,时间复杂度为O(1)。而哈希表的目的就是要让查找的时间复杂度尽量往O(1)上靠。

一、哈希表的最简单形式

假如有10000个数,比如0~9999,是可能出现的数字的集合,我们现在要将一段时间内,出现的数字,全部保存起来。如果出现的数字都不重复的情况下,我们可以使用一个长度为10000的数组a[10000]来保存,如果数字987出现了,那么我们直接将其保存在a[987]元素上,以此类推。当我们要查询某个数是否出现的时候,比如1002这个数,是否出现,我们可以直接去找a[1002],看看值是否是1002,为了保险起见,也可以在初始化的时候将数组做-1初始化。这样,查找的时间复杂度就是O(1)。

二、改进的哈希表

上面的做法感觉是很快,但是却有问题,太浪费空间了。还是上面的例子,假如可能出现的集合为0~9999这一万个数,但是我们可以预知,实际会出现的数字只有最多10个。如果按照上面的做法,就需要为查找这10个数,而花费10000个空间?是不是太奢侈了点?

其次,上面的做法,基础在于所有出现的数组,都不重复,如果我们人品太好,总共出现两个888怎么办?我们又想知道,888出现了几次?显然这时候,一个a[888]空间是不够的。

为了解决上面两个问题,分两步来做:

1、压缩存储空间。

如何压缩?这里要用到通常所说的哈希函数,哈希函数的作用,就是将大的集合数据,印射到一个相对较小的,我们能够接受的集合范围内,使得速度和内存空间达到一个平衡。比如这里,0~9999一万个可能出现的数字集合,而最多实际只会出现10个。我们就可以使用a%b(取余)操作来处理,比如这里,我们可以使用a%10,来让所有出现的数据的范围由0~9999,变成0~9这十个数,然后就可以使用一个a[10]的数组,去搞定直接寻址。

2、使用链表解决重复出现数据的问题

像上面说的,出现两个888怎么搞?那么我们在a[888]这个位置上,不放元素,我们将a数组作为一个链表数组,a[888]放链表的位置,这样,出现两个888,每次都从链表的头部插入,这样就能放的下了。

如果查找的时候,时间复杂度就不能是单单的O(1)了。我们考虑最坏的情况,比如n个元素的集合,数组的长度为m,当然(n>m)。这时候,除却哈希函数的取余操作的O(1),还要加上(n/m)的链表长度的查找,这是在所有位置链表的长度都相同的情况。如果链表的情况很极端。。。这就不好了。。

所以根据上面的分析,不难发现,这个哈希函数(散列函数)很关键,最好是能让数据,平均的分布到各个位置的链表上,而不要集中到一个或某几个,因为这样会造成某一个链表的长度很长,那么查询起来,时间复杂度就不理想了。

关于散列函数(哈希函数)的取法,有很多种,这里就不再讨论,下面给出除法(取余法)的散列函数,实现简单的哈希表代码:

#include <iostream>
using namespace std;

/**
 * @作者:Alex/苦咖啡
 * @时间:2015.03.18
 * @博客:http://blog.csdn.net/cyp331203
 */

struct node {
	int key;
	node* next;
	node* pre;
};

struct List {

	node* head;

	List() :
			head(NULL) {
	}

	~List() {
		node* tmp = NULL;
		while (head != NULL) {
			tmp = head->next;
			delete head;
			head = tmp;
		}
	}

	void print() {
		node* tmp = head;
		while (tmp != NULL) {
			cout << tmp->key << ' ';
			tmp = tmp->next;
		}
		cout << endl;
	}

	void insertHead(int key) {
		if (head != NULL) {
			node* n = new node();
			n->key = key;
			n->next = head;
			head->pre = n;
			head = n;
		} else {
			head = new node();
			head->key = 5;
		}
	}

	node* search(int key) {
		node* pre = NULL;
		node* curr = head;
		while (curr != NULL && curr->key != key) {
			pre = curr;
			curr = curr->next;
		}
		if (curr == NULL) {
			return NULL;
		} else if (pre == NULL) {
			return this->head;
		} else {
			return pre->next;
		}
	}

	void deleteNode(node* n) {
		if (n != NULL) {
			n->pre->next = n->next;
			n->next->pre = n->pre;
			delete n;
		}
	}
};

//void deleteList(List* l) {
//	if (l != NULL && (l->head) != NULL) {
//		node* pre = NULL;
//		node* curr = l->head;
//		while (curr != NULL) {
//			pre = curr;
//			delete curr;
//			curr = pre->next;
//		}
//	}
//}

struct HashTable {
	List* arr;

	HashTable() {
		arr = new List[100];
	}

	//析构
	~HashTable() {
		for (int i = 0; i < 100; i++) {
			if (arr + i != NULL) {
//				deleteList(arr + i);
				delete (arr+i);
			}
		}
	}

	void CHAINED_HASH_INSERT(int key) {
		(arr + (key % 100))->insertHead(key);
	}

	node* CHAINED_HASH_SEARCH(int key) {
		return (arr + (key % 100))->search(key);
	}

	void CHAINED_HASH_DELETE(node* n) {
		if (n != NULL) {
			(arr + (n->key % 100))->deleteNode(n);
		}
	}

};

int main() {

	HashTable* ht = new HashTable();
	ht->CHAINED_HASH_INSERT(5);
	node* n = ht->CHAINED_HASH_SEARCH(5);

//	cout << n->key << endl;

	ht->CHAINED_HASH_INSERT(205);

//	n.print();
	ht->arr[5].print();

	return 0;
}
时间: 2024-12-28 10:09:48

【算法导论】简单哈希表的除法实现的相关文章

算法6-4:哈希表现状

战争故事 非常久非常久以前,以前发生过非常多关于哈希函数的战争故事. 那些战争的基本原理就是通过精心构造造成大量的哈希冲突从而占用大量的CPU资源. 被攻击的软件例有下面样例: 带有漏洞的server:攻击者精心构造哈系冲突.仅仅须要56K的网速就能让server死机,从而达到DOS攻击的目的. Perl 5.8.0:攻击者精心构造哈系冲突插入到关联数组中 Linux 2.4.20 内核:攻击者精心构造文件名称,造成大量哈系冲突从而让系统性能骤降. 攻击原理 在Java中的String对象非常e

数据结构与算法07 之哈希表

哈希表也称为散列表,是根据关键字值(key value)而直接进行访问的数据结构.也就是说,它通过把关键字值映射到一个位置来访问记录,以加快查找的速度.这个映射函数称为哈希函数(也称为散列函数),映射过程称为哈希化,存放记录的数组叫做散列表.比如我们可以用下面的方法将关键字映射成数组的下标:arrayIndex = hugeNumber % arraySize. 哈希化之后难免会产生一个问题,那就是对不同的关键字,可能得到同一个散列地址,即同一个数组下标,这种现象称为冲突,那么我们该如何去处理冲

UVA1225DigitCounting(简单哈希表)

Trung is bored with his mathematics homeworks. He takes a piece of chalk and starts writing a sequence of consecutive integers starting with 1 to N (1 < N < 10000). After that, he counts the number of times each digit (0 to 9) appears in the sequenc

算法练习--二分搜索哈希表-JS 实现

1. 以哈希KEY的值建立二叉哈希表 2. 根据传入的哈希值使用二分法搜索 具体实现如下: function binarySearchTable(comp){ this.comp = comp; this.kv = new Array(); } binarySearchTable.prototype.add = function(k,v){ if(this.kv.length == 0 || this.comp(this.kv[0].key,k) >= 0){ this.kv.splice(0,

C语言-简单哈希表(hash table)

腾讯三面的时候,叫我写了个哈希表,当时紧张没写好···结果跪了··· 回来后粪发涂墙,赶紧写了一个! 什么都不说了···先让我到厕所里面哭一会··· %>_<% 果然现场发挥,以及基础扎实才是important的! 用链地址法解决冲突的哈希表(C语言,VS2008编写.测试): 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <math.h> 4 #include <string.h>

算法6-5:哈希表应用之集合

能够通过哈希表实现高效的集合操作. 接口 一个集合对象能够包括了下面接口: public interface Set<Key extends Comparable<Key>> { public void add(Key key); public boolean contains(Key key); public void remove(Key key); public int size(); public Iterator<Key> iterator(); } 黑名单过

算法设计7—哈希表1

简单的哈希表实现 C语言

简单的哈希表实现 简单的哈希表实现 原理 哈希表和节点数据结构的定义 初始化和释放哈希表 哈希散列算法 辅助函数strDup 哈希表的插入和修改 哈希表中查找 哈希表元素的移除 哈希表打印 测试一下 这是一个简单的哈希表的实现,用c语言做的. 原理 先说一下原理. 先是有一个bucket数组,也就是所谓的桶. 哈希表的特点就是数据与其在表中的位置存在相关性,也就是有关系的,通过数据应该可以计算出其位置. 这个哈希表是用于存储一些键值对(key -- value)关系的数据,其key也就是其在表中

哈希表详解

最近在做负荷分担的优化,将数据流均匀分到八条流中,学习点哈希算法 什么是哈希表?     哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表.说白了哈希表的原理其实就是通过空间换取时间的做法..     哈希表的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长