散列表(哈希表)介绍

散列表的定义:

散列表是一种数据结构。理想的散列表数据结构是一个具有固定大小的数组。

散列函数:

对于数组的每一个对象,都会有一个关键字,我们成为键值,例如数据对象是一个字符串,就可以直接作为键值,如果数据对象是一个类,那可以取其中的某一个成员变量作为键值。将每个键值映射到从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;
} 


 

        

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值