哈希表(HashTable)的开放定址法和链地址法的实现



散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。引用(百度)


算法时间复杂度分析:采用哈希表作为数据结构的存储系统中,根据关键字的值可快速定位到相应的地址上,在采用开放定址法时仅需O(1)复杂度就可找到,在采用链地址法时,需要O(N)复杂度,主要在链表中搜索,相对于搜索树的O(lg(N))的复杂度,开放定址法显然来得快,但是哈希表的长度会变得非常长,采用链地址法时快速定位到相应的头结点中,只需在链表中循环遍历即可,编程难度比树降低了不少,还可以将链地址法中哈希表数组中的指针指向一个树,这样在搜索时,快速定位到搜索树的根节点,根据树的对数搜索复杂度,更可快速的找到元素,比如说,红黑树,B树..

关于哈希表中的元素指针只想为树的结点时,相应的结构如下:

                                                   

下面采用开放定址法和链地址法实现哈希表:


1. 开放定址法:

include<stdio.h>
#include<stdlib.h>
#pragma warning(disable:4996)
typedef int KeyType;

int hashsize[] = { 11, 19, 29, 37 };//哈希表容量递增表,一个合适的素数序列
int m;//哈希表的表长,全局变量

struct ElemType{
	KeyType key;
	int order;
	struct ElemType*next;//方便在采用链地址法时用的
};
typedef struct HashTable1{
	ElemType *elem;//数据元素的基址
	int count;
	int hashindex;
}HashTable;

/*
	初始化哈希表
*/
void InitHashTable(HashTable&H){
	H.count = 0;
	H.hashindex = 0;
	m = hashsize[H.hashindex];//初始化哈希表表长为hashsize[H.hashindex]
	if ((H.elem = (ElemType*)malloc(sizeof(ElemType)*m)) == NULL){
		printf("初始化HashTable基址失败\n");
		exit(-1);
	}
	for (int i = 0; i < m; i++){
		H.elem[i].key = 0;//未填充的记录
	}
}

/*
Hash函数
采用取余法,用关键字的值余上表的长度,作为哈希存储的地址
*/
unsigned Hash(KeyType K){
	return K%m;
}
int d(int i){//增量序列是冲突次数i的函数
	return i;//线性探测再散列
	//return rand() 随机探测再散列,一班用线性探测再散列就够了
}


/*
开放定址法处理冲突
*/
void collision(KeyType K, int&p, int i){
	p = (Hash(K) + d(i)) % m;//最后得到的结果一定在0~m-1之间
}

/*
	在哈希表中查找关键字为K的记录,若找到了则返回success,p表示数据在表中的位置
	否则以p指示插入位置,并返回Unsuccess.i为冲突次数,传出参数
	*/
int SearchHash(HashTable&H, KeyType K, int &p, int &i){
	p = Hash(K);//根据关键字计算哈希地址
	while (H.elem[p].key != 0 && K != H.elem[p].key){//在所得的地址上关键字不为空,且不等于该位置上的关键字,则产生冲突
		++i;//冲突次数自增一次
		if (i < m){//还有可能找到位置
			collision(K, p, i);//计算下一个位置/根据开放定址法
		}
		else break;//否则找不到位置了s
	}
	if (K == H.elem[p].key){//找到了位置
		return 1;
	}
	else return 0;
}
int InsertHashTable(HashTable&H, ElemType e);
void RecreateHashTable(HashTable&H){
	int i, count = H.count;
	ElemType *p, *elem = (ElemType*)malloc(sizeof(ElemType)*count);
	p = elem;
	for (i = 0; i < m; i++){
		if (H.elem[i].key != 0){//H在该单元有数据
			*p++ = H.elem[i];
		}
	}//将H中的数据临时存放到elem中对应的位置上去
	H.count = 0;
	++H.hashindex;

	m = hashsize[H.hashindex];//新的存储容量
	H.elem = (ElemType*)realloc(H.elem, m*sizeof(ElemType));//利用H.elem重新分配表长大小为m哈希表长空间
	for (i = 0; i < m; i++){
		H.elem[i].key = 0;//赋初值
	}
	for (p = elem; p < elem + count; p++)
		InsertHashTable(H, *p);//将临时的数据再次插入到新构造的HashTable中
	free(elem);
}
/*
	在哈希表中插入数据项为e的元素
	*/
int InsertHashTable(HashTable&H, ElemType e){
	int p, c = 0;//c为冲突的次数
	if (SearchHash(H, e.key, p, c)){//如果找到了要插入的元素
		return -1;
	}
	else if (c < hashsize[H.hashindex] / 2){//未找到,冲突次数未达到上限
		H.elem[p] = e;
		H.count++;
		return 1;//插入成功
	}
	else {
		RecreateHashTable(H);//需重新建表
		return 0;
	}
}
/*
	遍历哈希
*/
void TraverseHashTable(HashTable H){
	for (int i = 0; i < m; i++){
		if (H.elem[i].key != 0)//第i个单元有数据
			printf("%d ,(%d,%d)\n", i, H.elem[i].key, H.elem[i].order);
	}
}

2. 接下来为链地址法处理冲突:


typedef int KeyType;

int hashsize[] = { 11, 19, 29, 37 };//哈希表容量递增表,一个合适的素数序列
int m;//哈希表的表长,全局变量
struct ElemType{
	KeyType key;
	int order;
	struct ElemType*next;
};
typedef struct HashTable2{
	ElemType **elem;//二级指针型向量,采用动态分配空间大小
	int count;//当前的头指针向量的元素
	int hashindex;//hashsize[H.hashindex]为当前容量
}HashTableLinkList;
//表的容量永远不会扩充,只是链地址的链表会很长,于是选择合适的表长变得 很重要了
/*
	在ELemTYpe形成的链表中查找关键字等于K的元素,L指向头结点
	动态查找的链表
	*/
