C++实现LRU算法

Python016

C++实现LRU算法,第1张

#include<iostream>

using namespace std

int size

int *w

//定义一个动态数组

struct mem

{

int num

int count

}memBlock[3]={0,0,0,0,0,0}

void LRU()

{

for( int i = 0i <sizei++ )

{

int maxCount = memBlock[0].count

int maxPos = 0

int j = 0

bool bFind = false

for( j = 0j <3j++ )

{

// 标记出count值最大的位置

if( maxCount <memBlock[j].count )

{

maxCount = memBlock[j].count

maxPos = j

}

// 将所有的count值都+1

memBlock[j].count++

// 如果命中,将其count值置为0

if( w[i] == memBlock[j].num )

{

memBlock[j].count = 0

bFind = true

}

}

// 未命中,将count最大的拿来替换

if( !bFind )

{

memBlock[maxPos].num = w[i]

memBlock[maxPos].count = 0

}

for(j = 0j <3j++) //输出

cout <<memBlock[j].num <<" "

cout <<" "<<endl

}

}

int main() //主函数

{

cout<<"请输入需访问的页面数量:"<<endl

cin>>size

w = new int[size]

cout<<"请输入需要访问的页面"<<endl

for(int a=0a<sizea++)

{

cin>>w[a]//输入数组

}

cout<<endl<<"(LRU)"<<endl

LRU()

return 0

}

引用 : 回答者: floxer | 三级

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

#define MAINMEM 3//主存可存储的页面个数

#define QUEUEMAXLEN 30 //队列最大长度

struct LRUQueue //定义一个特殊的队列

{

int number

int data[MAINMEM]

}lru

void Init()//初始化操作

void Add(int pageID) //加入队列

int Find(int pageID) //查找页面是否在主存中

void Update(int pos) //更新页所在的地址

void PrintQueue() //输出主存内容

int main()

{

int i

int queuelength

int queue[QUEUEMAXLEN]

Init() //初始化操作

printf("请输入队列的长度:")

scanf("%d",&queuelength)

printf("请以此输入队列的页号:")

for(i=0i<queuelengthi++)

{

scanf("%d",&queue[i])

Add(queue[i])

PrintQueue()

}

system("pause")

return 0

}

void Init()

{

lru.number = 0

int i

for(i = 0i<MAINMEMi++)

{

lru.data[i] = -1

}

}

int Find(int pageID)

{

int i

for (i=0i<MAINMEMi++)

{

if(lru.data[i] == pageID)

{

return i

}

}

return 0

}

void Update(int pos)

{

int tmp = lru.data[pos]

int i

for(i = posi<lru.number++i)

{

lru.data[i] = lru.data[i+1]

}

lru.data[lru.number-1] = tmp //将被访问的页放在最后一位,其他的页前移,因为置换时删掉的是主存中第一块放置的页面

}

void PrintQueue()

{

printf("当前内存中的页号:\n")

int i

putch('|')

for(i = 0i<MAINMEMi++)

{

if(lru.data[i] == -1)

printf(" |")

else

printf("%d |",lru.data[i])

}

putch('\n')

}

void Add(int pageID)

{

printf("当前访问的页面:%d\n",pageID)

if(lru.number<MAINMEM) //如果主存没有填满

{

int pos = Find(pageID)

if(pos)

{

Update(pos)

}

else

{

lru.data[lru.number++]=pageID

}

}

else

{

int pos = Find(pageID)

if(pos)

{

Update(pos)

}

else

{

int i

for(i=0i<MAINMEM-1i++)

{

lru.data[i]=lru.data[i+1] //数据左移一个单位

}

lru.data[i]=pageID//加入新数据

}

}

}

LRU是Least Recently Used的缩写,即最近最少使用算法,应用面非常的广泛,比如redis当中的内存淘汰策略。因为计算机的内存都是有限的,当用户访问的数据如果存在内存当中直接返回的给用户的话,效率会非常快,但是如果内存不存在,在去磁盘里面查找的,效率会大打折扣。所以我们希望在有限的内存空间当中,多存放点热点数据,用户不经常访问的数据,尽量淘汰掉,避免占用内存空间。

