转珠路径用什么算法?
从上一章我们创造了模拟游戏程序模拟获取结果,这一节我们需要利用上它们。
因为单从数组上考虑,我们需要在有限步骤内计算全排列(Am*An)/4的解(m行n列,对称情况排除).为了达到最优解可能转珠路径会重复。每次迭代路径都会有3个选择,所以时间复杂度为指数级。
也就是说纯暴力递归只可以求得指定步数内的最优解,达到最优解可能会需要用到强化学习或者记忆化。
问题简化
在开始暴力递归枚举之前,我们先把原问题简化一下:
在MxN的数组中,任意交换两个元素N次后,求可以得到的最大解(对交换后的数组执行上一章的Calculate()方法)
该问题很简单,可以用普通DFS去做即可
伪代码如下:
void dfs(数组){
if(达到最大步数)return;
int max;
for(任意一点的四个方向){
result = Calculate(某方向移动后的数组)
max保留最大值
dfs(某方向移动后的数组)
}
}
分析一下可以知道,原问题为3^n级指数级的递归
最大堆栈会达到2^n级,所以这里可以通过手动压栈+优化数组保存方式避免内存溢出。
上述问题只需要限制:
@每次移动只可以选取上一次移动的珠子
即可转换为转珠算法问题
看看运行效率
我们先用上述算法跑一边试试:
10步内:
11步的话:
可以看到超过10步就跑的很慢了,而且combo也很低。看看哪边可以优化?
优化
因为暴力优化没办法获取最优解,所以我们需要放弃最优解去最快速度寻找次优解。所以优化可以从下面几个方面入手:
1.放弃一定次数以上的重复路径
2.找到次优解立即返回结果
3.记忆化存储已经找过的解
4.用位存储优化计算
5.丢弃造成重复版面的路径
6.贪心:如果下一次迭代会减少combo,那么丢弃它
看看效果
10步内:
适用贪心算法后:
代码
int logicMaxCombo, maxStep;
pair<int, int> firstStep;
vector<vector<int>> randomVector(int m, int n, int l, int r) //生成M*N范围在l:r的随机数组
{
cout << "生在生成随机数组" << endl;
srand(time(0)); //设置时间种子
vector<vector<int>> res;
for (int i = 0; i < m; i++)
{
vector<int> tmp;
for (int j = 0; j < n; j++)
{
tmp.push_back(rand() % (r - l + 1) + l);
}
res.push_back(tmp);
}
return res;
}
//生成初始数组
vector<vector<int>> initGenerator(int m, int n, int l, int r)
{
int limit = 100;
srand(time(0)); //设置时间种子
vector<vector<int>> init;
init = randomVector(m, n, l, r);
int times = 1000;
while (calCulate(init, r - l + 1, false) != 0 && times--)
{
finish(init, false);
for (int i = 0; i < init.size(); i++)
for (int j = 0; j < init[0].size(); j++)
if (init[i][j] == 0)
init[i][j] = l + rand() % (r - l + 1);
}
// if(calCulate(tmp,r-l+1,false)==0) return init;
return init;
}
//计算理论最大combo
int calculateLogicMaxcombo(vector<vector<int>> matrix, int limit = 3)
{
unordered_map<int, int> mymap;
for (auto it : matrix)
for (auto iit : it)
{
mymap[iit]++;
}
int res = 0;
unordered_map<int, int>::iterator iter = mymap.begin();
while (iter != mymap.end())
{
if (iter->second >= limit)
res += iter->second / limit;
;
iter++;
}
return res;
}
//起手珠选取,这里待补全,先用随机函数模拟
pair<int, int> chooseFirstStep