一个普通技术宅的点点滴滴

0%

哈希表的优势和实现

在对二叉树进行学习之后,相信每个人都会对于其精妙和最坏情况下仍然能保证线性对数级别时间复杂度的增删改查赞叹不已。其用来实现符号表(存储键值对)是一种较为高效的实现。

在没有学习哈希表之前,如果我告诉你,有一种数据结构,可以对键值对做到常数时间级别的增删改查,你一定不会相信。但是哈希表做到了,有这么高效的数据结构怎么能够不对其有了解呢?这次我们就来谈谈哈希表这种数据结构的魅力所在。

要了解哈希表,首先我们得来看一看这个哈希函数是什么东西。函数,其实就是一个映射,将所有在定义域的值映射到值域范围内。有时候我们需要对任意长度的信息,提取出信息的“指纹”,这个指纹在一定程度上反映了信息的内容,但是占用空间大大缩小。此时我们就要设计一个函数,其定义域为信息可能出现的所有空间,而值域则为需要”指纹”的大小。当这个函数可以用来完成上述用途时,这个函数就被称为哈希函数,哈希函数输出的结果被称为哈系值。一些著名的哈希函数有MD5、SHA-1、SHA-2等。

有了哈希函数,我们就可以利用哈希函数的映射,将任意长度的键映射到一个合理的区间内。试想创建一个数组,数组的下标为键的哈希值。数组的内容即为键值对的值,当我们已知键,要对值进行查找时,只需要对键取哈希,就可以在常数时间内获取到值的内容。

在哈希表的实现中,我们设计的哈希函数应该考虑的重点有如下几个

  1. 尽可能让输入中的每一位都对输出结果造成影响,减少碰撞的发生
  2. 计算尽量简单,否则哈希函数的计算也会对运行时间造成极大的影响
  3. 输出范围应当尽可能的可控

因为2的原因,我们不得不抛弃MD5和SHA这些著名的哈希函数,并且这些函数也很难自己控制输出的数据范围。哈希函数的好坏,决定了哈希表的效率。当然,实现哈希函数的方法不止一种,这里介绍比较常用的一种方法——除留余数法。

这里其实可以采用一种简单的运算来实现这个哈希函数——模运算。相信学过一点编程语言的朋友都知道,对某个数取模就是除以某个数求余数。假设我们需要0-97的哈希范围,无论多大的数,只需要对97取模,就可以获得一个在0-97之间的数,但是要注意,当我们采用的基数不是素数时,就会导致输出仅仅依赖于输入的后几位数,不满足1的情况发生。所以在使用这种方法作为哈希函数的过程中,应当选择合适的基数。

当我们有了哈系函数以后,就可以利用哈希函数类构造哈希表了。首先创建一个很大的数组array,数组的大小即为哈希函数的值域范围,然后在插入键值对时,直接使用array[hash(key)] = value的方式给数组赋值,当需要已知键,想要取出值的时候,同样采用array[hash(key)]即可。

下面我对哈希表的实现,供参考:


哈希碰撞的解决方法

读到这里,你可能已经发现了,哈希函数由于对信息进行了缩短,所以导致了有可能出现碰撞的存在,这就好像出现了两个名字一样的“张三”,当我们说,张三坐在某某位置上时,并不知道到底是哪个张三真正坐在位置上。这里有两种解决方案:拉链法和线性探测法

拉链法是将具有相同哈希值的函数放到一个链表里(整个哈希表呈现为一个链表的数组),用一个链表的数组来存放所有键值对。这种方法在最坏情况(哈希函数的返回值始终为常数)下会退化成链表。

而线性探测法则是采用两个数组,一个放键,一个放值,当即将保存的位置已经保存有数据的时候,从该位置开始往下遍历,直到遇到一个空的位置进行插入。这种方法在最坏情况下会退化成为数组。

两种方案对比,很难说哪种就一定跟高效,具体的实现中应当使用哪一种应该很据不同需求选取。

下面分别是线性探测和拉链法的哈希表实现

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

template <typename Key, typename Value> class LinearProbingHashST
{
private:
size_t size_;
size_t pair_counts_ = 0;
Key *key_arr_ ;
Value *value_arr_;
size_t gethash_(Key k);
Value get_ (Key k, size_t index);
Value put_ (Key k, Value val, size_t index);
void LinearProbingHashST_(size_t size);
public:
LinearProbingHashST (size_t size);
LinearProbingHashST ();
virtual ~LinearProbingHashST ();
void put(Key k, Value val);
Value get(Key k);
void erase(Key k);
bool empty();
};

template <typename Key, typename Value>
size_t LinearProbingHashST<Key, Value>::
gethash_(Key k)
{
return ( int(k) & 0x7FFFFFFF )% size_;
//return 1;
}

