本文不再叙述具体的分酒问题,直接推广至n个容器的分酒问题解决方案。
首先,我们看看dWAlgorithm.h中的构成。
typedef vector<int> POINTS;
typedef vector<POINTS> PATHS;
typedef vector<PATHS> TREES;
给vector容器起别名,后面都是使用这三个,请注意。
PATHS operatorProduce(int, bool),该函数是产生操作算符,也就是液体转移的算符。对于n个容器的情形,这个函数最多能产生n^2个算符。
PATHS targetProduce(POINTS, const POINTS &),这个函数输出一个目标状态集合,设计这个的主要目的是用于处理一些特殊的情形。
例如,我们需要把酒分成(4,3,2),但我们不关心4,3,2的位置,或者说在哪个容器内,那么可能的目标状态就包含(4,2,3)、(2,3,4)、(2,4,3)、
(3,4,2)、(3,2,4)这剩下的5个。
POINTS operate(POINTS, const POINTS &, const POINTS &),这个函数是使用操作算符对状态进行转移的函数,同时,操作的结果是受容器容积限制的。
bool checkTP(const POINTS &, const PATHS &)这个函数是检查一个状态是否存在于一个状态集合中。
bool checkLimite(const POINTS &, const POINTS &),该函数用于判断状态是否超出容器限制,operate函数使用了该函数。
int sumP(const POINTS &),计算某一状态的酒水总量。
PATHS statusGenerate(const POINTS &, const int, const int),该函数用于产生酒水总数在一定范围内的状态集合。设计这个函数的主要目的是为了找
出最大的步数,通常的分酒问题是不需要该函数的。
PATHS opG(int, bool),是为提高statusGenerate函数效率而设计的,opS是用于产生状态的。
最后说一下分酒主函数。
bool divideWinePath(const POINTS &, const POINTS &, const POINTS &, TREES &, PATHS &, bool );
首先,广度优先搜索就是完整扩展了一层结点之后,才会继续扩展下一层结点,这和深度优先是不同的。
如果直接应用广度优先搜索,并且不及时剪除掉一些无用的枝,会导致数据量过大。笔者在没有考虑剪枝策略的情况下,分酒问题进行了求解,但是对于4
容器,步数在13以上的路径,其内存占用量能达到15GB,这对于实际的问题解决是不好的。所以,为了优化算法,必须除去一部分的结点来减少内存占用。
这里采用的剪枝策略为,如果在之前的所有节点中存在和刚扩展的节点一样的节点,那么就将该节点去除。因为我们需要找的是最短的路径,如果刚扩展的
节点在之前出现过,那么这个节点所在路径肯定不是最短的路径。
如果直接加入这个剪枝策略,效果并不是很理想,因为,随着树的扩张,节点数量也是快速增长的,那么当层数达到一定时,就需要花费大量的时间去对比
之前的节点。然而,通过产生的状态集,我们知道,和大量的节点数相比,状态集的容量是很小的。于是,我们可以用一个状态集合来表示之前出现过的节
点。当出现了新的节点,并且在状态集合中没有出现过,那么就把该节点加入到这个状态集合中。这样,我们就通过一个小容量的集合来取代了大容量的树
。这样就解决了函数运行到后期效率过低的问题。
divideWinePath函数的参数依次为初始状态,容量限制,初始目标状态,输出结果树,输出有效状态集合,目标产生选项(true表示多目标,false表示单目
标)。返回值为true表示存在最小路径,false表示不存在路径。
dWAlgorithm.h
#ifndef DWALGORITHM_H_INCLUDED
#define DWALGORITHM_H_INCLUDED
#include <iostream>
#include <vector>
#include <algorithm>
namespace divideWine
{
using std::vector;
using std::cout;
using std::cin;
using std::endl;
typedef vector<int> POINTS;
typedef vector<POINTS> PATHS;
typedef vector<PATHS> TREES;
PATHS operatorProduce(int, bool);
PATHS targetProduce(POINTS, const POINTS &);
POINTS operate(POINTS, const POINTS &, const POINTS &);
bool checkTP(const POINTS &, const PATHS &);
bool checkLimite(const POINTS &, const POINTS &);
int sumP(const POINTS &);
PATHS statusGenerate(const POINTS &, const int, const int);
PATHS startStatusGenerate(const POINTS &);
PATHS selectStartStatus(const PATHS &, int);
POINTS binary2Decimal(int, int);
POINTS myDot(const POINTS &, const POINTS &);
PATHS opG(int, bool);
POINTS opS(const POINTS &, const POINTS &);
bool judge(int, int, int);
bool _judge(bool, int, int, int);
//divideWinePath是主接口,参数依次为开始状态,各瓶容量(限制),目标状态,结果,这个忘记了,最后一个bool值是为了切换函数的运行模式,True是一次搜索多个目标,为了找出当前容量限制下的最大搜索长度,False就是一般的搜索形式,即按照给定的target去搜索。函数会返回一个bool值来提示是否找到路径。
bool divideWinePath(const POINTS &, const POINTS &, const POINTS &, TREES &, PATHS &, bool );
}
#endif // DWALGORITHM_H_INCLUDED
dWAlgorithm.cpp
#include "dWAlgorithm.h"
using divideWine::PATHS;
using divideWine::POINTS;
//using divideWine::TREES;
PATHS divideWine::operatorProduce(int n, bool flag)
{
PATHS operate;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
POINTS point;
point.resize(n, 0);
if (i != j)
{
point[i] = 1;
point[j] = -1;
operate.push_back(point);
}
}
}
if (flag)
{
for (int i = 0; i < n; i++)
{
POINTS point;
point.resize(n, 0);
point[i] = -1;
operate.push_back(point);
}
}
return operate;
}
PATHS divideWine::targetProduce(POINTS point, const POINTS &limite)
{
PATHS targets;
sort(point.begin(), point.end());
while (next_permutation(point.begin(), point.end()))
{
if (checkLimite(point, limite))
targets.push_back(point);
}
if (targets.empty() && checkLimite(point, limite))
targets.push_back(point);
return targets;
}
POINTS divideWine::operate(POINTS point, const POINTS &op, const POINTS &limite)
{
int n = point.size();
while (1)
{
for (int i = 0; i < n; i++)
point[i] += op[i];
if (!checkLimite(point, limite))
break;
}
for (int i = 0; i < n; i++)
point[i] -= op[i];
return point;
}
bool divideWine::checkTP(const POINTS &point, const PATHS &path)
{
int n = path.size();
for (int i = 0; i < n; i++)
if (path[i] == point)
return true;
return false;
}
bool divideWine::checkLimite(const POINTS &point, const POINTS &limite)
{
int n = point.size();
for (int i = 0; i < n; i++)
if (point[i] > limite[i] || point[i] < 0)
return false;
return true;
}
int divideWine::sumP(const POINTS &point)
{
int c = 0;
int n = point.size();
for (int i = 0; i < n; i++)
c += point[i];
return c;
}
PATHS divideWine::statusGenerate(const POINTS &limite, const int sumS, const int sumT)
{
PATHS status, tempPath, path;
int max_value = std::max(sumS, sumT);
int min_value = std::min(sumS, sumT);
POINTS startPoint;
int k, h;
bool flag = judge(min_value, max_value, sumP(limite));
if (flag)
{
startPoint.resize(limite.size(), 0);
k = sumP(startPoint);
h = 1;
}
else
{
startPoint = limite;
k = sumP(limite);
h = -1;
}
path.push_back(startPoint);
PATHS op = opG(limite.size(), flag);
int N_1 = op.size();
while (_judge(flag, min_value, max_value, k))
{
tempPath.clear();
swap(tempPath, path);
k += h;
int tempN_1 = tempPath.size();
for (int i = 0; i < tempN_1; i++)
{
for (int j = 0; j < N_1; j++)
{
POINTS tempPoint = opS(tempPath[i], op[j]);
if (!checkLimite(tempPoint, limite))
continue;
if (path.empty())
{
path.push_back(tempPoint);
continue;
}
int position = find(path.begin(), path.end(), tempPoint) - path.begin();
if (position == path.size())
path.push_back(tempPoint);
}
}
if (k >= min_value && k <= max_value)
status.insert(status.end(), path.begin(), path.end());
}
path.clear();
tempPath.clear();
return status;
}
PATHS divideWine::opG(int n, bool flag)
{
PATHS path;
int h;
if (flag)
h = 1;
else
h = -1;
for (int i = 0; i < n; i++)
{
POINTS point;
point.resize(n, 0);
point[i] = h;
path.push_back(point);
}
return path;
}
POINTS divideWine::opS(const POINTS &point, const POINTS &operators)
{
POINTS p;
int n = point.size();
for (int i = 0; i < n; i++)
p.push_back(point[i] + operators[i]);
return p;
}
bool divideWine::judge(int min_value, int max_value, int sumLimite)
{
if (max_value <= sumLimite - min_value)
return true;
else
return false;
}
bool divideWine::_judge(bool flag, int min_value, int max_value, int k)
{
if (flag)
if (k < max_value)
return true;
else
return false;
else if (k > min_value)
return true;
else
return false;
}
bool divideWine::divideWinePath(const POINTS &pointStart, const POINTS &limite, const POINTS &targetFirst, TREES &tree, PATHS &statusValid, bool targetFlag)
{
tree.clear();
statusValid.clear();
TREES tempTree;
PATHS targets, operators;
PATHS test, tempStatus;
test.push_back(pointStart);
tree.push_back(test);
test.clear();
statusValid.push_back(pointStart);
bool opFlag;
if (sumP(pointStart) > sumP(targetFirst))
opFlag = true;
else if (sumP(pointStart) == sumP(targetFirst))
opFlag = false;
else
return false;
operators = operatorProduce(pointStart.size(), opFlag);
if(targetFlag)
targets = targetProduce(targetFirst, limite);
else
targets.push_back(targetFirst);
if (pointStart == targetFirst)
return true;
auto n = operators.size();
int flag = 0, _flag = 0;
while (flag == 0 && tree.size() > 0)
{
tempTree.clear();
swap(tempTree, tree);
tempStatus.clear();
auto tempN_1 = tempTree.size();
for (int i = 0; i < tempN_1; i++)
{
PATHS tempPath = tempTree[i];
for (int j = 0; j < n; j++)
{
POINTS tempPoint = operate(tempPath.back(), operators[j], limite);
bool tempFlag = checkTP(tempPoint, targets);
if (sumP(tempPoint) < sumP(targetFirst))
continue;
if (tempFlag)
{
_flag = 1;
flag = 1;
}
if (_flag == 1)
{
_flag = 0;
PATHS _tempPath = tempPath;
_tempPath.push_back(tempPoint);
tree.push_back(_tempPath);
if (tempStatus.empty())
{
tempStatus.push_back(tempPoint);
continue;
}
auto position = find(tempStatus.begin(), tempStatus.end(), tempPoint) - tempStatus.begin();
if (position == tempStatus.size())
tempStatus.push_back(tempPoint);
}
else if (_flag == 0 && flag == 1)
{
if (tempFlag)
{
PATHS _tempPath = tempPath;
_tempPath.push_back(tempPoint);
tree.push_back(_tempPath);
if (tempStatus.empty())
{
tempStatus.push_back(tempPoint);
continue;
}
auto position = find(tempStatus.begin(), tempStatus.end(), tempPoint) - tempStatus.begin();
if (position == tempStatus.size())
tempStatus.push_back(tempPoint);
}
}
else
{
if (!checkTP(tempPoint, statusValid))
{
PATHS _tempPath = tempPath;
_tempPath.push_back(tempPoint);
tree.push_back(_tempPath);
if (tempStatus.empty())
{
tempStatus.push_back(tempPoint);
continue;
}
auto position = find(tempStatus.begin(), tempStatus.end(), tempPoint) - tempStatus.begin();
if (position == tempStatus.size())
tempStatus.push_back(tempPoint);
}
}
}
}
if (!tempStatus.empty())
statusValid.insert(statusValid.end(), tempStatus.begin(), tempStatus.end());
}
tempStatus.clear();
tempTree.clear();
swap(tempTree, tree);
auto tempN_2 = tempTree.size();
for (int i = 0; i < tempN_2; i++)
if (checkTP(tempTree[i].back(), targets))
tree.push_back(tempTree[i]);
if (tree.size() == 0)
return false;
tempTree.clear();
return true;
}
一个调用的例子如下。
#include "dwalgorihm.h"
#include <iostream>
#include <string>
using namespace std;
using divideWine::PATHS;
using divideWine::POINTS;
using divideWine::TREES;
void printPoint(POINTS point);
void printPath(PATHS path);
int main()
{
const POINTS start_status{ 0, 10, 0, 3 };//开始状态
const POINTS limite{ 12, 10, 6, 3 };//容量限制
const POINTS target{ 4, 1, 4, 3 };//目标状态
TREES result;//结果
PATHS p;//忘了是个啥了
if (divideWine::divideWinePath(start_status, limite, target, result, p, false))
//设定为False,明确函数的运行模式,具体内容在H文件中有写到。
{
cout << "From";
printPoint(start_status);
cout << " To ";
printPoint(target);
cout << " limite is:";
printPoint(limite);
cout << endl;
cout << "Find " << result.size() << " method to devide the wine." << endl;
for (int i = 0; i < result.size(); i++)
{
string str;
if (i == 0)
str = "st";
else if (i == 1)
str = "nd";
else if (i == 2)
str = "rd";
else if (i >= 3)
str = "th";
cout << i + 1 << str << " method is:";
printPath(result[i]);
}
}
return 0;
}
void printPath(PATHS path)
//打印输出一个具体方法。
{
for (int i = 0; i < path.size(); i++)
{
printPoint(path[i]);
if (i < path.size() - 1)
cout << "->";
if (i == path.size() - 1)
cout << endl;
}
}
void printPoint(POINTS point)
//打印输出某一个点
{
for (int j = 0; j < point.size(); j++)
{
if (j == 0)
cout << "[";
cout << point[j];
if (j == point.size() - 1)
cout << "]";
else
cout << ",";
}
}
运行界面:
当时还写了一个基于Qt的GUI界面,有需要的可以拿去瞅瞅(代码找不见了,只有exe文件,不过逻辑代码是从这里搬的,有需要你们可以自己去写一个,谢谢大家。)
code和GUI都提供了百度网盘链接。
链接: https://pan.baidu.com/s/12bSvwAf_AVxhd9F1eGuYHA 提取码: t5bn