内存是计算机重要的资源之一,程序运行的过程中必须对内存进行分配。
经典的内存分配过程是这样进行的:
1、 内存以内存单元为基本单位,每个内存单元用一个固定的整数作为标识,称为地址。地址从 0 开始连续排列,地址相邻的内存单元被认为是逻辑上连续的。我们把从地址 i 开始的 s 个连续的内存单元称为首地址为 i 长度为 s 的地址片。
2、 运行过程中有若干进程需要占用内存,对于每个进程有一个申请时刻 T,需要内存单元数 M 及运行时间 P。在运行时间 P 内(即 T 时刻开始,T+P 时刻结束),这 M 个被占用的内存单元不能再被其他进程使用。
3、假设在T时刻有一个进程申请 M 个单元,且运行时间为 P,则:
-
若 T 时刻内存中存在长度为 M 的空闲地址片,则系统将这 M 个空闲单元分配给该进程。若存在多个长度为 M 个空闲地址片,则系统将首地址最小的那个空闲地址片分配给该进程。
-
如果 T 时刻不存在长度为 M 的空闲地址片,则该进程被放入一个等待队列。对于处于等待队列队头的进程,只要在任一时刻,存在长度为 M 的空闲地址片,系统马上将该进程取出队列,并为它分配内存单元。注意,在进行内存分配处理过程中,处于等待队列队头的进程的处理优先级最高,队列中的其它进程不能先于队头进程被处理。
现在给出一系列描述进程的数据,请编写一程序模拟系统分配内存的过程。
输入格式
第一行是一个数 N,表示总内存单元数(即地址范围从 0 到 N−1)。
从第二行开始每行描述一个进程的三个整数 T、M、P(M≤N)。
最后一行用三个 0 表示结束。
数据已按 T 从小到大排序。
输入文件最多 10000 行,且所有数据都小于 10^9。
输入文件中同一行相邻两项之间用一个或多个空格隔开。
输出格式
输出包括 2 行。
第一行是全部进程都运行完毕的时刻。
第二行是被放入过等待队列的进程总数。
输入样例:
10
1 3 10
2 4 3
3 4 4
4 1 4
5 3 4
0 0 0
输出样例:
12
2
提示
题意:有很多的进程,每个进程有t,m,p三个数字分别代表申请时间,内存单元数,以及运行时间,
我们需要模拟操作系统进行内存分配,最后输出所有进程完成时间以及进入过等待队列进程的数量
思路:
首先这道题目是一道模拟题,不需要什么复杂的算法,只有两种关键性的操作,那就是使用(插入)操作和释放(删除)操作.
如果将内存表看作是一个区间,我们要做的就是不断的插入需要使用内存的进程,
以及删除使用完内存的进程从而释放内存
如果说我们可以存储当前进程的话,那么显然有两种情况.
1:当前内存表中本来就存在一个长度为M的空闲内存
2:我们经过了删除操作,使得内存表中出现了一个长度为M的空闲内存.
对于内存表区间而言,显然每一个占用内存的进程,就是一个阻碍物.我们来一个图片来揭示真相.
Hint:红色部分为占用内存,白色部分为空闲内存.下文都以红色白色代替.
我们发现,一段连续的白色部分可以表示为,第i个红色部分的结尾处到第i+1个红色部分的开头.
这里特别注意以下,我们要处理边界,可以将−1处染成红色,n处染成白色.请记住这里的内存表,是[0,N−1]
根据上面的表示,我们的图片就变成下图所示.
所以说我们这道题目就可以用双向链表处理了,来保存每一段红色部分,最后取存储白色部分,
但是我们也可以使用set(O(logn)实现插入,删除,且自动排序)来存储所有红色部分
既然我们的使用操作都完成完毕了,那么接下来就是我们的释放操作的处理.
释放操作,也就是释放第T时刻的时候,已经完成的进程.
首先我们要确定一个点,就是释放每一个内存段,实际上就是将它从我们的Set里面删除.
所有的可以释放的内存段都释放完毕后,我们再从等待的队列中取出队头,判断能否将队头成功加入即可.
题解来源
y总讲解
#include<iostream>
#include<set>
#include<queue>
using namespace std;
using pii=pair<int,int>;
int res=0,cnt=0;
queue<pii>waits;//等待队列,first:需要内存单元数,second:占用时间
set<pii>runs;//正在被使用的内存区间,first:内存起始下标,second:内存长度
priority_queue<pii,vector<pii>,greater<pii>> endts;
//小根堆维护进程释放,first存结束时间,second存内存起始下标
bool give(int t,int m,int p)//给进程分配内存函数,返回失败or成功
{
for(auto it=runs.begin();it!=runs.end();++it)//扫描所有的红色部分
{
auto jt=it;
jt++;//jt相当于i+1,it相当于i
if(jt!=runs.end())
{
int start=it->first+it->second-1;//第i个红色部分的结尾
if(jt->first-1-start>=m)//判断两红色部分之间空闲内存单元数是否满足条件
//jt-first-1表示第i+1个红色部分结尾后一位,
//第i个到第i+1个红色之间最后一个未被使用的内存单元地址
{
runs.insert({start+1,m});//分配内存给当前进程,同时将其加入runs
endts.push({t+p,start+1});//加入endts
return true;//分配成功
}
}
}
return false;//分配失败
}
void finish(int t)//释放结束时间为t进程的函数
{
while(endts.size()&&endts.top().first<=t)//找出所有结束时间<=t的进程
{
int f=endts.top().first;
while(endts.size()&&endts.top().first==f)//防止有多个结束时间都为f的进程
{
auto tm=endts.top();
endts.pop();
auto it=runs.lower_bound({tm.second,0});//找到第一个>=该起始内存地址的元素
runs.erase(it);//进程完结,释放内存
}
res=f;//进程完结,更新结果
while(waits.size())//每结束一次结束时间的进程,判断等待对列是否有进程可以分配
{
auto front=waits.front();
if(give(f,front.first,front.second))
waits.pop();
else
break;
}
}
}
int main()
{
int n;
cin>>n;
int t,m,p;
runs.insert({-1,1}),runs.insert({n,1});//边界处理,表示(0~n-1)内存可用
while(cin>>t>>m>>p,t||m||p){
finish(t);//先释放内存,确保剩余内存最大化.
if(!give(t,m,p))//分配失败
{
waits.push({m,p});//加入等待
cnt++;//等待+1
}
}
finish(2e9);//将进程全部释放
cout<<res<<endl<<cnt;
}