.  最近工作上有一个需求,需要将图片打包成图集,以便于让资源更紧凑,利用率更高,提升性能,游戏行内的同志应该很熟练这个操作.通常我们需要用一个app来完成这项工作,最出名的莫过于Texture Packer。

Texture Packer官方示意图

.  最早接触到这一概念的时候,我还是一个学生,当时玩的《暗黑魔破坏神2》,它有一个背包系统,这个背包系统跟现在大多数游戏都不一样,它的道具存放并非等大小,比如长剑比匕首长,斧头比长剑宽,因此在摆放道具时,不能乱放,不然背包就不够用。

暗黑破坏神2背包截图

.  这是一个很有意思的设计,但现在基本上绝迹了,以至于我想到游戏中的背包就会不禁想起暗黑破坏神2的背包系统,我在《天龙八部》游戏中,第一次发现了背包自动整理这一项功能,但是它的背包道具都是等大小的,所以平平无奇,但却不由让我想到《暗黑破坏神2》的背包自动整理,它必然会涉及到一个最优排列组合算法。这个疑惑一直困扰了我很多年,它到底是怎么实现的?直到上周,我重新打开了《暗黑破坏神2》这款游戏,发现它根本没有这个功能,童年幻想破灭~

.  虽然童年幻想破灭了,但成年梦想得跟上,正好工作有这么一个让我去实现的机会,于是我尝试去思考算法,起初想到经典的《背包算法》,仔细研究后发现不太适用,于是放弃。不得不说,厕所是一个很适合思考的地方,因为后来的实现方案是我在蹲厕所的时候突然就想出来了。有没有科学上的解释?

收纳箱算法?

.  这个算法跟我们日常使用收纳箱的思路很相似,起初收纳箱是空的,它只有一个存放空间,但是很大.之后我们逐个物品往里面放,每次放进去一个物品,原来的空间都会被拆分成两个,最后空间越来越小,直到不能装下剩下的物品,或者物品放完了,这一过程就结束了.

收纳箱示意图

.  上图清晰描述了收纳箱的存放过程,绿色是收纳箱,黄色是物品,起初收纳箱是空的,只有一个空间,放进物品后,空间被拆分了,这一步是关键,因为放进一个物品会有两种拆分策略,红色线和棕色线分表分表显示了两种拆分策略,如果按红色线拆分,则会产生一个很小的空间和一个很大的空间,如果按棕色线拆分,则产生两个大小均匀的空间。这两种策略对应不同的物品有奇效。每次放进去一个物品,收纳箱的一处空间就会被占用,而算法只考虑没有被占用的空间即可。

.  接下来就是逐个物品往收纳箱里放,这一步也是关键,因为每次放进一个物品,空间都会被划分,这个划分依据是物品的大小,如果先放进去一个很小的物品,比如1x1大小的物品,那么空间无论怎么分,都会产生一个很狭窄的空间,这个狭窄的空间很可能存不下后续的任何物品,那么这个空间就浪费了,因此在此之前,先对所有物品进行一个排序,这个排序可以按物品的面积,物品的宽度,物品的高度,物品的最长的边,等等。这些排列策略也会影响最终的排列组合。

改进

空间被浪费

.  上图是一个空间被浪费的例子,因为上文描述的算法需要已知收纳箱大小,比如已知收纳箱512x512大小,如果物品总面积超出这个大小,则装不下,如果物品远小于这个大小,则浪费空间,所以算法能动态计算大小,那就再好不过了。不过谢天谢地,很容易就可以搞定这个问题,我们只需要提前计算出单个物品最大需要的大小,用这个大小作为收纳箱的大小就可以了。

自动适应

.  优化过后,已经提高了不少空间利用率,第一个收纳箱装进了最大的物品,后面的收纳箱采用较小的尺寸继续装剩下的物品,直到全部装完为止。但是这里产生了一个新问题,会生成多个收纳箱,如果多个收纳箱都很小,那资源复用率就下降了,所以还需要进一步优化,尽可能提高资源复用率。比如两个128x128可以合并成一个256x256,两个256x256可以合并成一个512x512。

