看数据结构写代码(61) 哈希表

前面说的 各种查找都是 基于 “比较” 的基础 来进行 查找的。查找的 效率 要 看 比较的 次数。那么 有没有 不需要 比较,就可以 找到 想要的数据的 方法呢?

哈希表 就是 这样的 一种方法,它用  数组 作为 保存 关键字的 数据原型,通过 一个 哈希 函数f(k),来找到 关键字 存储的位置,从而 找到想要的信息。

例如 我们 想要解决 这样的一个问题:

假设这有一个各种字母组成的字符串,假设这还有另外一个字符串,而且这个字符串里的字母数相对少一些。什么方法能最快的查出所有小字符串里的字母在大字符串里都有?

比如,如果是下面两个字符串:

String
1: ABCDEFGHLMNOPQRS

String
2: DCGSRQPOM

我们
可以用 一个 分配 一个 26 个 int 型的 整形数组 a,将 0~25 分别 代表 A~Z 是否 出现,如果 出现则 值为 1,没有出现
值为0.


我们 只需 遍历 String1,然后 将 对应的 元素 设置 为1,然后 遍历 String2 ,如果 查找 过程中 ,遇到了 0 值, 则 不是
。否则 String2
的 字母 在 String1 中 都 存在。

哈希表 虽然 快速,但是 其 数据 原型 基于 数组,同样 有缺陷。

当 查找的 元素 集合 太大,不同的 关键字,却 得到 同样的 地址。即 k1 != k2,, F(K1) = = F(K2),这时 叫做 冲突。冲突 是无法避免的。只能 通过一些方法 减少 冲突。当 我们 插入 元素时,寻找 插入位置,造成的 冲突次数 太多,影响查找效率,我们 只能 重新 建表,这是个 费时的过程。

而且 哈希 是 无法 按 从小到 大 遍历 数据的。

所以 我们在 用哈希的时候得考虑这些:

1.哈希函数

2冲突函数

3初始表长 

4冲突多少次,我们就重新建表,

5.是否需要 顺序遍历。

下面代码 用的是

哈希函数:除整取余法

冲突函数:开发定址法(线性)

冲突次数 到达 表长的一半 就重新建表。

哈希表基本结构 ,初始化 和销毁

#include "stdafx.h"
#include <cstdlib>
int hashSize[] = {11,13,17,19};//哈希表容量增加 数组.

#define NULL_KEY	0
struct HashTable{
	int * base;//数据的基址
	int count;//表的数量
	int sizeIndex;//表的容量大小的索引
};

void initHash(HashTable * t){
	t->sizeIndex =0;
	t->base = (int *)calloc(hashSize[t->sizeIndex],sizeof(int));
	t->count = 0;
}

void destoryHash(HashTable * t){
	free(t->base);
	t->base = NULL;
	t->sizeIndex = 0;
	t->count = 0;
}

哈希函数:

//除留余数法
int hash(HashTable t,int key){
	return key % hashSize[t.sizeIndex];
}

冲突函数:

//开发定址 线性探索解决冲突法
int collision(HashTable t,int key,int times){
	return (key + times) % hashSize[t.sizeIndex];
}

查找函数:

int search(HashTable t,int key,int * index,int *ctimes){
	*index = hash(t,key);
	*ctimes = 0;
	while (t.base[*index] != NULL_KEY && t.base[*index] != key){
		(*ctimes)++;
		*index = collision(t,key,*ctimes);
	}
	printf("------------查找%d, 查找了%d次--------------\n",key,*ctimes+1);
	if (t.base[*index] == key){
		return t.base[*index];
	}
	else{
		return NULL_KEY;
	}
}

插入关键字,以及 重建表函数:

void reCreateHashTable(HashTable * t,int key);
void insertHash(HashTable *t,int key){
	int index;//插入位置
	int	ctimes;//冲突次数
	int result = search(*t,key,&index,&ctimes);
	if (result == NULL_KEY && ctimes < hashSize[t->sizeIndex]/2){//没找到
		t->base[index] = key;
		t->count ++;
	}
	else{//重新建表
		reCreateHashTable(t,key);
	}
}

//
void reCreateHashTable(HashTable * t,int key){
	printf("--------------重建哈希表----------------\n");
	int * oldBase = t->base;//保存老空间.
	int oldSize = hashSize[t->sizeIndex];//老空间的容量大小
	t->sizeIndex++;
	int newSize = hashSize[t->sizeIndex];//新空间大小
	t->base = (int *) calloc(newSize,sizeof(int));//新空间
	//插入之前将 表的数量置0
	t->count = 0;
	for (int i = 0; i < oldSize; i++){
		if (oldBase[i] != NULL_KEY){
			insertHash(t,oldBase[i]);
		}
	}
	free(oldBase);//释放老空间
	insertHash(t,key);//插入冲突的关键字.
}

测试函数:

static int testArray[10] = {1,18,7,55,23,45,98,76,35,29};

int _tmain(int argc, _TCHAR* argv[])
{
	HashTable table;
	initHash(&table);
	for (int i = 0; i < 10; i++){
		insertHash(&table,testArray[i]);
	}
	int index ,count;
	for (int i = 0; i < 10; i++){
		search(table,testArray[i],&index,&count);
	}
	destoryHash(&table);
	return 0;
}

代码工程文件网盘地址:http://pan.baidu.com/s/1kToXLcj

