将不同颜色的2D阵列最佳地转换为单一颜色
Turning a 2D array of different color into a single color optimally
我正在努力寻找益智游戏"Flood It"的解决方案。主要的想法是把一个由k种不同颜色组成的N*M游戏板变成一种单一的颜色。我必须从板的左上角开始,将相同颜色的块变成相邻节点的颜色之一,然后继续前进,最终将整个板淹没为单一颜色。例如:
Initial Board:
1 1 1 2 2 3
1 1 2 3 4 5
1 1 1 1 3 4
1 4 3 2 1 5
2 3 4 5 1 2
Final Board:
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
其中1,2,3,4,5表示不同的颜色。我准备了一个C++代码,用于找出板上任何位置相同颜色块的面积。这可以首先应用于左上角的单元格,然后应用于该单元格的相邻节点,以泛洪显示颜色。我的代码如下:
#include <cstdint>
#include <vector>
#include <queue>
#include <string>
#include <iostream>
typedef std::vector<int32_t> vec_1d;
typedef std::vector<vec_1d> vec_2d;
// Print the 2d vector with a label
void dump(std::string const& label, vec_2d const& v)
{
std::cout << label << "n";
for (std::size_t y(0); y < v.size(); ++y) {
for (std::size_t x(0); x < v[0].size(); ++x) {
std::cout << v[y][x] << " ";
}
std::cout << "n";
}
std::cout << "n";
}
// Recursive implementation of the search
void find_connected_r(int32_t target_color
, std::size_t x
, std::size_t y
, vec_2d const& colors
, vec_2d& result)
{
if ((result[y][x] == 1) || (colors[y][x] != target_color)) {
return;
}
result[y][x] = 1;
std::size_t width(colors[0].size());
std::size_t height(colors.size());
if (x > 0) {
find_connected_r(target_color, x - 1, y, colors, result);
}
if (y > 0) {
find_connected_r(target_color, x, y - 1, colors, result);
}
if (x < (width - 1)) {
find_connected_r(target_color, x + 1, y, colors, result);
}
if (y < (height - 1)) {
find_connected_r(target_color, x, y + 1, colors, result);
}
}
// Entry point to the search, select the implementation with last param
vec_2d find_connected(std::size_t x, std::size_t y, vec_2d const& colors, bool recursive)
{
if (colors.empty() || colors[0].empty()) {
throw std::runtime_error("Invalid input array size");
}
int32_t target_color(colors[y][x]);
vec_2d result(colors.size(), vec_1d(colors[0].size(), 0));
if (recursive) {
find_connected_r(target_color, x, y, colors, result);
}
else {
find_connected(target_color, x, y, colors, result);
}
return result;
}
void dump_coordinates(std::string const& label, vec_2d const& v)
{
std::cout << label << "n";
for (std::size_t y(0); y < v.size(); ++y) {
for (std::size_t x(0); x < v[0].size(); ++x) {
if (v[y][x]) {
std::cout << "(" << x << ", " << y << ") ";
}
}
}
std::cout << "n";
}
int main()
{
vec_2d colors{
{ 1, 1, 1, 1, 1, 1 }
, { 2, 2, 2, 3, 3, 1 }
, { 1, 1, 1, 1, 3, 1 }
, { 1, 3, 3, 3, 3, 1 }
, { 1, 1, 1, 1, 1, 1 }
};
}
如何通过检查相邻节点将整个板/矩阵变成单一颜色?
解决这个谜题的一个可能的顶级算法是重复以下步骤,直到整个板上只有一种颜色:
- 查找所有连续的颜色区域。将(0,0)处的区域视为主要区域,将所有其他区域视为次要区域
- 拾取颜色与主区域颜色不同的最大(按瓷砖计数)辅助区域。让我们将这个次要区域的颜色命名为new_color
- 将主区域重新着色为new_color
查找所有区域
我们应该保留一个cumulate_mask来跟踪已经被识别为某个区域一部分的所有瓦片。
首先,我们找到主区域,从(0,0)开始搜索,并用结果更新我们的accumulate_mask。
然后重复,直到找不到更多区域:
- 查找累积_mask中第一个零瓦片的位置,该累积_mask在主区域掩码中至少有一个非零瓦片
- 查找从此位置开始的区域
- 使用此区域的掩码更新accumulate_mask
选择颜色
只需在次要区域中迭代,找到计数最大的区域,该区域的颜色与主要区域不同。
代码
(也在coliru上)
注:有意以某种方式书写,使理解算法成为可能。这肯定是可以重构的,而且它缺少了很多错误检查。
#include <cstdint>
#include <vector>
#include <queue>
#include <string>
#include <iostream>
typedef std::vector<int32_t> vec_1d;
typedef std::vector<vec_1d> vec_2d;
typedef std::pair<std::size_t, std::size_t> position;
position const INVALID_POSITION(-1, -1);
int32_t const INVALID_COLOR(0);
// ============================================================================
struct region_info
{
int32_t color;
vec_2d mask;
std::size_t count() const
{
std::size_t result(0);
for (std::size_t y(0); y < mask.size(); ++y) {
for (std::size_t x(0); x < mask[0].size(); ++x) {
if (mask[y][x]) {
++result;
}
}
}
return result;
}
};
struct region_set
{
// The region that contains (0, 0)
region_info primary;
// All other regions
std::vector<region_info> secondary;
};
// ============================================================================
// Print the 2D vector with a label
void dump(std::string const& label, vec_2d const& v)
{
std::cout << label << "n";
for (std::size_t y(0); y < v.size(); ++y) {
for (std::size_t x(0); x < v[0].size(); ++x) {
std::cout << v[y][x] << " ";
}
std::cout << "n";
}
std::cout << "n";
}
// Print the coordinates of non-zero elements of 2D vector with a label
void dump_coordinates(std::string const& label, vec_2d const& v)
{
std::cout << label << "n";
for (std::size_t y(0); y < v.size(); ++y) {
for (std::size_t x(0); x < v[0].size(); ++x) {
if (v[y][x]) {
std::cout << "(" << x << ", " << y << ") ";
}
}
}
std::cout << "n";
}
void dump(region_info const& ri)
{
std::cout << "Region color: " << ri.color << "n";
std::cout << "Region count: " << ri.count() << "n";
dump("Region mask:", ri.mask);
}
void dump(region_set const& rs)
{
std::cout << "Primary Regionn" << "n";
dump(rs.primary);
for (std::size_t i(0); i < rs.secondary.size(); ++i) {
std::cout << "Secondary Region #" << i << "n";
dump(rs.secondary[i]);
}
}
// ============================================================================
// Find connected tiles - implementation
void find_connected(int32_t target_color
, std::size_t x
, std::size_t y
, vec_2d const& colors
, vec_2d& result)
{
std::size_t width(colors[0].size());
std::size_t height(colors.size());
std::queue<position> s;
s.push(position(x, y));
while (!s.empty()) {
position pos(s.front());
s.pop();
if (result[pos.second][pos.first] == 1) {
continue;
}
if (colors[pos.second][pos.first] != target_color) {
continue;
}
result[pos.second][pos.first] = 1;
if (pos.first > 0) {
s.push(position(pos.first - 1, pos.second));
}
if (pos.second > 0) {
s.push(position(pos.first, pos.second - 1));
}
if (pos.first < (width - 1)) {
s.push(position(pos.first + 1, pos.second));
}
if (pos.second < (height - 1)) {
s.push(position(pos.first, pos.second + 1));
}
}
}
// Find connected tiles - convenience wrapper
vec_2d find_connected(std::size_t x, std::size_t y, vec_2d const& colors)
{
if (colors.empty() || colors[0].empty()) {
throw std::runtime_error("Invalid input array size");
}
int32_t target_color(colors[y][x]);
vec_2d result(colors.size(), vec_1d(colors[0].size(), 0));
find_connected(target_color, x, y, colors, result);
return result;
}
// ============================================================================
// Change color of elements at positions with non-zero mask value to new color
vec_2d& change_masked(int32_t new_color
, vec_2d& colors
, vec_2d const& mask)
{
for (std::size_t y(0); y < mask.size(); ++y) {
for (std::size_t x(0); x < mask[0].size(); ++x) {
if (mask[y][x]) {
colors[y][x] = new_color;
}
}
}
return colors;
}
// Combine two masks
vec_2d combine(vec_2d const& v1, vec_2d const& v2)
{
vec_2d result(v1);
for (std::size_t y(0); y < v2.size(); ++y) {
for (std::size_t x(0); x < v2[0].size(); ++x) {
if (v2[y][x]) {
result[y][x] = v2[y][x];
}
}
}
return result;
}
// Find position of first zero element in mask
position find_first_zero(vec_2d const& mask)
{
for (std::size_t y(0); y < mask.size(); ++y) {
for (std::size_t x(0); x < mask[0].size(); ++x) {
if (!mask[y][x]) {
return position(x, y);
}
}
}
return INVALID_POSITION;
}
bool has_nonzero_neighbor(std::size_t x, std::size_t y, vec_2d const& mask)
{
bool result(false);
if (x > 0) {
result |= (mask[y][x - 1] != 0);
}
if (y > 0) {
result |= (mask[y - 1][x] != 0);
}
if (x < (mask[0].size() - 1)) {
result |= (mask[y][x + 1] != 0);
}
if (y < (mask.size() - 1)) {
result |= (mask[y + 1][x] != 0);
}
return result;
}
// Find position of first zero element in mask
// which neighbors at least one non-zero element in primary mask
position find_first_zero_neighbor(vec_2d const& mask, vec_2d const& primary_mask)
{
for (std::size_t y(0); y < mask.size(); ++y) {
for (std::size_t x(0); x < mask[0].size(); ++x) {
if (!mask[y][x]) {
if (has_nonzero_neighbor(x, y, primary_mask)) {
return position(x, y);
}
}
}
}
return INVALID_POSITION;
}
// ============================================================================
// Find all contiguous color regions in the image
// The region starting at (0,0) is considered the primary region
// All other regions are secondary
// If parameter 'only_neighbors' is true, search only for regions
// adjacent to primary region, otherwise search the entire board
region_set find_all_regions(vec_2d const& colors, bool only_neighbors = false)
{
region_set result;
result.primary.color = colors[0][0];
result.primary.mask = find_connected(0, 0, colors);
vec_2d cumulative_mask = result.primary.mask;
for (;;) {
position pos;
if (only_neighbors) {
pos = find_first_zero_neighbor(cumulative_mask, result.primary.mask);
} else {
pos = find_first_zero(cumulative_mask);
}
if (pos == INVALID_POSITION) {
break; // No unsearched tiles left
}
region_info reg;
reg.color = colors[pos.second][pos.first];
reg.mask = find_connected(pos.first, pos.second, colors);
cumulative_mask = combine(cumulative_mask, reg.mask);
result.secondary.push_back(reg);
}
return result;
}
// ============================================================================
// Select the color to recolor the primary region with
// based on the color of the largest secondary region of non-primary color
int32_t select_color(region_set const& rs)
{
int32_t selected_color(INVALID_COLOR);
std::size_t selected_count(0);
for (auto const& ri : rs.secondary) {
if (ri.color != rs.primary.color) {
if (ri.count() > selected_count) {
selected_count = ri.count();
selected_color = ri.color;
}
}
}
return selected_color;
}
// ============================================================================
// Solve the puzzle
// If parameter 'only_neighbors' is true, search only for regions
// adjacent to primary region, otherwise search the entire board
// Returns the list of selected colors representing the solution steps
vec_1d solve(vec_2d colors, bool only_neighbors = false)
{
vec_1d selected_colors;
for (int32_t i(0);; ++i) {
std::cout << "Step #" << i << "n";
dump("Game board: ", colors);
region_set rs(find_all_regions(colors, true));
dump(rs);
int32_t new_color(select_color(rs));
if (new_color == INVALID_COLOR) {
break;
}
std::cout << "Selected color: " << new_color << "n";
selected_colors.push_back(new_color);
change_masked(new_color, colors, rs.primary.mask);
std::cout << "n------------------------------------nn";
}
return selected_colors;
}
// ============================================================================
int main()
{
vec_2d colors{
{ 1, 1, 1, 1, 1, 1 }
, { 2, 2, 2, 3, 3, 1 }
, { 1, 1, 4, 5, 3, 1 }
, { 1, 3, 3, 4, 3, 1 }
, { 1, 1, 1, 1, 1, 1 }
};
vec_1d steps(solve(colors, true));
std::cout << "Solved in " << steps.size() << " step(s):n";
for (auto step : steps) {
std::cout << step << " ";
}
std::cout << "nn";
}
// ============================================================================
程序输出:
Step #0
Game board:
1 1 1 1 1 1
2 2 2 3 3 1
1 1 4 5 3 1
1 3 3 4 3 1
1 1 1 1 1 1
Primary Region
Region color: 1
Region count: 18
Region mask:
1 1 1 1 1 1
0 0 0 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
Secondary Region #0
Region color: 2
Region count: 3
Region mask:
0 0 0 0 0 0
1 1 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Secondary Region #1
Region color: 3
Region count: 4
Region mask:
0 0 0 0 0 0
0 0 0 1 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 0 0
Secondary Region #2
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Secondary Region #3
Region color: 3
Region count: 2
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 1 1 0 0 0
0 0 0 0 0 0
Secondary Region #4
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 1 0 0
0 0 0 0 0 0
Selected color: 3
------------------------------------
Step #1
Game board:
3 3 3 3 3 3
2 2 2 3 3 3
3 3 4 5 3 3
3 3 3 4 3 3
3 3 3 3 3 3
Primary Region
Region color: 3
Region count: 24
Region mask:
1 1 1 1 1 1
0 0 0 1 1 1
1 1 0 0 1 1
1 1 1 0 1 1
1 1 1 1 1 1
Secondary Region #0
Region color: 2
Region count: 3
Region mask:
0 0 0 0 0 0
1 1 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Secondary Region #1
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Secondary Region #2
Region color: 5
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Secondary Region #3
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 1 0 0
0 0 0 0 0 0
Selected color: 2
------------------------------------
Step #2
Game board:
2 2 2 2 2 2
2 2 2 2 2 2
2 2 4 5 2 2
2 2 2 4 2 2
2 2 2 2 2 2
Primary Region
Region color: 2
Region count: 27
Region mask:
1 1 1 1 1 1
1 1 1 1 1 1
1 1 0 0 1 1
1 1 1 0 1 1
1 1 1 1 1 1
Secondary Region #0
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Secondary Region #1
Region color: 5
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Secondary Region #2
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 1 0 0
0 0 0 0 0 0
Selected color: 4
------------------------------------
Step #3
Game board:
4 4 4 4 4 4
4 4 4 4 4 4
4 4 4 5 4 4
4 4 4 4 4 4
4 4 4 4 4 4
Primary Region
Region color: 4
Region count: 29
Region mask:
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 0 1 1
1 1 1 1 1 1
1 1 1 1 1 1
Secondary Region #0
Region color: 5
Region count: 1
Region mask:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Selected color: 5
------------------------------------
Step #4
Game board:
5 5 5 5 5 5
5 5 5 5 5 5
5 5 5 5 5 5
5 5 5 5 5 5
5 5 5 5 5 5
Primary Region
Region color: 5
Region count: 30
Region mask:
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
Solved in 4 step(s):
3 2 4 5
您的代码中有很多我不理解的地方,所以我不会试图修复它们,而是创建一个新函数,您可以比较两者。
// this function is called when the user inputs the x and y values
// the colors vector will be modified in place by reference
void change_color(int x, int y, vec_2d& colors)
{
int target_color = colors[x][y];
// call the recursive flood fill function
flood_fill(0, 0, target_color, colors);
}
//this function is the recursive flood fill
void flood_fill(int x, int y, const int target_color, vec_2d& colors)
{
// if the current tile is already the target color, do nothing
if (colors[x][y] == target_color) return;
// only need to go right and down, since starting from top left
// Also, only goes to the next tile if the next tile's color is
// the same as the current tile's color
if (x < colors.size()-1 && colors[x+1][y] == colors[x][y])
{
flood_fill(x+1, y, target_color, colors);
}
if (y < colors[0].size()-1 && colors[x][y+1] == colors[x][y])
{
flood_fill(x, y+1, target_color, colors);
}
// finally, fill in the current tile with target_color
colors[x][y] = target_color;
}
编辑:由于您的意思是要解决游戏而不是实现游戏。。。
随时记录板上仍有哪些颜色可用。在每个"转弯"处,从左上角开始,找到填充最多瓷砖区域的颜色。重复此步骤,直到所有瓷砖都填充了相同的颜色。这更像是一种蛮力方法,可能还有一种更优化的方法,但在我看来,这是最基本的方法。
相关文章:
- 将"打开的CV图像"中的"颜色"转换为整数格式
- GLSL 将 uint 转换为浮点颜色
- 颜色到灰度转换
- Qmage:使用颜色表从灰度转换为RGB
- OpenCV将一种颜色转换为任何其他颜色
- 从RGB到YUV(YCoCg)的颜色转换
- 如何将每个顶点的颜色转换为时间或某些功能
- 将RGB颜色图像转换为OpenCV C 中的索引颜色图像类型
- 如何将cv::Mat转换为带有颜色的pcl::点云
- 将整数转换为位图颜色 RGB 值 C++
- 将 32bpp 位图转换为 256 种颜色
- 将颜色图像转换为OpenCV中的灰度问题
- 处理 RGB 到 CIEL*a*b* 到 RGB "Out-of-Gamut"颜色转换
- 如何优化 YUV 到 RGB 颜色转换代码
- OpenCV将一种颜色转换为另一种颜色
- OpenGL颜色转换
- 将16位颜色转换为32位
- Nppi颜色转换问题
- 如何在C++中将16位十六进制颜色转换为RGB888值
- 颜色转换从ProPhoto RGB到监视器配置文件