用Java撸一下LFU简易版本

最近在别人文章看到一片关于LFU的题目,个人觉得实现可以不同,他用的c,那我用java写一下,毕竟c已经忘的差不多了。
那个题就是让我们来设计一个数据结构,使其能够按照key-value的方式来实现put和get,而且要满足LFU,而且在空间满了之后,又要满足最近最少使用即LRU,且操作时间复杂度要满足O(1)

首先看看什么是LFU

LFU(Leatest Frequently Used)即访问频次最少被使用。当空间满了时,或者达到了阙值,那么不应该在缓存里面的数据应该是使用时间点最久远的,而且使用次数最少的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里,为了满足一个操作时间复杂度,由get和put很容易就想到java里面有HashMap,这个很醒脑。
那么数据结构该怎么设计。
在这里插入图片描述
就这样,直接的数据缓存采用HashMap,所有的直接数据存这里。
但是还有就是关于频次的问题,采用的是一个双向链表+HashSet,但是底层又采用了HashMap。至于到底怎么回事,去看看我的文章看看他们怎么回事👍
为什么这么设计,HashMap不用管,大家都清楚,关键是这个LinkedHashSet怎么搞的。它是按照频次1,2,3,4,5…去存在HashSet里面的,然后每个频次会对应着LinkedHashSet来存储当前频次的所有节点,而且这个LinkedHashSet非常有意思,能够保持插入的顺序和读取的顺序是一样的,也就是说,如果越靠前说明是越早插入的。那么呢如果一个节点被访问了,或者被重置了,那么就将它放到频次高的,然后插入到频次高的后面,这样不仅频次高了,而且还能够满足它是频次高的且最近使用的。
总而言之,我们如果不看频次的Set,只看某一竖下来的双向链表,那么越在上面的是最久远的,要remove也是remove它。
那么来看看版本1的具体实现吧。
首先Node类,每个版本都一样的哦。
Node类:

class Node {
	    int key;//键
	    int value;//值
	    int freq = 1;//频率
		//构造方法
	    public Node() {}
	    //构造方法
	    public Node(int key, int value) {
	        this.key = key;
	        this.value = value;
	    }
	}

版本1

class LFUCache1 {
		Map<Integer, Node> cacheMap;//存储缓存的内容
		 Map<Integer,LinkedHashSet<Node>> freqMap; //存储每个频次对应的双向链表
		 int size;
		 int capacity;
		 int min;//存储最小频次
		 
	    LFUCache1(int capacity) {
	        cacheMap = new HashMap<Integer, p7.Node>(capacity);
	        freqMap = new HashMap<Integer, LinkedHashSet<Node>>();
	        this.capacity = capacity;
	    }
	    
	    public int get(int key) {
	        Node node = cacheMap.get(key);
	        if(node == null) {
	        	return -1;
	        }
	        freqInc(node);//增加一次频次
	        return node.value;
	    }
	    
	    public void put(int key, int value) {
	        if(capacity == 0) {
	        	return ;
	        }
	        Node node = cacheMap.get(key);
	        if(node != null) {
	        	//已经存在了就替换掉,且增加一次频次
	        	node.value = value;
	        	freqInc(node);
	        }else {
	        	if(size == capacity) {
	        		//容量到达极限,移除
	        		Node deadNode = removeNode();
	        		cacheMap.remove(deadNode.key);
	        		size--;
	        	}
	        	Node newnodeNode = new Node(key,value);
	        	cacheMap.put(key, newnodeNode);
	        	//没存在过,说明要增加最开始频率为1
	        	addNode(newnodeNode);
	        	size++;
	        }
	    }
	    void freqInc(Node node) {
	    	//从原来对应的链表里面一处,并更新min;
	    	//获取这个节点的频率
	    	int freq = node.freq;
	    	//获得当前节点频率找到对应频率的HashMap里面的LinkedHashSet
	    	LinkedHashSet<Node> set = freqMap.get(freq);
	    	//移除掉LinkedHashSet里面的这个元素
	    	set.remove(node);
	    	if(freq == min && set.size() == 0) {
	    		min = freq +1;
	    	}
	    	//加入新freq对应的链表
	    	node.freq++;
	    	LinkedHashSet<Node> newSet = freqMap.get(freq+1);
	    	if(newSet == null) {
	    		newSet = new LinkedHashSet<>();
	    		freqMap.put(freq+1, newSet);
	    	}
	    	newSet.add(node);
	    }
	    
	    void addNode(Node node) {
	    	LinkedHashSet<Node> set = freqMap.get(1);
	    	if( set == null) {
	    		set = new LinkedHashSet<p7.Node>();
	    		freqMap.put(1, set);
	    		
	    	}
	    	set.add(node);
	    	min = 1;
	    }
	    Node removeNode() {
	    	LinkedHashSet<Node> set = freqMap.get(min);
	    	Node deadNode = set.iterator().next();
	    	set.remove(deadNode);
	    	return deadNode;
	    }
	}

版本2

Node类

class Node {
	    int key;
	    int value;
	    int freq = 1;
	    Node preNode;
	    Node postNode;

	    public Node() {}
	    
	    public Node(int key, int value) {
	        this.key = key;
	        this.value = value;
	    }
	}

这个版本没有采用LinkedHashSet,而是自定义了一个双向链表

