哈希表也叫做散列表。在各种语言中都有hashmap的实现。其最突出的优点是查找和插入以及删除具有常数的时间复杂度
我们可以把哈希表理解为数组+链表
数组具有常数复杂度的查找,为什么呢,因为数组是在内存中连续存放,当我们索引某个位置的元素时候根据索引值自动计算距离数组首元素起始位置的内存间隔。然而对于中间插入和中间删除元素,数组会将待插入位置后面的原素先整体向后移动一个位置再将元素插入到待插入的位置。所以时间复杂度为O(n)对于大规模的数组来说,开销是很大的。
然而链表正相反。由于链表是存储在内存中不连续的位置,并且链表之间是通过链表指针链接到一起。前一个元素保存后一个元素的指针,我们也称之为前一个元素指向后一个元素。当我们在链表中寻找某个元素的时候必须按照链表指针遍历整个链表,时间复杂度为O(n)但是针对插入很删除来说,我们只需要更改待需要操作的链表元素的指针即可,时间复杂度为O(1).
那么我们可以不可以把两者结合起来组合成一种新的数据结构呢,这种数据结构即能实现常数复杂度的索引也能实现常数复杂度的插入和删除。当然哈希表应运而生。
哈希表的原理可以简述为(简述插入和索引操作):
插入:当我们插入hashmap['key']=value时,先将key映射为hashcode,然而用hashcode对哈希表长度取模获得索引位置。然后将值插入到索引位置。(这里可能会存在冲突,即相同的Key的hashcode相同)
索引:当我们索引某个值的时候,先将key映射为hashcode,然后将key对哈希表长度取模,然后取出该索引位置上的值。
下面我简单实现了一个哈希表,这仅仅是为了说明哈希的原理。
简单hashmap类定义:
1 #include<iostream> 2 using namespace std; 3 class MySimpleHashmap 4 { 5 public: 6 int *Array; 7 enum{MaxHashSize=7}; 8 9 int hashcode(int &key); 10 11 public: 12 MySimpleHashmap(); 13 ~MySimpleHashmap(); 14 15 int& operator[](int & key); 16 17 }; 18 19 20 int& MySimpleHashmap::operator [](int& key) 21 { 22 return Array[hashcode(key)]; 23 } 24 25 26 MySimpleHashmap::MySimpleHashmap() 27 { 28 Array=new int[MaxHashSize]; 29 30 for(int i=0;i<MaxHashSize;i++) 31 { 32 Array[i]=0; 33 } 34 } 35 36 MySimpleHashmap::~MySimpleHashmap() 37 { 38 delete[] Array; 39 Array=NULL; 40 } 41 42 int MySimpleHashmap::hashcode(int& key) 43 { 44 while(true) 45 { 46 if(Array[key%MaxHashSize]==0) 47 { 48 return key%MaxHashSize; 49 } 50 else 51 { 52 key++; 53 } 54 } 55 }
main.c
1 #include "VpoetHashmap.h" 2 #include <iostream> 3 using namespace std; 4 5 int main() 6 { 7 MySimpleHashmap MyHash; 8 9 int key1=15; //hashkey=1 10 int key2=16; //hashkey=2 11 int key3=9; //hashkey=2冲突 12 int key4=11; //hashkey=4 13 int key5=5; //hashkey=5 14 15 MyHash[key1]=100; 16 MyHash[key2]=200; 17 MyHash[key3]=300; 18 MyHash[key4]=400; 19 MyHash[key5]=500; 20 21 cout<<"hash table node1:"<<MyHash.Array[0]<<endl; 22 cout<<"hash table node2:"<<MyHash.Array[1]<<endl; 23 cout<<"hash table node3:"<<MyHash.Array[2]<<endl; 24 cout<<"hash table node4:"<<MyHash.Array[3]<<endl; 25 cout<<"hash table node5:"<<MyHash.Array[4]<<endl; 26 cout<<"hash table node6:"<<MyHash.Array[5]<<endl; 27 cout<<"hash table node7:"<<MyHash.Array[6]<<endl; 28 return 0; 29 }
运行截图:
此外:本文只是粗略的引入了哈希表的原理。里面有几个很重要的地方还没有涉及到:
1.如果不同的的Key的hashcode相同,那么便引起了冲突。如何解决冲突呢。
2.哈希表的初始大小应该怎么取。
3.key映射成索引的hash函数有哪些,这些函数的取法与是否与冲突的次数有关。
4.什么是二次聚集
5.若哈希表填满之后应该怎么扩展哈希表大小
6.装填因子是什么。
这些问题都是在实现hashmap中十分重要的点。