问题描述:
已知一宽为W高为H的目的矩形,时间序列为t0,t1,……,在时间tn时系统随机产生一宽为Wn(不超过W)高为Hn(不超过H)的小矩形,并且需将此小矩形放至目的矩形中,而且小矩形的生存期未知(即在同一时刻,产生一个小矩形时有可能同时需销毁若干个已存在小矩形)。寻找一算法使得在一段时间t内,目的矩形被重置的次数最少?
解决方案:
此问题类似于内存管理问题:随机出现的new(或malloc)和delete(或free)分别分配和释放内存,一段时间后,内存碎片的产生便会导致某一时刻new(或malloc)返回失败。这里的问题是,在产生的小矩形不能放至目的矩形时,便需要将目的矩形重置(即清空所有小矩形),求解结果为时间t内的重置次数尽可能少。
考虑到生存期问题,我们需要对目的矩形的可用空间进行分配和回收。当某个小矩形销毁时,我们将此空间重新加至可用空间中,并且尽可能拼接成更大的空间,以供下一个可能更大的小矩形使用(目的矩形左上角为原点(0,0)),采用伙伴系统算法。
先定义矩形分配的相关操作
// 定义矩形的小于操作符“<”,按从左至右,从上至下的顺序排列
inline bool operator<(RECT const& lhs, RECT const& rhs)
{
return (lhs.top < rhs.top || (lhs.top == rhs.top && lhs.left < rhs.left));
}
// 如果使用的是STL,也可以特化模板类std::less
template<>
struct std::less<RECT>
{
bool operator()(RECT const& lhs, RECT const& rhs) const
{
return (lhs.top < rhs.top || (lhs.top == rhs.top && lhs.left < rhs.left);
}
}
// 使用vecotr保存可用空间的矩形,初始状态为只含有一个元素且为目的矩形
std::vecotr<RECT> rect_for_use;
rect_for_use.push_back(CRect(0, 0, W, H));
// 定义矩形的大于等于操作符“>=”
inline bool operator>=(RECT const& lhs, RECT const& rhs)
{
return (lhs.right - lhs.left >= rhs.right - rhs.left && lhs.bottom - lhs.top >= rhs.bottom - rhs.top);
}
// 或者特化模板类std::greater_than
template<>
struct std::greater_than<RECT>
{
bool operator()(RECT const& lhs, RECT const& rhs) const
{
return (lhs.right - lhs.left >= rhs.right - rhs.left && lhs.bottom - lhs.top >= rhs.bottom - rhs.top);
}
}
// 从可用空间中查找一个能容下当前小矩形的空间
std::vector<RECT>::iterator it = std::find_if(rect_for_use.begin(), rect_for_use.end(), std::greater_than<RECT>());
// 如果未找到,重置目的矩形
if(it == rect_for_use.end())
{
rect_for_use.erase(rect_for_use.begin(), rect_for_use.end());
rect_for_use.push_back(CRect(0, 0, W, H));
}
再定义矩形回收的相关操作
// 查找需要插入的位置
std::vector<RECT>::iterator it = std::lower_bound(rect_for_use.begin(), rect_for_use.end(), rc, std::less<RECT>());
// 插入到可用空间中
it = std::insert(rect_for_use.begin(), rect_for_use.end(), it);
// 判断是否需要合并成更大的空间
while(1)
{
if(it != rect_for_use.begin())
{
RECT& forward = *(it - 1);
RECT& cur = *it;
if(forward.top == cur.top && forward.bottom == cur.bottom && forward.right == cur.left)
{
forward.right = cur.right;
it = rect_for_use.erase(it);
continue;
}
else if(forward.left == cur.left && forward.right == cur.right && forward.bottom == cur.top)
{
forward.bottom = cur.bottom;
it = rect_for_use.erase(it);
continue;
}
}
if(++it != rect_for_use.end())
continue;
break;
}
结论:
此算法原理很简单,实现也不复杂,在实践中随机情况下运行良好,并且效率较高。
附:实践中回收时合并操作并不一定只发生在相邻的两个矩形中,可能出现跳跃合并的情况,实现也是需要一个简单的查找而已。