class SelfLinkedList{
		Node headNode;
		Node tailNode;
		public SelfLinkedList() {
			headNode = new Node();
			tailNode = new Node();
			headNode.postNode = tailNode;
			tailNode.preNode = headNode;
		}
		void removeNode (Node node) {
			node.preNode.postNode = node.postNode;
			node.postNode.preNode = node.preNode;
		}
		void addNode (Node node) {
			node.postNode = headNode.postNode;
			headNode.postNode.preNode = node;
			headNode.postNode = node;
			node.preNode = headNode;
		}
	}
class LFUCache2 {
		Map<Integer, Node> cacheMap;//存储缓存内容
		Map<Integer,SelfLinkedList> freMap;
		int size;
		int capacity;
		int min; //存储当前最小频次
	    public LFUCache2(int capacity) {
	        cacheMap = new HashMap<> (capacity);
	        freMap = new HashMap<>();
	        this.capacity = capacity;
	    }
	    
	    public int get(int key) {
	        Node node = cacheMap.get(key);
	        if(node == null) {
	        	return -1;
	        }
	        freqInc(node);
	        return node.value;
	    }
	    public void put(int key, int value) {
	    	if(capacity == 0) {
	    		return;
	    	}
	    	Node node = cacheMap.get(key);
	    	if(node != null) {
	    		node.value = value;
	    		freqInc(node);
	    	}else {
	    		if(size == capacity) {
	    			SelfLinkedList minFreqLinkedList = freMap.get(min);
	    			cacheMap.remove(minFreqLinkedList.tail.pre.key);
	    			minFreqLinkedList.removeNode(minFreqLinkedList.tail.pre);
	    			size--;
	    		}
	    		Node newNode = new Node(key,value);
	    		cacheMap.put(key, newNode);
	    		SelfLinkedList linkedList = freMap.get(1);
	    		if(linkedList == null) {
	    			linkedList = new SelfLinkedList();
	    			freMap.put(1,linkedList);
	    		}
	    		linkedList.addNode(newNode);
	    		size++;
	    		min = 1;
	    	}
	    }
	    
	    void freqInc(Node node) {
	    	//先从原链表删除,然后加入频次更高的链表里面
	    	int freq = node.freq;
	    	SelfLinkedList linkedList = freMap.get(freq);
	    	linkedList.removeNode(node);
	    	if(freq == min && linkedList.headNode.postNode == linkedList.tailNode) {
	    		min = freq + 1;
	    	}
	    	node.freq++;
	    	linkedList = freMap.get(freq+1);
	    	if(linkedList == null) {
	    		linkedList = new SelfLinkedList();
	    		freMap.put(freq+1, linkedList);
	    	}
	    	linkedList.addNode(node);
	    }
	   
	}

版本3

LFU(Least Frequently Used)是一种缓存淘汰算法,可以用于对缓存中的数据进行淘汰。下面是 Java 手动实现 LFU 的示例代码: ``` import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; public class LFUCache<K, V> { private Map<K, V> cacheMap; // 存放缓存数据 private Map<K, Integer> freqMap; // 存放缓存数据的访问频率 private Map<Integer, LinkedHashSet<K>> freqKeysMap; // 存放访问频率相同的缓存数据 private int capacity; // 缓存容量 private int minFreq; // 缓存数据的最小访问频率 public LFUCache(int capacity) { this.cacheMap = new HashMap<>(); this.freqMap = new HashMap<>(); this.freqKeysMap = new HashMap<>(); this.capacity = capacity; this.minFreq = 0; } public V get(K key) { V value = cacheMap.get(key); if (value == null) { return null; } // 更新缓存数据的访问频率 int freq = freqMap.get(key) + 1; freqMap.put(key, freq); // 更新访问频率相同的缓存数据 freqKeysMap.get(freq - 1).remove(key); if (freqKeysMap.get(freq - 1).isEmpty()) { freqKeysMap.remove(freq - 1); if (minFreq == freq - 1) { minFreq++; } } freqKeysMap.computeIfAbsent(freq, k -> new LinkedHashSet<>()).add(key); return value; } public void put(K key, V value) { if (capacity <= 0) { return; } // 缓存数据已存在,更新其值和访问频率 if (cacheMap.containsKey(key)) { cacheMap.put(key, value); get(key); return; } // 缓存容量已满,淘汰访问频率最小的缓存数据 if (cacheMap.size() >= capacity) { K minFreqKey = freqKeysMap.get(minFreq).iterator().next(); freqKeysMap.get(minFreq).remove(minFreqKey); if (freqKeysMap.get(minFreq).isEmpty()) { freqKeysMap.remove(minFreq); } cacheMap.remove(minFreqKey); freqMap.remove(minFreqKey); } // 添加新的缓存数据 cacheMap.put(key, value); freqMap.put(key, 1); freqKeysMap.computeIfAbsent(1, k -> new LinkedHashSet<>()).add(key); minFreq = 1; } } ``` 这个示例代码中,LFUCache 类实现了 LFU 缓存淘汰算法。在 get 方法中,缓存数据的访问频率会加 1,同时更新访问频率相同的缓存数据;在 put 方法中,如果缓存容量已满,则会淘汰访问频率最小的缓存数据,然后添加新的缓存数据。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小满锅lock

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值