template <typename Key, typename Value>
void LinearProbingHashST<Key, Value>::
LinearProbingHashST_(size_t size)
{
size_ = size;
key_arr_ = new Key[size_];
value_arr_ = new Value[size_]();
}

template <typename Key, typename Value>
LinearProbingHashST<Key, Value>::
LinearProbingHashST(size_t size)
{
LinearProbingHashST_(size);
}

template <typename Key, typename Value>
LinearProbingHashST<Key, Value>::
LinearProbingHashST()
{
LinearProbingHashST_(997);
}

template <typename Key, typename Value>
LinearProbingHashST<Key, Value>::
~LinearProbingHashST()
{
delete[] key_arr_;
delete[] value_arr_;
}

template <typename Key, typename Value>
void LinearProbingHashST<Key, Value>::
put(Key k, Value val)
{
if (pair_counts_+1 >= size_)
{
return;
}
size_t i;
for (i = gethash_(k);key_arr_[i] != k;i = (i+1) % size_)
{
if (key_arr_[i] == (Key)NULL)
{
key_arr_[i] = k;
pair_counts_++;
break;
}
}
value_arr_[i] = val;
}

template <typename Key, typename Value>
Value LinearProbingHashST<Key, Value>::
get(Key k)
{
for (size_t i = gethash_(k);key_arr_[i] != (Key) NULL;i = (i+1) % size_)
{
if (key_arr_[i] == k)
{
return value_arr_[i];
}
}
return (Value) NULL;
}

template <typename Key, typename Value>
void LinearProbingHashST<Key, Value>::
erase(Key k)
{
for (size_t i = gethash_(k);key_arr_[i] != (Key) NULL;i = (i + 1) % size_)
{
if (key_arr_[i] == k)
{
key_arr_[i] = (Key) NULL;
value_arr_[i] = (Value) NULL;
pair_counts_--;
for (size_t j = i+1;key_arr_[j] != (Key) NULL;j = (j+1) % size_)
{
Key temp_k = key_arr_[j];
Value temp_val = value_arr_[j];
key_arr_[j] = (Key) NULL;
value_arr_[j] = (Key)NULL;
pair_counts_--;
put(temp_k, temp_val);
}
break;
}
}
}

template <typename Key, typename Value>
bool LinearProbingHashST<Key, Value>::
empty()
{
return pair_counts_ == 0;
}
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

template <typename Key, typename Value>class SequentialSearchST
{
private:
//节点
struct Node
{
Key key;
Value value;
Node *next;
Node (Key k,Value v,Node *n):key(k),value(v),next(n){};
};
Node *head_ = nullptr;
Node *erase_(Node *node, Key k);
public:
SequentialSearchST() = default;
//获取对应键的键值
Value get(Key k);
//设置指定键的键值,如没有,创建一个新键
void put(Key k, Value val);
//返回符号表存的元素个数
int size();
bool empty();
void erase(Key k);
virtual ~SequentialSearchST();
};

template <typename Key, typename Value>
typename SequentialSearchST<Key, Value>::Node*
SequentialSearchST<Key,Value>::erase_(Node *node, Key k)
{
if (node == nullptr)
{
return nullptr;
}
if (node->key == k)
{
Node *next_node = node->next;
delete node;
return next_node;
}
node->next = erase_(node->next, k);
return node;
}

template <typename Key, typename Value>
Value SequentialSearchST<Key,Value>::get(Key k)
{
for (Node *it = head_; it != nullptr; it = it->next)
{
if (k == it->key)
{
return it->value;
}
}
return (Value)0;
}

template <typename Key, typename Value>
void SequentialSearchST<Key,Value>::put(Key k, Value val)
{
for (Node *it = this->head_; it!= nullptr; it = it->next)
{
if (k == it->key)
{
it->value = val ;
return;
}
}
head_ = new Node(k, val, head_);
}

template <typename Key, typename Value>
int SequentialSearchST<Key,Value>::size()
{
int count = 0;
for(Node *it = head_;it != nullptr;it = it->next)
{
count++;
}
return count;
}

template <typename Key, typename Value>
SequentialSearchST<Key,Value>::~SequentialSearchST()
{
Node *it = head_;
while (it != nullptr)
{
Node *del_node = it;
it = it->next;
delete del_node;
}
}

template <typename Key, typename Value>
bool SequentialSearchST<Key,Value>::empty()
{
return head_ == nullptr;
}

template <typename Key, typename Value>
void SequentialSearchST<Key,Value>::erase(Key k)
{
if (head_ == nullptr)
{
return;
}
head_ = erase_(head_, k);
}