原文地址:散列查找
基本概念 散列(Hashing)通过散列函数(哈希函数)将要参与检索的数据与散列值(哈希值)关联起来,生成一种便于搜索的数据结构,我们称其为散列表(哈希表),也就是说,现在我们需要将一堆数据保存起来,这些数据会通过哈希函数进行计算,得到与其对应的哈希值,当我们下次需要查找这些数据时,只需要再次计算哈希值就能快速找到对应的元素了
散列函数 散列函数也叫哈希函数,哈希函数可以对一个目标计算出其对应的哈希值,并且,只要是同一个目标,无论计算多少次,得到的哈希值都是一样的结果,不同的目标计算出的结果介乎都不同。哈希函数在现实生活中应用十分广泛,比如很多下载网站都提供下载文件的MD5码校验,可以用来判别文件是否完整,哈希函数多种多样,目前应用最为广泛的是SHA-1和MD5,比如我们在下载IDEA之后,会看到有一个验证文件SHA-256校验和的选项,我们可以点进去看看
散列表 可以利用哈希值的特性,设计一张全新的表结构,这种表结构是专为哈希设立的,我们称其为哈希表(散列表)
我们可以将这些元素保存到哈希表中,而保存的位置则与其对应的哈希值有关,哈希值是通过哈希函数计算得到的,我们只需要将对应元素的关键字(一般是整数)提供给哈希函数就可以进行计算了,一般比较简单的哈希函数就是取模操作,哈希表长度是多少(长度最好是一个素数),模就是多少
哈希值是通过哈希函数计算得到的,我们只需要将对应元素的关键字(一般是整数)提供给哈希函数就可以进行计算了,一般比较简单的哈希函数就是取模操作,哈希表长度是多少(长度最好是一个素数),模就是多少
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #define SIZE 9 typedef struct Element { int key; } * E;typedef struct HashTable { E * table; } * HashTable;int hash (int key) { return key % SIZE; }void init (HashTable hashTable) { hashTable->table = malloc (sizeof (struct Element) * SIZE); for (int i = 0 ; i < SIZE; ++i) hashTable->table[i] = NULL ; }void insert (HashTable hashTable, E element) { int hashCode = hash(element->key); hashTable->table[hashCode] = element; }_Bool find (HashTable hashTable, int key) { int hashCode = hash(key); if (!hashTable->table[hashCode]) return 0 ; return hashTable->table[hashCode]->key == key; }E create (int key) { E e = malloc (sizeof (struct Element)); e->key = key; return e; }int main () { struct HashTable hashTable ; init(&hashTable); insert(&hashTable, create(10 )); insert(&hashTable, create(7 )); insert(&hashTable, create(13 )); insert(&hashTable, create(29 )); printf ("%d\n" , find(&hashTable, 1 )); printf ("%d\n" , find(&hashTable, 13 )); }
哈希冲突 通过哈希函数计算得到一个目标的哈希值,但是在某些情况下,哈希值可能会出现相同的情况。
这种问题是很严重的,因为哈希函数的设计不同,难免会出现这种情况,这种情况是不可避免的,我们只能通过使用更加高级的哈希函数来尽可能避免这种情况,但是无法完全避免。当然,如果要完全解决这种问题,我们还需要去寻找更好的方法。
线性探针法 可以去找找哈希表中相邻的位置上有没有为空的,只要哈希表没装满,那么我们肯定是可以找到位置装下这个元素的,这种类型的解决方案我们统称为线性探测法,开放定址法包含,线性探测法、平方探测法、双散列法等,这里我们以线性探测法为例。
既然第一次发生了哈希冲突,那么我们就继续去找下一个空位:
$$ h_i(key) = (h(key) + d_i) % TableSize $$ 其中di是随着哈希冲突次数随之增加的量,比如上面出现了一次哈希冲突,那么就将其变成1表示发生了一次哈希冲突,然后可以继续寻找下一个位置
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void insert (HashTable hashTable, E element) { int hashCode = hash(element->key), count = 0 ; while (hashTable->table[hashCode]) { hashCode = hash(element->key + ++count); } hashTable->table[hashCode] = element; }_Bool find (HashTable hashTable, int key) { int hashCode = hash(key), count = 0 ; const int startIndex = hashCode; do { if (hashTable->table[hashCode]->key == key) return 1 ; hashCode = hash(key + ++count); } while (startIndex != hashCode && hashTable->table[hashCode]); return 0 ; }int main () { struct HashTable hashTable ; init(&hashTable); for (int i = 0 ; i < 9 ; ++i) { insert(&hashTable, create(i * 9 )); } for (int i = 0 ; i < 9 ; ++i) { printf ("%d " , hashTable.table[i]->key); } }
链地址法 常见的哈希冲突解决方案是链地址法,当出现哈希冲突时,我们依然将其保存在对应的位置上,我们可以将其连接为一个链表的形式
1.通过结合链表的形式,哈希冲突问题就可以得到解决了,但是同时也会出现一定的查找开销
2.因为现在有了链表,我们得挨个往后看才能找到,当链表变得很长时,查找效率也会变低
3.可以考虑结合其他的数据结构来提升效率。
4.当链表长度达到8时,自动转换为一棵平衡二叉树或是红黑树,这样就可以在一定程度上缓解查找的压力了
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #define SIZE 9 typedef struct ListNode { int key; struct ListNode * next ; } * Node;typedef struct HashTable { struct ListNode * table ; } * HashTable;void init (HashTable hashTable) { hashTable->table = malloc (sizeof (struct ListNode) * SIZE); for (int i = 0 ; i < SIZE; ++i) { hashTable->table[i].key = -1 ; hashTable->table[i].next = NULL ; } }int hash (int key) { return key % SIZE; }Node createNode (int key) { Node node = malloc (sizeof (struct ListNode)); node->key = key; node->next = NULL ; return node; }void insert (HashTable hashTable, int key) { int hashCode = hash(key); Node head = hashTable->table + hashCode; while (head->next) head = head->next; head->next = createNode(key); }_Bool find (HashTable hashTable, int key) { int hashCode = hash(key); Node head = hashTable->table + hashCode; while (head->next && head->key != key) head = head->next; return head->key == key; }int main () { struct HashTable table ; init(&table); insert(&table, 10 ); insert(&table, 19 ); insert(&table, 20 ); printf ("%d\n" , find(&table, 20 )); printf ("%d\n" , find(&table, 17 )); printf ("%d\n" , find(&table, 19 )); }