問題描述:
已知一寬爲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;
}
結論:
此算法原理很簡單,實現也不復雜,在實踐中隨機情況下運行良好,並且效率較高。
附:實踐中回收時合併操作並不一定只發生在相鄰的兩個矩形中,可能出現跳躍合併的情況,實現也是需要一個簡單的查找而已。