散列表的定义:
散列表是一种数据结构。理想的散列表数据结构是一个具有固定大小的数组。
散列函数:
对于数组的每一个对象,都会有一个关键字,我们成为键值,例如数据对象是一个字符串,就可以直接作为键值,如果数据对象是一个类,那可以取其中的某一个成员变量作为键值。将每个键值映射到从0到tableSize-1这个范围中的某个数,并且将其放到适当的单元中,这个映射就称为散列函数。理想情况下,它应该运算简单并且应该保证任何两个不同的键映射到不同的单元。
冲突:
理想的散列函数是不可能存在的,因为单元的数目是有限的,而键值实际上是用不完的。当两个键值散列到同一个单元的时候,这种情况称为冲突。解决冲突,也是散列表的重要的特征。
解决冲突的方法之分离链表法:
分离链表法,其做法是将散列到同一个值的所有元素保留到一个链表中。举个代码例子:
///***************************HashMap****************************/
//1.用数组形式建立哈希表
//2.使用链式方法解决冲突
//3.平方取中法通过字符串Hash函数求hash值(还有很多种其他的hash函数)
#include <iostream>
#include <string>
#include <cstring>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
typedef unsigned long(*GetKeyValue)(const string& );
//该类用来处理冲突的节点
template<class TypeA,class TypeB>
struct HashNode{
TypeA key;
TypeB value;
HashNode *next;
HashNode(TypeA key,TypeB value){
HashNode::key = key;
HashNode::value = value;
next = NULL;
}
HashNode& operator=(const HashNode& node){
key = node.key;
value = node.key;
next = node.next;
return *this;
}
};
//该类是HashMap用来存放hash表
template<class TypeA,class TypeB,class FuncType>
class HashMap
{
HashNode<TypeA,TypeB> **table;
unsigned long capacity;
FuncType GetKeyValue;
const TypeB TYPEB_NULL;
public:
HashMap(FuncType func,const TypeB& _null);
~HashMap();
TypeB Put(const HashNode<TypeA,TypeB>& hashNode); //插入一个HashNode 返回该节点的value值
TypeB GetValue(const TypeA& key); // 查找某个hashNode 其key为“key“的元素
TypeB Delete(const TypeA& key); // 查找某个hashNode 其key为“key“的元素
};
template<class TypeA,class TypeB,class FuncType> //在调用的时候指定函数
HashMap<TypeA,TypeB,FuncType>::HashMap(FuncType func,const TypeB& _null) : TYPEB_NULL(_null)
{
capacity = 10000000;
GetKeyValue = func;
table = new HashNode<TypeA,TypeB>*[capacity];
for(unsigned i = 0;i < capacity;i++)
table[i] = NULL;
}
template<class TypeA,class TypeB,class FuncType>
HashMap<TypeA,TypeB,FuncType>::~HashMap(){
for(unsigned i = 0;i < capacity;i++){
HashNode<TypeA,TypeB> *currentNode = table[i];
while(currentNode)
{
HashNode<TypeA,TypeB> *temp = currentNode;
currentNode = currentNode->next;
delete temp;
}
}
delete table;
}
//新增节点操作,用鏈式法解決衝突
template<class TypeA,class TypeB,class FuncType>
TypeB HashMap<TypeA,TypeB,FuncType>::Put(const HashNode<TypeA,TypeB>& hashNode){
HashNode<TypeA,TypeB> *newNode = NULL;
unsigned long index = GetKeyValue(hashNode.key);
if(table[index] == NULL){
table[index] = new HashNode<TypeA,TypeB>(hashNode.key,hashNode.value);
newNode = table[index];
}
else{
newNode = table[index];
while(newNode->next){
newNode = newNode->next;
}
newNode->next = new HashNode<TypeA,TypeB>(hashNode.key,hashNode.value);
newNode = newNode->next;
}
return newNode->value;
}
//由键值获得value
template<class TypeA,class TypeB,class FuncType>
TypeB HashMap<TypeA,TypeB,FuncType>::GetValue(const TypeA& key){
unsigned long index = GetKeyValue(key);
if(table[index] == NULL)
return TYPEB_NULL;
else{
HashNode<TypeA,TypeB> *currentNode = table[index];
while(currentNode){
if(currentNode->key == key)
return currentNode->value;
currentNode = currentNode->next;
}
}
return TYPEB_NULL;
}
//由键值查找后,删除该节点,返回该删除的节点的value
template<class TypeA,class TypeB,class FuncType>
TypeB HashMap<TypeA,TypeB,FuncType>::Delete(const TypeA& key){
TypeB deletedNodeValue = TYPEB_NULL;
unsigned long index = GetKeyValue(key);
if(table[index] == NULL)
return deletedNodeValue;
else{
HashNode<TypeA,TypeB> *currentNode = table[index];
if(currentNode->key == key){
table[index] = currentNode->next;
deletedNodeValue = currentNode->value;
delete currentNode;
return deletedNodeValue;
}
while(currentNode){
if(currentNode->next->key == key){
HashNode<TypeA,TypeB> *temp = currentNode->next;
currentNode->next = currentNode->next->next;
deletedNodeValue = temp->value;
delete temp;
return deletedNodeValue;;
}
currentNode = currentNode->next;
}
}
return deletedNodeValue;
}
/***************************************测试****************************/
//平方取中法
//实现,取字符串中间3个字母,不足3个用0补足
unsigned long GetKeyValue_1(const string& key){
unsigned long keyValue = 0;
int strSize = key.size();
string tempStr(key);
for(int i = strSize;i < 3;i++)
tempStr = "0" + tempStr;
//如果大于3就取中间3位
if(strSize >= 3){
tempStr[0] = key[strSize / 2 - 1];
tempStr[1] = key[strSize / 2];
tempStr[2] = key[strSize / 2 + 1];
}
tempStr = tempStr.substr(0,3);
unsigned long num = 10000 * (unsigned long)(48);
num += 100 * (unsigned long)(tempStr[1]);
num += (unsigned long)(tempStr[2]);
num *= num;
char *numStr = new char[15];
snprintf(numStr,10,"%d",num) ;
int strLen = strlen(numStr);
tempStr = "000000";
for(int i = -2;i < 4;i++)
tempStr[2 + i] = numStr[strLen / 2 + i];
keyValue = strtol(tempStr.c_str(),NULL,10);
delete []numStr;
return keyValue;
}
int main()
{
clock_t start = clock();
//传入一个求哈希散列值的方法GetKeyValue_1
HashMap<string,string,GetKeyValue> hashMap(GetKeyValue_1,"NULL");
for(int i = 0;i < 100000;i++){
char *ckey = new char[20];
char *cvalue = new char[20];
snprintf(ckey,10,"%d",i);
snprintf(cvalue,10,"%d",i);
string key(ckey);
string value(cvalue);
if(i == 67){
key = "67";
value = "hello hash No.67";
}
HashNode<string,string> node1(key,value);
//插入该节点
hashMap.Put(node1);
// cout << "index: " <<GetKeyValue_1(key) << " nodeValue: "
// << hashMap.Put(node1) << endl;
}
clock_t end = clock();
cout << "Test Result: \n";
//调用了time.h文件的CLOCKS_PER_SEC,表示每过千分之一秒,clock()函数返回值加1
cout << "插入100000条数据耗时: "
<< double(end - start) / CLOCKS_PER_SEC << " 秒" << endl;
cout << "hashMap.GetValue(\"67\"): " << hashMap.GetValue("67") << endl;
cout << "hashMap.Delete(\"67\"): " << hashMap.Delete("67") << endl;
cout << "hashMap.GetValue(\"67\"): " << hashMap.GetValue("67") << endl;
return 0;
}
解决冲突方法之探测散列:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include <windows.h>
#include <time.h>
#define MAXSIZE 20 //电话薄记录数量
#define MAX_SIZE 20 //人名的最大长度
#define HASHSIZE 53 //定义表长
#define SUCCESS 1
#define UNSUCCESS -1
#define LEN sizeof(HashTable)
typedef int Status;
typedef char NA[MAX_SIZE];
typedef struct{//记录
NA name;
NA tel;
NA add;
}Record;
typedef struct{//哈希表
Record *elem[HASHSIZE]; //数据元素存储基址
int count; //当前数据元素个数
int size; //当前容量
}HashTable;
Status eq(NA x,NA y)
{//关键字比较,相等返回SUCCESS;否则返回UNSUCCESS
if(strcmp(x,y)==0)
return SUCCESS;
else return UNSUCCESS;
}
Status NUM_BER; //记录的个数
void getin(Record* a){//键盘输入各人的信息
printf("输入要添加的个数:\n");
scanf("%d",&NUM_BER);
int i;
for(i=0;i<NUM_BER;i++){
printf("请输入第%d个记录的用户名:\n",i+1);
scanf("%s",a[i].name);
printf("请输入%d个记录的电话号码:\n",i+1);
scanf("%s",a[i].tel);
printf("请输入第%d个记录的地址:\n",i+1);
scanf("%s",a[i].add); //gets(str2);??????
}
}
void ShowInformation(Record* a)//显示输入的用户信息
{
int i;
for( i=0;i<NUM_BER;i++)
printf("\n第%d个用户信息:\n 姓 名:%s\n 电话号码:%s\n 联系地址:%s\n",i+1,a[i].name,a[i].tel,a[i].add);
}
void Cls(Record* a)
{
printf("*");
system("clear");
}
long fold(NA s)
{//人名的折叠处理
char *p;
long sum=0;
NA ss;
strcpy(ss,s);//复制字符串,不改变原字符串的大小写
// strupr(ss);//将字符串ss转换为大写形式
p=ss;
while(*p!='\0')
sum+=*p++;
printf("\nsum====================%d",sum);
return sum;
}
int Hash1(NA str)
{//哈希函数
long n;
int m;
n = fold(str);//先将用户名进行折叠处理
m = n%HASHSIZE; //折叠处理后的数,用除留余数法构造哈希函数
return m; //并返回模值
}
int Hash2(NA str)
{//哈希函数
long n;
int m;
n = atoi(str);//把字符串转换成整型数.
m = n % HASHSIZE;
return m; //并返回模值
}
Status collision(int p,int c)
{//冲突处理函数,采用二次探测再散列法解决冲突
int i,q;
i=c/2+1;
while(i<HASHSIZE){
if(c%2==0){
c++;
q=(p+i*i)%HASHSIZE;
if(q>=0) return q;
else i=c/2+1;
}
else{
q=(p-i*i)%HASHSIZE;
c++;
if(q>=0) return q;
else i=c/2+1;
}
}
return UNSUCCESS;
}
void benGetTime();
void CreateHash1(HashTable* H,Record* a)
{//建表,以人的姓名为关键字,建立相应的散列表
//若哈希地址冲突,进行冲突处理
benGetTime();
int i,p=-1,c,pp;
for(i=0;i<NUM_BER;i++)
{
c=0;
p=Hash1(a[i].name);
pp=p;
while(H->elem[pp]!=NULL)
{
pp=collision(p,c);
if(pp<0)
{
printf("第%d记录无法解决冲突",i+1);//需要显示冲突次数时输出
continue;
}//无法解决冲突,跳入下一循环
}
H->elem[pp]=&(a[i]); //求得哈希地址,将信息存入
H->count++;
printf("第%d个记录冲突次数为%d。\n",i+1,c);//需要显示冲突次数时输出
}
printf("\n建表完成!\n此哈希表容量为%d,当前表内存储的记录个数为%d.\n",HASHSIZE,H->count);
benGetTime();
}
void SearchHash1(HashTable* H,int c)
{//在通讯录里查找姓名关键字,若查找成功,显示信息
//c用来记录冲突次数,查找成功时显示冲突次数
benGetTime();
NA str;
printf("\n请输入要查找记录的姓名:\n");
scanf("%s",str);
int p,pp;
p=Hash1(str);
pp=p;
while((H->elem[pp]!=NULL)&&(eq(str,H->elem[pp]->name)==-1))
pp=collision(p,c);
if(H->elem[pp]!=NULL&&eq(str,H->elem[pp]->name)==1)
{
printf("\n查找成功!\n查找过程冲突次数为%d.以下是您需要要查找的信息:\n\n",c);
printf("姓 名:%s\n电话号码:%s\n联系地址:%s\n",H->elem[pp]->name,H->elem[pp]->tel,H->elem[pp]->add);
}
else printf("\n此人不存在,查找不成功!\n");
benGetTime();
}
void benGetTime()
{
// SYSTEMTIME sys; SHSIZE; //用除留余数法构造哈希函数
// GetLocalTime( &sys );
time_t lt; /*define a longint time varible*/
lt=time(NULL);/*system time and date*/
printf(ctime(<)); /*english format output*/
// printf( "%4d/%02d/%02d %02d:%02d:%02d.%03d \n",sys.wYear,sys.wMonth,sys.wDay,sys.wHour,sys.wMinute, sys.wSecond,sys.wMilliseconds);
}
void CreateHash2(HashTable* H,Record* a)
{//建表,以电话号码为关键字,建立相应的散列表 若哈希地址冲突,进行冲突处理
benGetTime();
int i,p=-1,c,pp;
for(i=0;i<NUM_BER;i++)
{
c=0;
p=Hash2(a[i].tel);
pp=p;
while(H->elem[pp]!=NULL)
{
pp=collision(p,c);
if(pp<0)
{
printf("第%d记录无法解决冲突",i+1);//需要显示冲突次数时输出
continue;
}//无法解决冲突,跳入下一循环
}
H->elem[pp]=&(a[i]); //求得哈希地址,将信息存入
H->count++;
printf("第%d个记录冲突次数为%d。\n",i+1,c);//需要显示冲突次数时输出
}
printf("\n建表完成!\n此哈希表容量为%d,当前表内存储的记录个数为%d.\n",HASHSIZE,H->count);
benGetTime();
}
void SearchHash2(HashTable* H,int c)
{//在通讯录里查找电话号码关键字,若查找成功,显示信息 c用来记录冲突次数,查找成功时显示冲突次数
benGetTime();
NA tele;
printf("\n请输入要查找记录的电话号码:\n");
scanf("%s",tele);
int p,pp;
p=Hash2(tele);
pp=p;
while((H->elem[pp]!=NULL)&&(eq(tele,H->elem[pp]->tel)==-1))
pp=collision(p,c);
if(H->elem[pp]!=NULL&&eq(tele,H->elem[pp]->tel)==1){
printf("\n查找成功!\n查找过程冲突次数为%d.以下是您需要要查找的信息:\n\n",c);
printf("姓 名:%s\n电话号码:%s\n联系地址:%s\n",H->elem[pp]->name,H->elem[pp]->tel,H->elem[pp]->add);
}
else printf("\n此人不存在,查找不成功!\n");
benGetTime();
}
void Save()
{
FILE *fp;
if((fp=fopen("c:\test.txt", "w"))==NULL){
printf("\nERROR opening customet file");
}
fclose(fp);
}
int main(int argc, char* argv[])
{
int c,flag=1;
HashTable *H;
H=(HashTable*)malloc(LEN);
int i=0;
for(;i<HASHSIZE;i++)
{
H->elem[i]=NULL;
H->size=HASHSIZE;
H->count=0;
}
Record a[MAXSIZE];
while (1)
{
printf("\n ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ");
printf("\n ┃ 欢迎使用电话号码查找系统 ┃ ");
printf("\n ┏〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓┓");
printf("\n ┃ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ 哈希表的设计与实现 ★ ★ ★ ★ ★ ★ ★ ★ ★ ┃ ");
printf("\n ┃ 【1】. 添加用户信息 ┃");
printf("\n ┃ 【2】. 读取所有用户信息 ┃");
printf("\n ┃ 【3】. 以姓名建立哈希表(再哈希法解决冲突) ┃");
printf("\n ┃ 【4】. 以电话号码建立哈希表(再哈希法解决冲突) ┃");
printf("\n ┃ 【5】. 查找并显示给定用户名的记录 ┃");
printf("\n ┃ 【6】. 查找并显示给定电话号码的记录 ┃");
printf("\n ┃ 【7】. 清屏 ┃");
printf("\n ┃ 【8】. 保存 ┃");
printf("\n ┃ 【9】. 退出程序 ┃");
printf("\n ┃ 温馨提示: ┃");
printf("\n ┃ Ⅰ.进行5操作前 请先输出3 ┃");
printf("\n ┃ Ⅱ.进行6操作前 请先输出4 ┃");
printf("\n ┗〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓┛");
printf("\n");
printf("请输入一个任务选项>>>");
printf("\n");
int num;
scanf("%d",&num);
switch(num)
{
case 1:
getin(a);
break;
case 2:
ShowInformation(a);
break;
case 3:
CreateHash1(H,a); /* 以姓名建立哈希表 */
break;
case 4:
CreateHash2(H,a); /* 以电话号码建立哈希表 */
break;
case 5:
c=0;
SearchHash1(H,c);
break;
case 6:
c=0;
SearchHash2(H,c);
break;
case 7:
Cls(a);
break;
case 8:
Save();
break;
case 9:
return 0;
break;
default:
printf("你输错了,请重新输入!");
printf("\n");
break;
}
}
return 0;
}