int SearchHashTableElemType(ElemType *L, KeyType K, ElemType *&v){
	ElemType *s = NULL;
	s = (ElemType*)malloc(sizeof(ElemType));
	s->key = K;
	if (!L->next){//一开始为空,插入一个元素
		s->next = L->next;
		L->next = s;
		v = s;
		return 0;
	}
	else{
		//	printf("\n进入了尾插法\n");
		ElemType* p = L->next;
		ElemType *q = L;
		while (p&&p->key != K){
			q = p;
			p = p->next;
		}
		if (p&&p->key == K){
			v = p;
			return 1;//找到了数据元素
		}

		else{//插入一个数据,此时p为空,需要她的前驱指针q指向最后一个元素,采用尾插法插入元素
			s->next = q->next;
			q->next = s;
			v = s;
			return 0;
		}
	}

}
/*
	链地址法解决哈希冲突
	*/
void InitHashTableLinkListHash(HashTableLinkList &H){
	H.count = 0;
	m = hashsize[0];
	H.elem = (ElemType**)malloc(m*sizeof(ElemType*));
	for (int i = 0; i < m; i++){
		H.elem[i] = (ElemType*)malloc(sizeof(ElemType));
		H.elem[i]->next = NULL;
	}
}
/*
	计算哈希地址
	*/
int HashLinkList(KeyType K){
	return K%m;
}
/*
	在哈希表中查找元素,从哈希链地址中查找数据项值等于e的元素
	,不需要冲突次数
	*/
ElemType* SearchHashTableLinkList(HashTableLinkList H, KeyType K, int &p){
	//ElemType *v = NULL;
	p = Hash(K);//p为根据关键字计算的处的头结点所在的位置,关键
	//printf("%d\n",p);
	ElemType *head = H.elem[p];//记下头结点,关键字记录肯定在以head为头结点的单链表中
	return head;
	//SearchHashTableElemType(head, K, v);
	//return v;
}

/*
	遍历采用链地址法的哈希表
	*/
void TraverseHashTableLinkList(HashTableLinkList H){
	ElemType *p = NULL, *q = NULL;
	for (int i = 0; i < m; i++){
		if ((p = H.elem[i])->next){//头结点不空
			printf("\n进入了新的链表:\n");
			q = p->next;//指向首节点
			while (q){
				printf("%d ", q->key);
				q = q->next;
			}
		}
	}
	printf("\n");
}
/*
	在哈希表中插入一个元素
	*/
ElemType* InsertHashTableLinkList(HashTableLinkList&H, ElemType e){
	int p = 0;//为插入的位置
	ElemType*v = NULL;
	ElemType*head = SearchHashTableLinkList(H, e.key, p);//找到数据项e应该插入的头结点所在的链表
	//SearchHashTableLinkListElemType;
	SearchHashTableElemType(head, e.key, v);//动态查找链表
	return v;//返回这个结点,不管找没找到,找到了返回,没找到会自动插入这个元素也返回
	//在以head为头结点的链表中查找e.key关键字的元素
}


#include #include typedef struct node { int data; struct node *next; }node; init_hash(node **A,int n) { int i; for(i=0;idata=0; A[i]->next=NULL; } } insert_hash(node **A,int value,int n) { int key; node *p,*q; key=value%n; if(A[key]->next!=NULL) { p=A[key]->next; while(p->next!=NULL) p=p->next; q=(node *)malloc(sizeof(node)); q->data=value; q->next=NULL; p->next=q; } else { q=(node *)malloc(sizeof(node)); q->data=value; q->next=NULL; A[key]->next=q; } } int search_hash(node **A,int value,int n) { int key; node *p; key=value%n; if(A[key]->next==NULL) return 0; else { p=A[key]->next; while(p!=NULL) { if(p->data==value) return 1; } return 0; } } delete_hash(node **A,int value,int n) { int key; node *p,*q; key=value%n; p=A[key]; q=A[key]->next; while(q->data!=value) { p=q; q=q->next; } p->next=q->next; free(q); } print_hash(node **A,int n) { int i; node *p; for(i=0;inext!=NULL) { p=A[i]->next; while(p!=NULL) { printf("%d ",p->data); p=p->next; } } } printf("\n"); } main() { int i,n,value,Case; node **A; printf("输入待排序元素个数:\n"); scanf("%d",&n); A=(node **)malloc(sizeof(node*)*n); //申请一个指针型数组A[n] init_hash(A,n);//初始化数组A printf("输入hash表的值(空格键分开):\n"); for(i=0;i<n;i++) //建hash表 { scanf("%d",&value); insert_hash(A,value,n); } printf("请选择hash表的操作\n1.查询\t2.插入\t3.删除\t4.输出\n"); scanf("%d",&Case); switch(Case) { case 1: { printf("请输入要查询的元素:"); scanf("%d",&value); printf(search_hash(A,value,n)?"Success!\n":"Failure!\n"); //查询 } case 2: { printf("请输入要插入的元素:"); scanf("%d",&value); insert_hash(A,value,n); //插入 } case 3: { printf("请输入要删除的元素:"); scanf("%d",&value); printf(search_hash(A,value,n)?"删除成功!\n":"表中不存在该元素!\n"); delete_hash(A,value,n); } case 4: //输出 { printf("表中元素为:\n"); print_hash(A,n); } } }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值