最近复习操作系统,想到了两个常用的页面置换算法,但之前一种没实现过,想想应如何实现。
FIFO(先进先出页面置换)
FIFO最易理解,也易于实现,但在应用中,其缺页率比较高。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
int a[maxn];
int d[maxn]; //散列便于查询
int main()
{
int m,n,x,c=0;
printf("输入物理块数和页面数:\n");
scanf("%d %d",&m,&n);
for(int i=0;i<m;i++){
a[i]=1004;
}
memset(d,0,sizeof(d));
for(int i=0;i<n;i++){
scanf("%d",&x);
if(d[x]==0){ //如果物理块中没有
d[a[c%m]]=0; //循环队列中将尾指针所指的元素删除
a[c%m]=x; //插入到尾指针
d[x]=1;
c++; //尾指针进行自增
}
}
printf("缺页数:%d\n",c);
return 0;
}
实测效果:
LRU(最近最久未使用算法)
顾名思义,该算法每次将内存中最久未使用过的内存页面换到存中,通常情况下,缺页率比FIFO低,所以更优。但这里仅提高了单链表的实现方法,由于每次查询页面是否存在内存时,都需要进行遍历,时间复杂度为O(n),效率比较低。如何使用哈希链表实现时间复杂度为O(1),需要再去学习一下(本人太菜,暂时无能为力)。
/*单链表实现LRU*/
#include <bits/stdc++.h>
using namespace std;
typedef int ElemType; //链表的数据类型
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
int CreateList(LinkList &L,int n){ //&L为引用变量
L = (LinkList)malloc(sizeof(LNode)); //分配空间
L->data = n;
L->next = NULL; //头指针为空
for(int i=n;i>0;--i){
LinkList p; //LinkList p 相当于 LNode *p
p = (LinkList)malloc(sizeof(LNode));
p->data = -1; //初始化时将链表值置为-1;
p->next = L->next; //后插法
L->next = p;
}
return 0;
}
int ListInsert(LinkList &L,int i,ElemType e){ //i为插入的位置
LinkList p = L;
LinkList s;
int j = 0;
while(p&&j<i-1){
p = p->next;
++j;
}
if(!p || j>i-1){
return 0;
}
s = (LinkList)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return 1;
}
int ListDelet(LinkList &L,int i){ //删除第i位置上的元素
LinkList p = L;int j = 0;
while(p->next && j<i-1) {
p = p->next;++j;
}
if(!(p->next) || j>i-1)return 0;
LinkList q = L;
q = p->next;
p->next = q->next;
free(q);
return 1;
}
int List_search(LinkList L,ElemType e){
LinkList p;
p = L->next;
int j=1;
while(p != NULL){
if(p->data == e){
return j; //有该元素
break;
}
else{
p = p->next;
j++;
}
}
return 0; //没有该元素
}
int main(){
int m,n,x;
int c = 0;
LinkList L;
printf("输入物理块数和页面数:\n");
scanf("%d %d",&m,&n); //m为内存空间,n为元素的个数
CreateList(L,m);
for(int i=0;i<n;i++){
scanf("%d",&x);
int p = List_search(L,x);
if(p>0){ //找到该点要更新
ListDelet(L,p);
ListInsert(L,1,x);
}
else{ //没找到该点要将该点插入头节点,然后删除尾节点
ListDelet(L,m);
ListInsert(L,1,x);
c++;
}
}
printf("缺页数:%d\n",c);
return 0;
}
实现效果(通常情况下缺页率比FIFO更低):
要求对容器的访问、加入新元素、修改元素的时间复杂度都为O(1)。需要使用哈希配合双链表实现。假设缓存块为页面,哈希表存放关键字key对应的页面节点。使用head指针指向链表开头、tail指针指向结尾,双链表可以让链表刷新时找到前后页面。当访问或修改某个关键字对应页面时,将该页面放置到链表末尾,然后更新tail指针。当加入新关键字时,若当前链表长度达到容量上限,移除head指针指向元素,然后更新head指针即可。所以无论访问、修改或添加元素,时间复杂的都为O(1)。
在实现上,可以在链表的头尾分别加上 虚拟的头尾指针,避免移除head节点或添加tail节点时需要单独考虑。空间复杂度方面,双向链表最大节点量为capacity + 2,哈希表最大节点量为capacity,所以空间复杂度为O(capacity)。
// 内存节点
public class cacheNode
{
public var index:Int
public var value:Int
public var next:cacheNode?
public var pre:cacheNode?
init(_ id_:Int, _ value_: Int)
{
index = id_
value = value_
next = nil
pre = nil
}
}
// LRU算法
class LRUCache {
private var head:cacheNode?
private var map:Dictionary<Int, cacheNode>
private var capacity_:Int
private var count:Int
private var tail:cacheNode?
init(_ capacity: Int) {
capacity_ = capacity
head = cacheNode(-1, -1)
head?.next = cacheNode(-2, -1)
tail = head?.next
tail?.pre = head
map = Dictionary<Int, cacheNode>()
count = 0
}
private func delete()
{
map.removeValue(forKey: head!.next!.index)
head!.next!.next?.pre = head
head!.next = head!.next!.next
count -= 1
}
private func refresh(_ key:Int)
{
let cur = map[key]
if cur?.next === tail
{
return
}
cur?.pre?.next = cur?.next
cur?.next?.pre = cur?.pre
cur?.pre = tail?.pre
tail?.pre?.next = cur
tail?.pre = cur
cur?.next = tail
map[key] = tail?.pre
}
func get(_ key: Int) -> Int {
if map.keys.contains(key) == true
{
refresh(key)
}
return map[key]?.value ?? -1
}
func put(_ key: Int, _ value: Int) {
if map.keys.contains(key) == true
{
map[key]?.value = value
refresh(key)
}
else
{
count += 1
if count > capacity_
{
delete()
}
let tmp:cacheNode? = tail?.pre
tmp?.next = cacheNode(key, value)
tmp?.next?.pre = tmp
tail?.pre = tmp?.next
tmp?.next?.next = tail
map[key] = tail?.pre
}
}
}