华为OD机试中的“内存资源分配”题目是一道经典的资源分配类题目,它考察考生如何有效地管理和分配有限的资源,并能根据用户的请求优先分配合适的资源。以下是对该题目的详细解析:
一、题目描述
有一个简易内存池,内存按照大小粒度分类,每个粒度有若干个可用内存资源。用户会进行一系列内存申请,系统需要按需分配内存池中的资源,并返回每次申请是否成功的结果。
二、分配规则
- 分配的内存要大于等于内存的申请量。
- 存在满足需求的内存就必须分配。
- 优先分配粒度小的内存块,但不能拆分内存块使用,只能分配整块。
- 按申请顺序分配内存,先申请的优先处理。
- 有可用内存分配则申请结果为true,没有可用则返回false。
三、输入描述
输入为两行字符串:
- 第一行为内存池资源列表,包含内存粒度数据信息,粒度数据间用逗号分割。一个粒度信息内用冒号分割,冒号前为内存粒度大小,冒号后为数量。资源列表不大于1024,每个粒度的数量不大于4096。
- 第二行为申请列表,申请的内存大小间用逗号分割。申请列表不大于100000。
四、输出描述
输出为一行字符串,表示每次内存申请的结果(成功为true,失败为false),结果间用逗号分割。
五、示例
输入:
64:2,128:1,32:4,1:128
50,36,64,128,127
输出:
true,true,true,false,false
六、解题思路
- 解析输入数据,将内存池资源列表和申请列表分别转换为适当的数据结构(如列表或字典)。
- 遍历申请列表,对于每个申请,按顺序检查内存池中的资源:
- 从粒度最小的资源开始检查,看是否有足够的内存满足申请。
- 如果有,则分配内存,并更新内存池的状态(减少该粒度的可用数量)。
- 如果没有,则继续检查下一个粒度的资源。
- 如果所有粒度的资源都不满足申请,则记录申请失败。
- 输出每次申请的结果。
七、代码实现
在Java中,你可以通过创建一个类来模拟内存资源分配的过程。以下是一个简单的实现,它遵循了华为OD机试中“内存资源分配”题目的要求:
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class MemoryAllocator {
// 内存池,使用TreeMap来保持粒度有序(从小到大)
private final TreeMap<Integer, Integer> memoryPool;
// 构造函数,初始化内存池
public MemoryAllocator(String poolStr) {
memoryPool = new TreeMap<>();
String[] poolEntries = poolStr.split(",");
for (String entry : poolEntries) {
String[] parts = entry.split(":");
int size = Integer.parseInt(parts[0]);
int count = Integer.parseInt(parts[1]);
memoryPool.put(size, count);
}
}
/**
* 分配内存并返回结果
*
* 本方法旨在处理多个内存分配请求,并将处理结果以布尔值的形式存储在列表中返回
* 每个请求以逗号分隔,并且每个请求的大小将被解析为整数
*
* @param requestStr 包含多个内存分配请求的字符串,每个请求的大小以逗号分隔
* @return 包含每个内存分配请求结果的列表,true表示成功分配,false表示分配失败
*/
public List<Boolean> allocateMemory(String requestStr) {
List<Boolean> results = new ArrayList<>();
// 将输入字符串分割成多个内存分配请求
String[] requests = requestStr.split(",");
// 遍历每个请求,尝试分配内存,并记录结果
for (String request : requests) {
int requestSize = Integer.parseInt(request);
boolean allocated = allocate(requestSize);
results.add(allocated);
}
return results;
}
// 尝试分配指定大小的内存
/**
* 本函数旨在从内存池中分配指定大小的内存块。
* 它会遍历内存池,寻找第一个大于或等于所需大小的可用内存块,并减少其可用数量。
* 如果找到合适的内存块并成功分配,则返回true;否则,返回false。
*
* @param size 需要分配的内存大小
* @return 如果成功分配内存,则返回true;否则返回false。
*/
private boolean allocate(int size) {
// 遍历内存池,从最小的粒度开始尝试
for (Map.Entry<Integer, Integer> entry : memoryPool.entrySet()) {
int granularity = entry.getKey();
int available = entry.getValue();
// 检查当前粒度是否满足大小要求且有可用的内存块
if (granularity >= size && available > 0) {
// 分配内存并更新内存池状态
memoryPool.put(granularity, available - 1);
// 成功分配内存,返回true
return true;
}
}
// 没有找到合适的内存块,返回false
return false;
}
// 主函数,用于测试
public static void main(String[] args) {
String poolStr = "64:2,128:1,32:4,1:128";
String requestStr = "50,36,64,128,127";
MemoryAllocator allocator = new MemoryAllocator(poolStr);
List<Boolean> results = allocator.allocateMemory(requestStr);
// 输出结果
System.out.println(String.join(",", results.stream().map(String::valueOf).toArray(String[]::new)));
}
}
八、代码解释
-
内存池表示:
- 使用
TreeMap<Integer, Integer>
来存储内存池,其中键是内存粒度(大小),值是该粒度下可用内存块的数量。TreeMap
保证了键的自然排序,这样我们可以从最小的粒度开始尝试分配内存。
- 使用
-
构造函数:
MemoryAllocator(String poolStr)
:解析输入的内存池字符串,并将其转换为TreeMap
中的条目。
-
内存分配:
allocateMemory(String requestStr)
:解析输入的请求字符串,并逐个处理每个请求。对于每个请求,调用allocate(int size)
方法尝试分配内存,并将结果添加到结果列表中。allocate(int size)
:遍历内存池,从最小的粒度开始,查找第一个能够满足请求的内存块。如果找到,则分配该内存块(将对应粒度的可用数量减1),并返回true
。如果遍历完所有粒度都没有找到合适的内存块,则返回false
。
-
测试:
- 在
main
函数中,我们创建了一个MemoryAllocator
对象,并使用示例输入进行测试。最后,将结果列表转换为逗号分隔的字符串并输出。
- 在
九、运行示例解析
初始化内存池
首先,我们创建一个MemoryAllocator
实例,并传入一个字符串poolStr
来描述内存池。在这个字符串中,每个内存块的大小和数量由冒号分隔,不同的内存块由逗号分隔。
例如,对于输入字符串:
String poolStr = "64:2,128:1,32:4,1:128";
内存池将被初始化为:
- 64大小的内存块有2个。
- 128大小的内存块有1个。
- 32大小的内存块有4个。
- 1大小的内存块有128个。
这些内存块按照大小存储在TreeMap
中,确保它们按照从小到大的顺序排列。
分配内存
接下来,我们调用allocateMemory
方法来处理一系列内存分配请求。每个请求的大小由逗号分隔。
例如,对于输入字符串:
String requestStr = "50,36,64,128,127";
我们尝试分配以下大小的内存:
- 50
- 36
- 64
- 128
- 127
内存分配过程
-
分配50大小的内存:
- 检查内存池,找到第一个大于或等于50的内存块是64。
- 64大小的内存块有2个,可以分配一个。
- 更新内存池:64大小的内存块剩余1个。
- 分配成功。
-
分配36大小的内存:
- 再次检查内存池,找到第一个大于或等于36的内存块是64。
- 64大小的内存块有1个(之前已经分配了一个),可以分配一个。
- 注意:虽然32大小的内存块更接近36,但因为我们从最小的满足条件的粒度开始分配(这是当前实现的一个限制),所以我们选择64。
- 更新内存池:64大小的内存块为0,无法再分配。
- 分配成功。
-
分配64大小的内存:
- 检查内存池,发现没有64大小的内存块了。
- 下一个更大的内存块是128,但它太大,不满足“最小满足条件”的当前策略(实际上,这里可以改进策略以更好地利用内存)。
- 分配失败。
-
分配128大小的内存:
- 检查内存池,找到128大小的内存块,有1个。
- 分配一个128大小的内存块。
- 更新内存池:128大小的内存块剩余0个。
- 分配成功。
-
分配127大小的内存:
- 检查内存池,发现没有127大小的内存块,且所有现有的内存块都不满足这个大小(128太大)。
- 分配失败。
结果
最后,allocateMemory
方法返回一个布尔列表,表示每个内存分配请求的结果:
[true, true, false, true, false]
输出
在main
函数中,我们将这个结果列表转换为逗号分隔的字符串并打印出来:
System.out.println(String.join(",", results.stream().map(String::valueOf).toArray(String[]::new)));
输出结果为:
true,true,false,true,false
十、改进建议
当前的内存分配策略是简单的“最小满足条件”策略,即找到第一个大于或等于请求大小的内存块进行分配。这种策略在某些情况下可能不是最优的(例如,当存在多个接近请求大小的内存块时)。可以通过更复杂的策略(如最佳拟合、首次拟合等)来改进内存分配的效率。
十一、注意事项
- 内存不能拆分使用,只能分配整块。
- 按申请顺序分配内存,先申请的优先处理。
- 输出结果要与输入申请的顺序一一对应。