前言
本文依赖链表实现,由于js没有自带的链表,所以先要用js实现链表,可以加强对链表数据结构的理解。
js实现链表
一个数据结构基本的功能就是增删改查,我们的链表也要实现这些功能。
首先我们来复习一下链表的数据结构。
链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的“结点”。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。如图所示,我们把这个记录下个结点地址的指针叫作后继指针 next。
从下图中,你应该可以发现,其中有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。我们习惯性地把第一个结点叫作头结点,把最后一个结点叫作尾结点。其中,头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是链表上最后一个结点。
根据以上描述,我们来实现我们的基础类Node,(新建Node.js文件)
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
module.exports = Node
然后新建链表类 SingList,先把用到的方法列出来,接下来实现这些方法。(新建SingList.js文件)
const Node = require('./Node')
class SingleList {
find() {}
findLast() {}
findPre() {}
append() {}
insert() {}
insertByHead(){}
display() {}
remove() {}
getLength(){}
}
find用于查找传入的节点
find(item) {
let current = this.head
while (current && current.data !== item) {
current = current.next
}
return current
}
findLast用于查找最后一个节点
findLast() {
let current = this.head
while (current && current.next !== null) {
current = current.next
}
return current
}
findPre用于查找当前节点的前一个节点
findPre(item) {
let current = this.head
let preNode = null
while (current && current.next !== null) {
if (current.next.data === item) {
preNode = current
break
}
current = current.next
}
return preNode
}
append用于新增节点
append(item) {
let current = this.findLast()
current.next = new Node(item)
this.size++
}
insert用于在某个节点之后插入节点
insert(item, element) {
let current = this.find(element)
let newCur = new Node(item)
if (!current) {
return
}
let nextCur = current
newCur.next = current.next
nextCur.next = newCur
this.size++
}
insertByHead用于在哨兵节点之后插入节点
insertByHead(item){
let newNode=new Node(item)
let current=this.head
if(this.size==0){
current.next=newNode
}else{
newNode.next=current.next
current.next=newNode
}
this.size++
}
display用于打印节点
display() {
let result = ''
let current = this.head
while (current && current.next !== null) {
current = current.next
result += current.data + '->'
}
console.log(result)
}
remove移除节点
remove(item) {
let preNode = this.findPre(item)
let current = this.find(item)
if (!preNode) return null
preNode.next = current.next
this.size--
}
getLength获取链表的长度
getLength(){
return this.size
}
我们来测试一下我们的链表
let myList = new SingleList();
let arr = [3, 4, 5, 6, 7, 8, 9];
for(let i=0; i<arr.length; i++){
myList.append(arr[i]);
}
myList.display(); // head->3->4->5->6->7->8->9
console.log(myList.find(4)); // Node {data: 4, prev: null, next: Node}
myList.insert(9, 9.1);
myList.insert(3, 3.1);
myList.display(); // head->3->3.1->4->5->6->7->8->9->9.1
myList.remove(9.1);
myList.remove(3);
myList.display(); // head->3.1->4->5->6->7->8->9
console.log(myList.findLast()); // Node {data: 9, prev: null, next: null}
console.log(myList.getLength()); // 7
代码测试没啥问题,接着来实现我们的LRUCache类,由于基本方法已经在链表里面实现了,我们这里只需要对链表继承,新建lru.js,
js实现LRU
LRU:最近最少使用。有两个关键点,一个是最近,一个是最少。
LRUCache
const SingleList = require('./SingleList')
// 常见的策略有三种:先进先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。
class LRUCache extends SingleList{
constructor(size) {
super();
this.maxSize=size
}
add(item){
let size=super.getLength()
let current=super.find(item)
if(current){
let preNode=super.findPre(item)
if(preNode.data!='head'){
super.remove(item)
super.insertByHead(item)
}
}else{
if(size>=this.maxSize){
super.remove(super.findLast().data)
}
super.insertByHead(item)
}
}
}
module.exports = LRUCache
让我们来测试一下我们的代码
const LRUCatch=require('./Lru')
let lru=new LRUCatch(3)
lru.add(1)
lru.display() //1->
lru.add(2)
lru.display() //2->1->
lru.add(3)
lru.display() //3->2->1->
lru.add(1)
lru.display() //1->3->2->
lru.add(2)
lru.display() //2->1->3->