华为OD机试真题E卷中关于VLAN资源池的问题,主要涉及到对VLAN资源池的管理和操作,具体是对VLAN ID的分配与回收。以下是对该问题的详细解析:
一、题目背景
VLAN(Virtual Local Area Network)是一种对局域网设备进行逻辑划分的技术。为了标识不同的VLAN,引入了VLAN ID的概念,VLAN ID是一个1到4094之间的整数。定义一个VLAN ID的资源池(下称VLAN资源池),资源池中连续的VLAN用“开始VLAN-结束VLAN”表示,不连续的用单个整数表示,所有的VLAN用英文逗号连接起来。
二、题目要求
现在有一个VLAN资源池,业务需要从资源池中申请一个VLAN。要求输出从VLAN资源池中移除申请的VLAN后的资源池。
三、输入与输出
-
输入:
- 第一行为字符串格式的VLAN资源池。
- 第二行为业务要申请的VLAN,VLAN的取值范围为1到4094。
-
输出:从VLAN资源池中移除申请的VLAN后的资源池。
四、解题思路
- 解析输入:首先,需要将输入的VLAN资源池字符串解析成一个方便操作的数据结构,如列表或集合。同时,也需要记录下要申请的VLAN ID。
- 查找并移除VLAN:在解析后的VLAN资源池中,查找并移除与申请的VLAN ID相匹配的部分。这包括处理连续VLAN和不连续VLAN的情况。
- 对于连续VLAN,如果申请的VLAN ID在连续范围内,则需要更新该连续范围或将其拆分为两个连续范围。
- 对于不连续VLAN,如果申请的VLAN ID在单个VLAN中,则直接移除该VLAN。
- 重构资源池:在移除申请的VLAN后,需要重新构造VLAN资源池字符串,确保其格式正确。
- 输出结果:将重构后的VLAN资源池字符串作为输出。
五、示例
假设输入的VLAN资源池为“1-10,12,15-20”,要申请的VLAN为5:
- 解析输入后,VLAN资源池为[[1, 10], [12], [15, 20]],要申请的VLAN为5。
- 由于5不在任何连续范围或不连续VLAN中,因此资源池无需更改(但此示例是为了说明格式,实际情况下5的申请可能意味着检查是否资源池有误或需扩展资源池表示法以包含“不可用”或“已分配”区间)。但按题目要求,我们假设5的申请是无效的或已处理(如记录日志、返回错误等),且不影响当前资源池表示(因为5本就不在资源池中)。若改为申请一个存在的VLAN,如16,则需更新资源池。
- 假设改为申请16(有效申请),则更新后的资源池为“1-10,12,15,17-20”。
- 输出更新后的VLAN资源池字符串。
注意:上述示例中的“5的申请”部分是为了解释输入解析过程,并不符合题目要求的输出格式。题目要求的是从已有资源池中移除一个已存在的VLAN后的新资源池表示。
六、代码实现(一)
import java.util.ArrayList;
import java.util.List;
public class VLANPool {
// 内部类表示VLAN范围
static class VLANRange {
int start;
int end;
VLANRange(int start, int end) {
this.start = Math.min(start, end);
this.end = Math.max(start, end);
}
// 检查VLAN是否在范围内
boolean contains(int vlan) {
return vlan >= start && vlan <= end;
}
/**
* 从范围中移除指定的VLAN
* 如果该VLAN是范围内的唯一VLAN,则该范围将被移除
* 如果VLAN位于范围的开始或结束,则范围将被相应地调整
* 如果VLAN位于范围的中间,则范围将被拆分为两个新范围
*
* @param vlan 要移除的VLAN编号
* @return 返回一个新的范围列表,反映了移除VLAN后的变化
*/
List<VLANRange> remove(int vlan) {
List<VLANRange> newRanges = new ArrayList<>();
if (vlan == start && vlan == end) {
// VLAN是范围的唯一元素,不添加新范围
} else if (vlan == start) {
// VLAN是范围的开始,创建一个新的范围从下一个VLAN开始
newRanges.add(new VLANRange(start + 1, end));
} else if (vlan == end) {
// VLAN是范围的结束,创建一个新的范围到前一个VLAN结束
newRanges.add(new VLANRange(start, end - 1));
} else {
// VLAN在范围中间,拆分为两个范围
newRanges.add(new VLANRange(start, vlan - 1));
newRanges.add(new VLANRange(vlan + 1, end));
}
return newRanges;
}
@Override
public String toString() {
return start == end ? String.valueOf(start) : start + "-" + end;
}
}
/* 将字符串表示的VLAN资源池解析为VLANRange列表
*
* 输入的vlanPoolStr格式示例:"100-200,250,300-350"
*
*/
static List<VLANRange> parseVLANPool(String vlanPoolStr) {
// 创建一个列表来存储VLAN范围
List<VLANRange> vlanRanges = new ArrayList<>();
// 将输入的字符串按逗号分割成单个VLAN或VLAN范围的字符串数组
String[] vlanStrs = vlanPoolStr.split(",");
// 遍历每个VLAN或VLAN范围的字符串
for (String vlanStr : vlanStrs) {
// 检查字符串是否包含"-",以确定它是单个VLAN还是一个VLAN范围
if (vlanStr.contains("-")) {
// 分割字符串成开始和结束的VLAN ID
String[] parts = vlanStr.split("-");
// 去除可能的前后空格并解析开始的VLAN ID
int start = Integer.parseInt(parts[0].trim());
// 去除可能的前后空格并解析结束的VLAN ID
int end = Integer.parseInt(parts[1].trim());
// 创建一个VLANRange对象并添加到列表中
vlanRanges.add(new VLANRange(start, end));
} else {
// 对于单个VLAN,解析VLAN ID并创建一个具有相同开始和结束ID的VLANRange对象
int vlan = Integer.parseInt(vlanStr.trim());
vlanRanges.add(new VLANRange(vlan, vlan));
}
}
// 返回解析后的VLANRange列表
return vlanRanges;
}
/* 申请VLAN并更新资源池
* 该方法从给定的VLAN资源池中分配一个VLAN,并更新资源池的配置
* 参数:
* vlanPoolStr: VLAN资源池的字符串表示,格式为"起始VLAN-结束VLAN,起始VLAN-结束VLAN,..."
* vlan: 要分配的VLAN ID
* 返回值:
* 更新后的VLAN资源池的字符串表示
* 异常:
*如果请求的VLAN不在资源池中,抛出IllegalArgumentException
*/
static String allocateVLAN(String vlanPoolStr, int vlan) {
// 解析VLAN资源池字符串为VLANRange对象列表
List<VLANRange> vlanRanges = parseVLANPool(vlanPoolStr);
// 初始化一个新的VLANRange列表,用于存储更新后的资源池
List<VLANRange> newRanges = new ArrayList<>();
// 标记VLAN是否已成功分配
boolean vlanAllocated = false;
// 遍历VLAN资源池中的每一个范围
for (VLANRange range : vlanRanges) {
// 如果当前VLAN在范围中
if (range.contains(vlan)) {
// 从范围中移除该VLAN,并将更新后的范围添加到新列表中
List<VLANRange> updatedRanges = range.remove(vlan);
newRanges.addAll(updatedRanges);
// 标记VLAN已分配
vlanAllocated = true;
} else {
// 如果VLAN不在当前范围中,直接将范围添加到新列表中
newRanges.add(range);
}
}
// 如果VLAN未被分配,抛出异常
if (!vlanAllocated) {
throw new IllegalArgumentException("VLAN " + vlan + " is not available in the pool.");
}
// 将更新后的范围列表转换回字符串表示
StringBuilder vlanPoolBuilder = new StringBuilder();
for (int i = 0; i < newRanges.size(); i++) {
vlanPoolBuilder.append(newRanges.get(i));
// 如果不是最后一个范围,添加分隔符
if (i < newRanges.size() - 1) {
vlanPoolBuilder.append(",");
}
}
// 返回更新后的VLAN资源池字符串
return vlanPoolBuilder.toString();
}
public static void main(String[] args) {
String vlanPoolStr = "1-10,12,15-20";
int vlanToAllocate = 16; // 假设要申请的VLAN是16
try {
String updatedVLANPool = allocateVLAN(vlanPoolStr, vlanToAllocate);
System.out.println("更新后的资源池: " + updatedVLANPool);
} catch (IllegalArgumentException e) {
System.err.println(e.getMessage());
}
}
}
七、代码实现(二)
当然,对于VLAN资源池的管理,有多种方法可以实现。以下是一种基于集合(Set)的Java实现,这种方法更适合处理单个VLAN的分配,而不是处理VLAN范围。然而,为了处理范围,我们可以稍微修改这种方法,使其能够处理范围并维护一个可用的VLAN集合。
import java.util.*;
public class VLANPoolManager {
private Set<Integer> availableVLANs;
// 构造函数,初始化VLAN资源池
public VLANPoolManager(String vlanPoolStr) {
availableVLANs = new HashSet<>();
String[] vlanRanges = vlanPoolStr.split(",");
for (String range : vlanRanges) {
if (range.contains("-")) {
String[] parts = range.split("-");
int start = Integer.parseInt(parts[0].trim());
int end = Integer.parseInt(parts[1].trim());
for (int vlan = start; vlan <= end; vlan++) {
availableVLANs.add(vlan);
}
} else {
int vlan = Integer.parseInt(range.trim());
availableVLANs.add(vlan);
}
}
}
// 申请VLAN
public Integer allocateVLAN() {
Integer vlan = availableVLANs.iterator().next(); // 获取集合中的第一个VLAN(这只是为了演示)
if (vlan != null) {
availableVLANs.remove(vlan); // 从集合中移除已分配的VLAN
}
return vlan; // 返回分配的VLAN(如果集合为空,则返回null)
}
// 更新VLAN资源池(例如,当VLAN被释放时)
public void releaseVLAN(int vlan) {
availableVLANs.add(vlan); // 将VLAN添加回集合中
}
// 获取当前可用的VLAN资源池(用于调试或显示)
public Set<Integer> getAvailableVLANs() {
return new HashSet<>(availableVLANs); // 返回一个新的集合以避免外部修改
}
// 打印当前可用的VLAN资源池
public void printAvailableVLANs() {
System.out.println("Available VLANs: " + availableVLANs);
}
public static void main(String[] args) {
String vlanPoolStr = "1-10,12,15-20";
VLANPoolManager vlanPoolManager = new VLANPoolManager(vlanPoolStr);
vlanPoolManager.printAvailableVLANs();
// 申请VLAN
Integer vlan1 = vlanPoolManager.allocateVLAN();
System.out.println("Allocated VLAN: " + vlan1);
// 再次打印可用的VLAN资源池
vlanPoolManager.printAvailableVLANs();
// 释放VLAN
vlanPoolManager.releaseVLAN(vlan1);
// 最后一次打印可用的VLAN资源池
vlanPoolManager.printAvailableVLANs();
}
}
注意事项:
-
VLAN分配策略:在上面的代码中,
allocateVLAN方法简单地返回了集合中的第一个VLAN。在实际应用中,您可能需要实现更复杂的分配策略,例如基于某种优先级或轮询算法。 -
并发性:如果多个线程同时访问VLAN资源池,那么需要实现同步机制来避免竞态条件。上面的代码没有处理并发性。
-
VLAN释放:
releaseVLAN方法允许将VLAN释放回资源池。在实际应用中,您可能需要验证释放的VLAN是否确实之前被分配过,以避免潜在的错误或安全漏洞。 -
性能:对于非常大的VLAN集合,使用
HashSet可能不是最优的选择,因为查找和删除操作的时间复杂度是O(1)(在平均情况下),但在最坏情况下(例如,当哈希函数导致大量冲突时),性能可能会下降。如果性能成为问题,您可以考虑使用其他数据结构,如TreeSet(有序集合,查找和删除的时间复杂度为O(log n))或自定义的数据结构。 -
错误处理:上面的代码没有处理任何错误情况,例如无效的VLAN范围字符串或重复的VLAN。在实际应用中,您应该添加适当的错误处理和验证逻辑。
-
VLAN范围处理:虽然上面的代码可以处理VLAN范围,但它并没有优化范围的处理。例如,当从范围中移除一个VLAN时,它不会合并相邻的范围。如果需要这种优化,您可能需要实现更复杂的数据结构来跟踪范围。
八、代码实现(详细版)
为了优化VLAN范围的处理,并在移除VLAN时能够合并相邻的范围,我们可以使用一个自定义的数据结构来跟踪VLAN的连续范围。这个数据结构可以是一个列表,其中每个元素都是一个表示VLAN范围的类(包含起始VLAN和结束VLAN)。
下面是一个优化后的VLANPoolManager类,它使用这种数据结构来管理VLAN范围:
import java.util.*;
class VLANRange {
int start;
int end;
VLANRange(int start, int end) {
this.start = Math.min(start, end);
this.end = Math.max(start, end);
}
boolean contains(int vlan) {
return vlan >= start && vlan <= end;
}
boolean overlaps(VLANRange other) {
return this.start <= other.end && this.end >= other.start;
}
VLANRange merge(VLANRange other) {
return new VLANRange(Math.min(this.start, other.start), Math.max(this.end, other.end));
}
@Override
public String toString() {
return start + "-" + end;
}
}
public class VLANPoolManager {
private List<VLANRange> vlanRanges;
// 构造函数,初始化VLAN资源池
public VLANPoolManager(String vlanPoolStr) {
vlanRanges = new ArrayList<>();
String[] vlanRangePairs = vlanPoolStr.split(",");
for (String rangePair : vlanRangePairs) {
String[] range = rangePair.split("-");
int start = Integer.parseInt(range[0].trim());
int end = range.length > 1 ? Integer.parseInt(range[1].trim()) : start; // 如果只有一个数,则起始和结束VLAN相同
vlanRanges.add(new VLANRange(start, end));
}
// 合并重叠的范围(初始化时可能输入了重叠的范围)
vlanRanges = mergeOverlappingRanges(vlanRanges);
}
// 申请VLAN(为了简单,这里总是返回第一个范围的第一个VLAN,并调整范围)
public Integer allocateVLAN() {
for (VLANRange range : vlanRanges) {
if (range.start <= range.end) { // 确保范围有效
int vlan = range.start;
range.start++; // 分配VLAN后,调整起始VLAN
if (range.start > range.end) { // 如果起始VLAN超过结束VLAN,则移除该范围
vlanRanges.remove(range);
}
return vlan;
}
}
return null; // 没有可用的VLAN
}
// 释放VLAN(尝试合并范围)
public void releaseVLAN(int vlan) {
for (VLANRange range : vlanRanges) {
if (range.contains(vlan)) {
// 如果释放的VLAN是范围的起始或结束,则只需调整范围
if (vlan == range.start) {
range.start--;
} else if (vlan == range.end) {
range.end++;
} else {
// 如果释放的VLAN在范围中间,则需要拆分范围并可能合并相邻范围
VLANRange newRange1 = new VLANRange(range.start, vlan - 1);
VLANRange newRange2 = new VLANRange(vlan + 1, range.end);
vlanRanges.remove(range);
vlanRanges.add(newRange1);
vlanRanges.add(newRange2);
vlanRanges = mergeOverlappingRanges(vlanRanges); // 合并可能产生的重叠范围
return; // 释放后直接返回,因为已经处理了范围变化
}
// 检查是否需要与相邻范围合并(但在这个简单的实现中,我们不在这里立即合并,而是在下次分配或释放时处理)
// 注意:为了性能,可以在这里添加逻辑来立即合并相邻范围,但这会增加释放操作的复杂度。
break; // 假设每个VLAN只在一个范围内,找到后退出循环
}
}
}
// 合并重叠的范围
private List<VLANRange> mergeOverlappingRanges(List<VLANRange> ranges) {
List<VLANRange> mergedRanges = new ArrayList<>();
ranges.sort(Comparator.comparingInt(r -> r.start)); // 按起始VLAN排序
for (VLANRange range : ranges) {
boolean merged = false;
for (int i = 0; i < mergedRanges.size(); i++) {
VLANRange mergedRange = mergedRanges.get(i);
if (mergedRange.overlaps(range)) {
mergedRanges.set(i, mergedRange.merge(range));
merged = true;
break;
}
}
if (!merged) {
mergedRanges.add(range);
}
}
return mergedRanges;
}
// 打印当前可用的VLAN资源池
public void printAvailableVLANs() {
System.out.println("Available VLAN Ranges: " + vlanRanges);
}
public static void main(String[] args) {
String vlanPoolStr = "1-10,12,15-20";
VLANPoolManager vlanPoolManager = new VLANPoolManager(vlanPoolStr);
vlanPoolManager.printAvailableVLANs();
// 申请VLAN
Integer vlan1 = vlanPoolManager.allocateVLAN();
System.out.println("Allocated VLAN: " + vlan1);
// 再次打印可用的VLAN资源池
vlanPoolManager.printAvailableVLANs();
// 释放VLAN
vlanPoolManager.releaseVLAN(vlan1);
// 最后一次打印可用的VLAN资源池
vlanPoolManager.printAvailableVLANs();
// 申请另一个VLAN
Integer vlan2 = vlanPoolManager.allocateVLAN();
System.out.println("Allocated VLAN: " + vlan2);
// 打印最终的VLAN资源池
vlanPoolManager.printAvailableVLANs();
}
}
注意事项:
-
性能:在
releaseVLAN方法中,我没有立即合并相邻范围,而是在每次释放后调用mergeOverlappingRanges方法来合并整个列表。这是为了简化逻辑,但可能不是最高效的方法。在实际应用中,您可能需要根据性能需求来调整这一点。 -
错误处理:上面的代码没有处理任何错误情况,例如无效的VLAN输入或尝试释放一个从未被分配的VLAN。在实际应用中,您应该添加适当的错误处理和验证逻辑。
-
VLAN分配策略:
allocateVLAN方法总是返回第一个范围的第一个VLAN。在实际应用中,您可能需要实现更复杂的分配策略。 -
并发性:上面的代码没有处理并发性。如果多个线程同时访问VLAN资源池,您需要实现同步机制来避免竞态条件。

522

被折叠的 条评论
为什么被折叠?



