データ構造とアルゴリズム インタビューの質問: ハッシュ テーブルを実装し、ハッシュ衝突の解決策を検討します。
概要: ハッシュ テーブルを実装し、ハッシュ衝突の解決策を検討します。
アルゴリズムのアイデア
ハッシュ テーブル (ハッシュ テーブル、ハッシュ テーブルとも呼ばれます) は、高速な挿入と検索速度を備えたデータ構造であり、データを迅速に検索して挿入する必要がある一部のアプリケーションに適しています。ハッシュ衝突に対する一般的な解決策には、線形検出とチェーン アドレス指定が含まれます。
- 線形検出: ハッシュ衝突が発生した場合、挿入される要素は次の空きスロットに配置されます。次の位置がすでに占有されている場合は、最初の空きスロットが見つかるまで逆方向に検索されます。
- チェーンアドレス方式: ハッシュ競合が発生した場合、その位置にあるすべての以前の要素はリンクリストに保存され、新しい要素を挿入する場合はリンクリストの最後に追加するだけです。
以下は、C++ でハッシュ テーブルを実装するコードと詳細なコメントです。
#include <iostream>
#include <vector>
using namespace std;
// 哈希表节点
struct Node {
int key;
int val;
Node* next;
Node(int k, int v) : key(k), val(v), next(nullptr) {}
};
class MyHashMap {
private:
vector<Node*> data; // 存储哈希表节点的数据数组
int size; // 当前数据个数
int cap; // 数组容量
double loadFactor; // 负载因子
// 获取某个数字的哈希值
int hashCode(int key) {
return key % cap;
}
public:
// 构造函数
MyHashMap() : size(0), cap(1000) {
data.resize(cap, nullptr);
loadFactor = 0.75;
}
// 插入或更新数据
void put(int key, int value) {
int idx = hashCode(key); // 获取插入位置的哈希值
// 检测是否已有该元素
Node* node = data[idx];
while (node != nullptr) {
if (node->key == key) {
node->val = value;
return;
}
node = node->next;
}
// 如果容量满了,进行扩容
if (size >= loadFactor * cap) {
vector<Node*> old_data = data;
cap *= 2;
data.clear();
data.resize(cap, nullptr);
size = 0;
for (int i = 0; i < old_data.size(); ++i) {
Node* node = old_data[i];
while (node != nullptr) {
put(node->key, node->val);
node = node->next;
}
}
}
// 在指定位置插入新元素
Node* new_node = new Node(key, value);
new_node->next = data[idx];
data[idx] = new_node;
++size;
}
// 获取某个键对应的值,不存在则返回-1
int get(int key) {
int idx = hashCode(key);
Node* node = data[idx];
while (node != nullptr) {
if (node->key == key) {
return node->val;
}
node = node->next;
}
return -1;
}
// 删除某个元素
void remove(int key) {
int idx = hashCode(key);
Node* node = data[idx];
if (node == nullptr) return;
// 特殊处理头节点的情况
if (node->key == key) {
data[idx] = node->next;
delete node;
--size;
return;
}
// 删除中间或尾部位置上的元素
while (node->next != nullptr) {
if (node->next->key == key) {
Node* del_node = node->next;
node->next = del_node->next;
delete del_node;
--size;
return;
}
node = node->next;
}
}
};
int main() {
MyHashMap myHashMap;
myHashMap.put(1, 1);
myHashMap.put(2, 2);
cout << myHashMap.get(1) << endl; // 1
cout << myHashMap.get(3) << endl; // -1
myHashMap.put(2, 1);
cout << myHashMap.get(2) << endl; // 1
myHashMap.remove(2);
cout << myHashMap.get(2) << endl; // -1
return 0;
}
この実装では、vector<Node*>
配列を使用してハッシュ テーブルにノードを格納します。これは、Node
リンク リスト ノードの構造です。ハッシュ テーブルのコンストラクターでは、デフォルトの容量を 1000 に設定し、配列サイズをこの値に初期化します。また、負荷率も規定されておりloadFactor
、現在のデータ総量が容量と負荷率の積に達すると、容量拡張が必要となります。
hashCode()
この関数は、キーに対応するハッシュ値を取得するために使用されます。挿入を行う際には、まず挿入する要素のハッシュ値を計算しidx
、その位置に要素が既に存在するかどうかを確認します。存在する場合はその値を更新し、そうでない場合はこの位置に新しい要素を挿入します。2倍を超える場合は容量拡張が必要です。
特定のキーに対応する値を取得するには、そのハッシュ値を計算し、リンクされたリスト全体を走査し、キーの値に従って要素が存在するかどうかを判断する必要もあります。見つかった場合は対応する値が返され、見つからなかった場合は -1 が返されます。
要素を削除するときも、まず削除する要素のハッシュ値を計算し、対応するリンク リストの先頭を見つけます。次に、リンクされたリストを調べて、削除する要素を探します。リンクされたリストでは、先頭、中間、末尾の 3 つの削除ケースに対処する必要があります。
ハッシュ テーブルでは多くの衝突が発生する可能性があるため、ハッシュの衝突を正しく解決することが重要です。この例では、オープン アドレス指定、特に線形プローブを使用してハッシュの衝突を解決します。
- Javaのバージョン
public class MyHashMap {
// 哈希表节点
class Node {
int key; // 键值
int val; // 数值
Node next; // 下一个节点的指针
public Node(int k, int v) {
key = k; val = v; }
}
private Node[] data; // 存储哈希表节点的数据数组
private int size; // 当前数据个数
private int cap = 1000; // 数组容量
private double loadFactor; // 负载因子
// 构造函数
public MyHashMap() {
data = new Node[cap];
loadFactor = 0.75;
}
// 获取某个数字的哈希值
private int hashCode(int key) {
return key % cap;
}
// 插入或更新数据
public void put(int key, int value) {
int idx = hashCode(key); // 获取插入位置的哈希值
// 检测是否已有该元素
Node node = data[idx];
while (node != null) {
if (node.key == key) {
node.val = value;
return;
}
node = node.next;
}
// 如果容量满了,进行扩容
if (size >= loadFactor * cap) {
Node[] old_data = data;
cap *= 2;
data = new Node[cap];
size = 0;
for (int i = 0; i < old_data.length; ++i) {
node = old_data[i];
while (node != null) {
put(node.key, node.val);
node = node.next;
}
}
}
// 在指定位置插入新元素
Node new_node = new Node(key, value);
new_node.next = data[idx];
data[idx] = new_node;
++size;
}
// 获取某个键对应的值,不存在则返回-1
public int get(int key) {
int idx = hashCode(key);
Node node = data[idx];
while (node != null) {
if (node.key == key) {
return node.val;
}
node = node.next;
}
return -1;
}
// 删除某个元素
public void remove(int key) {
int idx = hashCode(key);
Node node = data[idx];
if (node == null) return;
// 特殊处理头节点的情况
if (node.key == key) {
data[idx] = node.next;
--size;
return;
}
// 删除中间或尾部位置上的元素
while (node.next != null) {
if (node.next.key == key) {
node.next = node.next.next;
--size;
return;
}
node = node.next;
}
}
public static void main(String[] args) {
MyHashMap myHashMap = new MyHashMap();
myHashMap.put(1, 1);
myHashMap.put(2, 2);
System.out.println(myHashMap.get(1)); // 1
System.out.println(myHashMap.get(3)); // -1
myHashMap.put(2, 1);
System.out.println(myHashMap.get(2)); // 1
myHashMap.remove(2);
System.out.println(myHashMap.get(2)); // -1
}
}