.  思路是这样的,设定一个打包级别,当这个级别的打包数量达到一个值,就将这个级别提升一级,再重新打包,直到所有级别的收纳箱都没有超出限制。比如,128x128 2,256x256 2,512x512 3 分别代表三个不同的级别,他们是递增关系,当128x128的收纳箱达到2个的时候,说明它需要提升一个级别重新打包,于是级别提升到256x256,依次类推。

最终版本

以上结果是采用按棕色线拆分空间,按物品最长边排序,及以下打包级别:

{ 128, 128, 1 },
{ 256, 256, 2 },
{ 512, 512, 3 },
{ 1024, 1024, 4 },
{ 2048, 2048, 100 },

动态图

//  storage_box.h
#pragma once#include <list>
#include <tuple>
#include <array>
#include <vector>
#include <cassert>
#include <algorithm>using iint = int;
using uint = unsigned int;class StorageBox {
public:struct Item {uint i;uint w;uint h;uint GetV() const{return std::max(w, h);}};struct ResultItem {uint i;uint x;uint y;uint w;uint h;ResultItem(): i((uint)~0){ }uint GetV() const{return w * h;}bool IsReady() const{return i != (uint)~0;}bool IsContains(const Item & item) const{return w >= item.w && h >= item.h;}bool AddItem(const Item & item, ResultItem * out0, ResultItem * out1){if (!IsContains(item)){return false;}auto nx = x + item.w;auto ny = y + item.h;auto s0 = (w - item.w) * item.h;auto s1 = (h - item.h) * w;auto s2 = (w - item.w) * h;auto s3 = (h - item.h) * item.w;//  两种切分策略://      按最大面积切分//      按均匀面积切分//if (std::max(s0, s1) > std::max(s2, s3))if (std::max(s0, s1) - std::min(s0, s1) < std::max(s2, s3) - std::min(s2, s3)){out0->x = nx;out0->y = y;out0->w = w - item.w;out0->h = item.h;out1->x = x;out1->y = ny;out1->w = w;out1->h = h - item.h;}else{out0->x = nx;out0->y = y;out0->w = w - item.w;out0->h = h;out1->x = x;out1->y = ny;out1->w = item.w;out1->h = h - item.h;}w = item.w;h = item.h;i = item.i;return true;}};struct ResultBox {uint level;std::vector<ResultItem> items;};//  打包级别static constexpr iint PACK_LEVEL[][3] = {{ 128, 128, 1 },{ 256, 256, 2 },{ 512, 512, 3 },{ 1024, 1024, 4 },{ 2048, 2048, 100 },};std::vector<ResultBox> Pack(std::vector<Item> items);private://  确定使用哪个级别打包图集uint CheckLevel(const Item & item);uint CheckLevel(const std::vector<Item> & items);//  根据图片的V值进行排序void SortItems(std::vector<Item> & items);void SortItems(std::vector<ResultItem> & items);uint CheckLimit(std::vector<ResultBox>::iterator cur,std::vector<ResultBox>::iterator end);//  打包ResultBox PackBox(std::vector<Item> & items, uint level);void PackBox(std::vector<Item> & items, uint level, std::vector<ResultBox> & retBoxs);//  解包void UnpackBox(std::vector<Item> & items, std::vector<ResultBox>::iterator cur,std::vector<ResultBox>::iterator end);
};
//  storage_box.cpp
#include "storage_box.h"std::vector<StorageBox::ResultBox> StorageBox::Pack(std::vector<Item> items)
{std::vector<ResultBox> retBoxs;PackBox(items, 0, retBoxs);for (auto it = retBoxs.begin(); it != retBoxs.end();){auto level = it->level;auto limit = StorageBox::PACK_LEVEL[level][2];auto count = CheckLimit(it, retBoxs.end());if (count > limit){UnpackBox(items, it, retBoxs.end());retBoxs.erase(it, retBoxs.end());PackBox(items, level+1, retBoxs);it = retBoxs.begin();}else{++it;}}return retBoxs;
}uint StorageBox::CheckLevel(const Item & item)
{for (auto i = 0; i != sizeof(PACK_LEVEL) / sizeof(PACK_LEVEL[0]); ++i){if ((uint)PACK_LEVEL[i][0] >= item.w && (uint)PACK_LEVEL[i][1] >= item.h){return i;}}return (uint)~0;
}uint StorageBox::CheckLevel(const std::vector<Item>& items)
{uint level = 0;for (auto & item : items){auto i = CheckLevel(item);assert((uint)~0 != i);if (i > level) { level = i; }}return level;
}void StorageBox::SortItems(std::vector<Item>& items)
{std::sort(items.begin(), items.end(), [](const Item & item0, const Item & item1){return item0.GetV() > item1.GetV();});
}void StorageBox::SortItems(std::vector<ResultItem> & items)
{std::sort(items.begin(), items.end(), [](const ResultItem & item0, const ResultItem & item1){return item0.GetV() < item1.GetV();});
}uint StorageBox::CheckLimit(std::vector<ResultBox>::iterator cur, std::vector<ResultBox>::iterator end)
{uint count = 0;uint level = cur->level;cur = std::next(cur);while (cur != end && cur->level == level){++cur; ++count;}return count;
}StorageBox::ResultBox StorageBox::PackBox(std::vector<Item> & items, uint level)
{ResultBox retBox;retBox.level = level;std::vector<ResultItem> retItems;ResultItem retItem;retItem.i = (uint)~0;retItem.x = 0;retItem.y = 0;retItem.w = PACK_LEVEL[level][0];retItem.h = PACK_LEVEL[level][1];retItems.push_back(retItem);auto itemIndex = 0u;ResultItem retItem0;ResultItem retItem1;while (itemIndex != items.size()){auto isNewItem = false;for (auto it = retItems.begin(); it != retItems.end(); ++it){if (it->AddItem(items.at(itemIndex), &retItem0, &retItem1)){isNewItem = true;//  添加到收纳箱retBox.items.push_back(*it);retItems.erase(it);//  新增2个新收纳箱retItems.push_back(retItem0);retItems.push_back(retItem1);SortItems(retItems);//  删除物品items.erase(items.begin() + itemIndex);break;}}if (!isNewItem) { ++itemIndex; }}return retBox;
}void StorageBox::PackBox(std::vector<Item>& items, uint level, std::vector<ResultBox>& retBoxs)
{SortItems(items);while (!items.empty()){retBoxs.push_back(PackBox(items, level == 0? CheckLevel(items): level)); level = 0;}
}void StorageBox::UnpackBox(std::vector<Item> & items, std::vector<ResultBox>::iterator cur, std::vector<ResultBox>::iterator end)
{for (; cur != end; ++cur){for (auto & retItem : cur->items){if (retItem.IsReady()){Item item;item.i = retItem.i;item.w = retItem.w;item.h = retItem.h;items.push_back(item);}}}
}

这个算法并不能得到最优排列组合,但是这个算法简单而且在大多数情况下都够用。

转载于:https://www.cnblogs.com/mmc1206x/p/10847543.html

算法 数据结构——收纳箱算法???相关推荐

  1. 算法 | 数据结构与算法(代码版)

    ================================================= 博主github:https://github.com/MichaelBeechan 博主CSDN: ...

  2. C语言单链表实现FCFS算法,数据结构与算法复习题(含答案).doc

    <数据结构与算法>2015-2016学年第1学期考试复习题 选择题(下面各小题有一个正确答案,请将正确答案的编号填写在各小题的括号内). 1.在一棵具有5层的满二叉树中结点总数为( A ) ...

  3. 贪心算法设计作业调度c语言,贪心算法 - 数据结构与算法教程 - C语言网

    1.简介 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解. 贪心算法不是对所有问题都能得到整体最优 ...

  4. 二叉树的建立和遍历算法 - 数据结构和算法47

    二叉树的建立和遍历算法 让编程改变世界 Change the world by program   有童鞋会说,我们上节课研究这么多遍历的方法干啥呢?聪明的鱼油们怎么看?! 对于二叉树,思路方面我们已 ...

  5. Day600601.马踏棋盘算法 -数据结构和算法Java

    马踏棋盘算法 图的深度优先DFS 回溯 八皇后问题.小老鼠找迷宫问题 一.介绍 二.思路分析 三.代码实现 package com.achang.algorithm;import java.awt.* ...

  6. Day595.普利姆算法 -数据结构和算法Java

    普利姆算法 一.问题引出 二.最小生成树 三.普利姆算法介绍 四.图解分析 五.代码实现 package com.achang.algorithm;import java.util.Arrays;/* ...

  7. 数据结构与算法笔记 - 绪论

    数据结构与算法笔记 - 绪论 1. 什么是计算 2. 评判DSA优劣的参照(直尺) 3. 度量DSA性能的尺度(刻度) 4. DSA的性能度量的方法 5. DSA性能的设计及其优化 x1. 理论模型与 ...

  8. JavaScript数据结构和算法

    前言 在过去的几年中,得益于Node.js的兴起,JavaScript越来越广泛地用于服务器端编程.鉴于JavaScript语言已经走出了浏览器,程序员发现他们需要更多传统语言(比如C++和Java) ...

  9. JAVA数据结构与算法【简单介绍】

    前几天去面一个大厂,面试官特别好,面试官说到,我们的学习不能本末倒置,数据结构和算法是程序的基础,如果数据结构你没有学好,你真正意义上不算会写代码.你的代码是各处粘贴,杂乱无章的. 由于现在大多用JA ...

最新文章

  1. 大数据笔记2019.5.7
  2. [python数据分析] 简述幂率定律及绘制Power-law函数
  3. linux系统 硬链接和软链接
  4. php图片中不显示文字内容,水印效果 只有图片,文字不显示
  5. 杭州师范大学计算机与科学,杭州师范大学信息科学与工程学院
  6. 对做“互联网产品”的一些想法
  7. 拓端tecdat|R语言中GLM(广义线性模型),非线性和异方差可视化分析
  8. html5 video标签嵌入视频
  9. matlab simulink 单气室油气弹簧阻尼特性分析
  10. 详谈软件工程之系统设计模式
  11. 加ing形式的单词有哪些_哪些单词是动词加 -ing 变形容词,在加 -ly 变副词的?能不能列举一些,有十几个就可以了。?...
  12. 满足国六标准通用型故障诊断仪:Q-OBD
  13. Requirement diagram
  14. 数字加字母文件 服务器,unraid服务器all in one 篇四:1秒钟完美解决jellyfin字幕乱码...
  15. 常用的第三方SDK介绍(搜集中)
  16. 【华为OD机试真题 python】连续出牌数量【2022 Q4 | 200分】
  17. python人脸识别项目_face++与python实现人脸识别签到(考勤)功能
  18. 计算机网络测试题第五章答案,计算机组成原理练习题答案第五章.doc
  19. 计算机专业云平台管理试题,练习题云平台/计算机软考考试试题-考试系统
  20. 点与有向线段的位置关系

热门文章

  1. iphone装android,iPhone上安装Android系统详细步骤。
  2. 大数据Spark(三十一):Spark On Hive
  3. GP(greenplum)4 for RH Linux 6 安装详细过程
  4. 51nod 1015 水仙花数
  5. 认识Buildroot
  6. BUUCTF Web [MRCTF2020]Ez_bypass1 [GXYCTF2019]BabySQli1 [GXYCTF2019]BabyUpload1
  7. SP 导出 PS 有缝隙的问题
  8. 静态图片转3D动态GIF/视频
  9. 微信小程序循环数组+删除
  10. python代码设置超参数_Python 机器学习:超参数调优