使用双向链表来实现LRU 这篇文章已经用双向链表来实现过LRU算法了,但是基于双向链表的特性,使得该算法的时间复杂度为O(n),显然不是最优的算法,那么有没有算法,可以达到O(1),当然是有的,早早的计算机科学家已经想到,并且已经实现了。

在笔者介绍接下来的内容时,还是希望先了解一下两篇博文:

一、 图解HashMap原理

二、 图解LinkedHashMap

之前使用双向链表去实现LRU算法时,时间复杂度没有达到O(1),主要原因在于遍历结点时,带来的时间开销,那么换句话说,要实现遍历结点时,时间复杂度为O(1),第一时间想到的应该是hash数组,但是hash算法可能会存在不同的key值,产生相同的hash值,那么可以将不同key,但是相同hash值的结点,以单链表的形式存放。这样仅仅是实现了存取时间复杂度为O(1),如何实现数据能够按访问顺序存放呢?并且增删的时间复杂度为O(1),这个可以使用双向链表来实现,所以综合来讲,就是实现散列数组+双向链表来使用LRU,可以达到时间复杂度为O(1)。

逻辑视图如下:

咋一看这个图乱的很,稍微解释一下,如果感觉理解上有点困难,可以先去了解一下之前推荐的两篇博文,那里会介绍的更加详细一点。

1.最左侧其实就是一个普通的数组,数组的大小必须是2的倍数,这个原因是什么呢?因为这个数组的存放方式是散列的,意思就是需要key.hashcode & (length -1)才能得出存放位置的方式,hash的好处是可以根据key值,在时间复杂度为O(1)的前提找到对应的存放位置,这也是我们的初衷,说到这里其实还没有解释为什么一定要是2的倍数,因为2的倍数-1,这个数的二进制,一定全是1,比如16-1=15,二进制表示就是1111,&运算符其实就是将值全部化成二进制逐位与,比如10111011 &1111 = 1011 = 11,但是如果不是2的倍数,比如7-1=6,化成二进制就是0110,由于末位是0,不管什么二进制值与0110做&运算,一定是偶数,这样会导致散列分布不均匀。

2.不同key值,相同hash值,如何存放呢?相同的hash值做与运算一定能够得到相同的数组脚标,这些结点,以单链表的形式存在,就是图中数组右侧的单链表。

3.如何实现按访问顺序?上图除去数组和挂在数组右侧的单链表,还有绿色和黄色的单向箭头,在右上角还有一个双向链表的头指针。其实这些箭头就是维护不同结点的访问顺序,即双向链表。

总上所述,这种数据结构定义的结构体如下:

class Node{

Object key//存放key值,用于计算存放数组脚标位置

Object value//存放元素值

int hash//存放key.hashcode

Node next//维护单链表顺序

Node before//维护双向链表顺序

Node after

}

笔者用java实现如下,感兴趣可以用自己喜欢的语言去实现一遍,加深理解:

其实以上实现底层原理就是LinkedHashMap的实现原理,只不过笔者做了一些简化,去掉了繁琐的继承,扩容等,突出了算法核心,如果读者感兴趣也可以去研究一下LinkedHashMap的源码。

使用LinkedHashMap来实现LRU算法:

看起来是不是简单了很多,因为LinkedHashMap底层已经封装好了,我们直接调用就好,但是作为一个想要变优秀的码农,一定要知其然知其所以然。

使用散列+双向链表的方式是如何实现O(1)复杂度的?在实现LRU算法过程中,无非两种操作,查找和修改,使用散列数组实现查找时间复杂度为O(1),使用双向链表实现修改复杂度为O(1),并且双向链表还可以维护访问顺序,所以使用这种方式,可以达到O(1)。