时间: 2024-08-28 07:01:15

看数据结构写代码(61) 哈希表的相关文章

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(36) 图的邻接表表示与实现

图的邻接表表示法,是为每一个顶点建立一个链表,链表里存放着相同弧尾的 弧的信息,这些链表顺序存放在数组中.下面是无向图g2的邻接表 邻接表 比 邻接矩阵 节省空间,同时 也带来一些操作上的 不便,例如 看 两个顶点是否 相邻,需要 遍历 链表,在 求 无向图顶点的度时,只需 遍历 顶点的链表,而 求 有向图 顶点的度 需要 遍历 整个图 查找 弧头 为这个顶点的 个数. 如果 不想这样做,可以 建立 逆邻接表,即 链表里 存放着 相同 弧头的 弧 的信息. 下一节 要说的 十字链表 类似于这种结

看数据结构写代码(52) 广义表的扩展线性链表存储表示

广义表 的另一种 存储结构是 扩展线性链表存储表示,这种 存储结构的 根 节点 必 存在,并且 根节点的 表尾 为空,将 根节点的 表尾 放 在 表头 的 表尾 指针上. 这样 从 表头 一直 就可以 遍历 所有 同级 节点. 具体j结构 如下: 例如 下面的 广义表 ,用 扩展线性链表 表示为: 而 头尾 存储表示,是 把 表头 和 表尾 都放在 根节点 的 指针上.其存储结构如下: 所以 其 实现 代码略有 不同,要 小心 处理 下面 上代码: // GList2.cpp : 定义控制台应用

看数据结构写代码(51) 广义表

广义表是一种非线性的数据结构.但如果广义表的每个元素都是原子,它就变成了线性表.广义表广泛地用于人工智能等领域的LISP语言. 广义表一般记作 LS = (a1, a2, ···, an), n是它的长度,ai可以是单个元素(原子),也可以是广义表(子表),当广义表非空时,称第一个元素a1为LS的表头,称其余元素组成的表为LS的表尾.注意:表头是元素(可以是原子,也可以是广表),表尾一定是广义表.E=(a, E)是一个递归的表.D=(( ),(e),(a,(b,c,d)))是多层次的广义表,长度

看数据结构写代码(38) 图的邻接多重表表示法与实现

图的邻接多重表 是 无向图的 另一种表示法.其与 邻接表 的差别 仅仅 在于 ,邻接表 用 两个 顶点 来表示 一条边,而 邻接多重表 用一个 顶点来表示一条边.这样使得 邻接多重表 在 某些操作 要 来的 方便.例如 将 搜索过的边 做记号 或者 删除 一条边. 下面是邻接多重表的结构: 下面的 6条边 用 6个弧 节点表示,用12个指针指向,每个弧节点被 指向2次.这样使得我们 在 释放内存的时候 需要格外小心. 下面上代码: 源码工程文件网盘地址:点击打开链接 // AMLGraph.cp

看数据结构写代码(53) 静态查找表(线性查找,二分查找,斐波那契查找,插值查找)

查找定义:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录). 查找表分类:静态查找表和动态查找表. 静态查找表:只查找,而不进行插入,删除. 动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素. 静态表的 查找 大致 四种 算法: 线性查找,二分查找,斐波那契查找和插值查找. 其中 在线性查找之前,对表 无要求.对于 其余三种 需要 在查找之前 排序.插值查找 除了 需要 排序,还需要 均匀分布. 下面 给出代码: 线性查

看数据结构写代码(50)伙伴系统

伙伴系统 是一种 只 可以 分配 2的 幂次方 个 空间的 ,回收 内存 时 只 合并 "伙伴空间" 的一种 动态内存管理方式. 例如 一个 空间 大小 为 64 的 内存,伙伴 系统 为 这 64 的内存  建立 一组 双向循环 链表,分别 管理着  2的 0 次方,2的1 次方幂,2的 2 次方幂...2的6次方幂的 可用空间. 即使 我们 只想分配 一个 大小 为3的 空间,系统 却 只能 返回 一个 内存 大小 为 4(2的2次方)的 一个空间. 系统 在 初始化的 时候 ,并

看数据结构写代码(15)链式队列的实现

队列 和 栈 是 一种 受限制的 线性表.所以 他们的 实现方式 都 相差 无几.之前有过  链栈 和 链式线性表 的 实现经验,自然 写 链队 ,也毫无问题. 下面详细讲解每一段代码 的技术要点 下面是队列节点的数据结构 struct QueueNode { ElementType data; QueueNode * next; }; //生成一个节点 QueueNode * queueNodeMake(ElementType data){ QueueNode * pNode = (Queue

看数据结构写代码(44) 判断无向图是否有环路

在 看 严蔚敏的 数据结构 一书 7.5小节时,书上 说" 判断有向图是否存在环要不无向图复杂.对于无向图来说,深度优先遍历过程中遇到回边(即指向已访问过的顶点的边),则必定存在环路". 看的不明白,所以 网上 百度了一下. 有了思路:故写下算法 和思路,以便以后 温故. 思路: 1.一个n个顶点,e条边的 无向图,若 e>= n,必有环路. 2.若 e < n ,需要 深度 遍历,并把 父节点传入 参数中,如果 遇到 一个 节点 被访问过 并且 不是 父节点,那么 就有环