📜  使用双向链表链接的哈希表(1)

📅  最后修改于: 2023-12-03 15:36:38.635000             🧑  作者: Mango

使用双向链表链接的哈希表

哈希表是一种常见的数据结构,在实际开发中经常会用到。其中一种哈希表的实现方式是使用双向链表来链接碰撞的键值对。本文将介绍如何使用双向链表链接的哈希表来实现一个简单的键值对存储结构。

哈希表的实现
哈希函数

哈希表的实现需要一个哈希函数,它能够将键值映射到一个固定大小的数组中。一个好的哈希函数应该尽可能地将键值均匀地分布到数组中,以此最大程度地减少碰撞。

存储结构

使用链表来存储键值对可以有效地解决碰撞的问题。每个链表节点都包含一个键和一个值。当哈希函数计算出某个键需要存储的位置时,就将这个键值对添加到该位置对应的链表中。

为了能够快速地在链表中查找某个键的位置,我们需要维护一个指向链表头节点的指针。当某个键需要添加到链表中时,我们可以遍历链表来查找是否已经存在该键。如果存在,就更新它的值;如果不存在,就创建一个新的节点并将其添加到链表的头部。

哈希表的操作

哈希表最常用的操作包括添加、删除和查找元素。我们可以按照以下步骤来实现这些操作:

  1. 在哈希表中查找元素。
  2. 如果元素存在,执行相应的操作;如果不存在,将其插入到哈希表中。

其中,查找元素的过程需要遍历链表,时间复杂度为 O(n),n 表示链表的长度。因此,在使用链表实现哈希表时,我们应该尽量减少链表长度,以此来提高查找效率。

代码实现

以下是一个简单的使用双向链表链接的哈希表的实现,我们使用 C++ 来实现:

#include <iostream>
#include <unordered_map>

using namespace std;

class Node {
public:
    int key;
    int value;
    Node* prev;
    Node* next;

    Node(int key, int value) {
        this->key = key;
        this->value = value;
        prev = nullptr;
        next = nullptr;
    }
};

class LRUCache {
private:
    unordered_map<int, Node*> cache;
    Node* head;
    Node* tail;
    int capacity;
    int size;

public:
    LRUCache(int capacity) {
        this->capacity = capacity;
        size = 0;
        head = new Node(-1, -1);
        tail = new Node(-1, -1);
        head->next = tail;
        tail->prev = head;
    }

    ~LRUCache() {
        Node* p = head;
        while (p) {
            Node* q = p;
            p = p->next;
            delete q;
        }

        cache.clear();
    }

    int get(int key) {
        if (cache.count(key) == 0) {
            return -1;
        }

        Node* node = cache[key];
        moveToHead(node);
        return node->value;
    }

    void put(int key, int value) {
        if (cache.count(key) == 1) {
            Node* node = cache[key];
            node->value = value;
            moveToHead(node);
        } else {
            Node* node = new Node(key, value);
            addToHead(node);
            cache[key] = node;
            size++;

            if (size > capacity) {
                Node* last = removeTail();
                cache.erase(last->key);
                delete last;
                size--;
            }
        }
    }

private:
    void moveToHead(Node* node) {
        removeNode(node);
        addToHead(node);
    }

    void addToHead(Node* node) {
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }

    void removeNode(Node* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }

    Node* removeTail() {
        Node* node = tail->prev;
        removeNode(node);
        return node;
    }
};

int main() {
    LRUCache cache(2);

    cache.put(1, 1);
    cache.put(2, 2);
    cout << cache.get(1) << '\n';   // 返回 1
    cache.put(3, 3);
    cout << cache.get(2) << '\n';   // 返回 -1
    cache.put(4, 4);
    cout << cache.get(1) << '\n';   // 返回 -1
    cout << cache.get(3) << '\n';   // 返回 3
    cout << cache.get(4) << '\n';   // 返回 4

    return 0;
}

在这个实现中,我们使用双向链表来维护键值对,使用 unordered_map 来实现哈希表。我们将头节点和尾节点分别作为哨兵节点,这样可以避免在对链表进行添加和删除操作时需要判断是否为头节点或尾节点。

总结

使用双向链表链接的哈希表是一种 很常见的数据结构,它能够有效地减少碰撞,并且支持快速随机访问。在实际开发中,我们可以根据实际需要来选择不同的哈希函数和存储结构来实现一个高效的哈希表。