哈希表(HashTable)的开放定址法和链地址法的实现
2014-07-19 18:26
411 查看
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。引用(百度)
算法时间复杂度分析:采用哈希表作为数据结构的存储系统中,根据关键字的值可快速定位到相应的地址上,在采用开放定址法时仅需O(1)复杂度就可找到,在采用链地址法时,需要O(N)复杂度,主要在链表中搜索,相对于搜索树的O(lg(N))的复杂度,开放定址法显然来得快,但是哈希表的长度会变得非常长,采用链地址法时快速定位到相应的头结点中,只需在链表中循环遍历即可,编程难度比树降低了不少,还可以将链地址法中哈希表数组中的指针指向一个树,这样在搜索时,快速定位到搜索树的根节点,根据树的对数搜索复杂度,更可快速的找到元素,比如说,红黑树,B树..
关于哈希表中的元素指针只想为树的结点时,相应的结构如下:
下面采用开放定址法和链地址法实现哈希表:
1. 开放定址法:
2. 接下来为链地址法处理冲突:
散列表(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关键字的元素 }
相关文章推荐
- 取余数法实现哈希表(包括开放定址法和链地址法解决冲突)
- javascript 哈希表(hashtable)的简单实现
- javascript 哈希表(hashtable)的简单实现
- javascript中的哈希表(hashtable)实现
- 深入哈希表(二)--开放定址法实现哈希表
- Javascript实现HashTable(哈希表)
- c语言实现最简单的哈希表(开放地址线性探测法)
- 哈希表(散列)HashTable实现
- javascript 哈希表(hashtable)的简单实现
- [PHP] PHP数组的实现哈希表(HashTable)结构
- js实现HashTable(哈希表)的实例分析
- 哈希表的简单实现【链地址法解决冲突】
- Java中哈希表(Hashtable)是如何实现的
- javascript 实现HashTable(哈希表)
- 哈希表(散列)HashTable实现
- 模拟实现hashTable(哈希表)
- 哈希表(hashtable)的javascript简单实现
- C语言实现哈希表HashTable及迅雷面试题哈希表桶结构代码
- 哈希表(hashtable)的javascript简单实现
- 哈希表(hashtable)的javascript简单实现