Skiplist 和LRU Cache

文章介绍了Skiplist作为数据库中用于快速查找的数据结构,强调其简洁的实现和LogN的查找效率,并对比了与其他数据结构如红黑树的区别。此外,文章还探讨了LRUCache缓存淘汰策略,利用双链表和哈希表实现高效查找和更新。
摘要由CSDN通过智能技术生成

Skiplist 和LRU Cache

由于本人最近在看redis, 其实很多DB都使用了跳表和LRU, 跳表目的是为了Logn时间内查找,添加删除节点,相对于红黑树,AVL,哈希等,跳表最大的优势就是实现简单,代码短,你只需要花费白天时间就可以自己写出简单的跳表。

跳表的图像是这样的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DGKKFnmC-1677074997650)(C:\Users\41988\Desktop\image-20230222214432980.png)]

每个节点都有一个next数组,next[i]表示在当前节点在i的next是谁,这句话仔细看几遍,因为我第一次完全不知道在说啥。

redis里面的层数是32层,日常生活一般8层10层就够了。这个层数是非常关键的,我们知道0层,也就是最下面的层数,是所有的节点,也就是n个,但是上面一层,严格保证了期望值达到 n / 2个节点,符合正态分布,具体的证明在网上是一篇英文的论文,我就不提了,比较理论。

复杂度为什么是logN级别是因为我们每一层的期望跳步是O1级, 因为顶层的节点非常少,一次就可以跳一半,下一层又是一半,其实和二分查找是类似的,只不过我们用链表实现了二分查找。

力扣这道题目比较简单,和现实的跳表距离很大,但是逻辑框架相差无二,我们设计了一个辅助函数find,为了找到对应的前驱节点数组,方便我们查找,插入和删除。

1206. 设计跳表 - 力扣(LeetCode)

class Skiplist {
public:
    static const int level = 8;

    struct Node {
        int val;
        vector<Node*> next;
        Node(int value) 
        {
            val = value;
            next.resize(level, nullptr);
        }
    }*head;
    Skiplist() {
        head = new Node(-1);
    }
    ~Skiplist() {
        delete head; 
    }
    
   void find(int target, vector<Node*> &pre) {
        Node *p = head;
        for (int i = level - 1; i >= 0; i--) {
            while (p->next[i] && p->next[i]->val < target)
                p = p->next[i];

            pre[i] = p;
        }
    }
    bool search(int target) {
        vector<Node*> pre(level);
        find(target, pre);

        auto p = pre[0]->next[0];
        return p && p->val == target;
    }
    
    void add(int num) {
        vector<Node*> pre(level);
        find(num, pre);

        auto newnode = new Node(num);

        for (int i = 0; i < level; i ++) {
            newnode->next[i] = pre[i]->next[i]; //这里其实和单链表的插入相似
            pre[i]->next[i] = newnode;
            if (rand() % 2) break;  //这一步其实严格应该调用正态分布函数, 但是为了简化
        }
    }
    
    bool erase(int num) {
        vector<Node*> pre(level);
        find(num, pre);

        auto p = pre[0]->next[0];
        if (!p || p->val != num) return false;
        for (int i = 0; i < level && pre[i]->next[i] == p; i ++) {
            pre[i]->next[i] = p->next[i]; //单链表的删除,但是在每层都删除
        }
        delete p;
        return true;
    }

};

LRU

LRU(least recently used)是一种缓存置换算法, 其意义在于为我们设置了一种键淘汰策略,这里的键你不需要知道是啥,如果你感兴趣请读redis相关的书籍。我简要说明一下,LRU的目的是有新的数据进来后,我们由于有限的内存,需要淘汰一些缓存在内存上的数据,怎么选择数据是他的设计目的,我们只要搞懂他是怎么淘汰旧数据的即可。 LRU的做法是删除最少用的一个数据,很符合直觉的行为。

比如下面这个图片,我们放了2进来,假设此时内存满,我们应该淘汰1.

这里面有几个问题需要考虑 :: 我们怎么知道谁是最古老的数据?? 答案是我们用一个双链表,不停的头插,这样满了只要尾删除就可。 第二个问题是, 假设我们要查找某数据,如何做到快并且可以移动到头部? LRU的查找较为特殊, 每次查找会把找到的数据放置于链表头,这样叫做更新时间戳。 答案是我们需要一个哈希, 在O1时间找到对应的数据。为了方便,我们设计了两个辅助函数,删除和插入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vQsyniYv-1677074997651)(C:\Users\41988\Desktop\image-20230222215656145.png)]

146. LRU 缓存 - 力扣(LeetCode)

class LRUCache {
public:
    struct Node {
        int key, val;
        Node *left, *right;
        Node(int _key, int _val): key(_key), val(_val), left(NULL), right(NULL) {}
    }*L, *R;
    unordered_map<int, Node*> hash;
    int n;

    void remove(Node* p) {
        p->right->left = p->left;
        p->left->right = p->right;
    }

    void insert(Node* p) {
        p->right = L->right;
        p->left = L;
        L->right->left = p;
        L->right = p;
    }

    LRUCache(int capacity) {
        n = capacity;
        L = new Node(-1, -1), R = new Node(-1, -1);
        L->right = R, R->left = L;
    }

    int get(int key) {
        if (hash.count(key) == 0) return -1;
        auto p = hash[key];
        remove(p);
        insert(p);
        return p->val;
    }

    void put(int key, int value) {
        if (hash.count(key)) {
            auto p = hash[key];
            p->val = value;
            remove(p);
            insert(p);
        } else {
            if (hash.size() == n) {
                auto p = R->left;
                remove(p);
                hash.erase(p->key);
                delete p;
            }
            auto p = new Node(key, value);
            hash[key] = p;
            insert(p);
        }
    }
};

rase(p->key);
delete p;
}
auto p = new Node(key, value);
hash[key] = p;
insert§;
}
}
};




  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值