我有一个简单的(暴力)递归求解器算法,需要花费大量时间来获得更大的OpxCnt变量值。对于OpxCnt的小值,没问题,就像魅力一样。随着OpxCnt变量变大,算法变得非常慢。这是预期但任何优化或不同的算法?
我的最终目标是::我想通过读取地图数组中的所有True值 执行一些具有最小操作的读操作 成本。这与最小读取操作次数不同。 在功能完成时,应该没有未读的True值。
map数组由一些外部函数填充,任何成员可以是1或0。
例如::
map [4] = 1; map [8] = 1;
具有Adr = 4的1读取操作,Cnt = 5具有最低成本(35)
,而
2次读取操作,其中Adr = 4,Cnt = 1& Adr = 8,Cnt = 1成本(27 + 27 = 54)
#include <string.h>
typedef unsigned int Ui32;
#define cntof(x) (sizeof(x) / sizeof((x)[0]))
#define ZERO(x) do{memset(&(x), 0, sizeof(x));}while(0)
typedef struct _S_MB_oper{
Ui32 Adr;
Ui32 Cnt;
}S_MB_oper;
typedef struct _S_MB_code{
Ui32 OpxCnt;
S_MB_oper OpxLst[20];
Ui32 OpxPay;
}S_MB_code;
char map[65536] = {0};
static int opx_ListOkey(S_MB_code *px_kod, char *pi_map)
{
int cost = 0;
char map[65536];
memcpy(map, pi_map, sizeof(map));
for(Ui32 o = 0; o < px_kod->OpxCnt; o++)
{
for(Ui32 i = 0; i < px_kod->OpxLst[o].Cnt; i++)
{
Ui32 adr = px_kod->OpxLst[o].Adr + i;
// ...
if(adr < cntof(map)){map[adr] = 0x0;}
}
}
for(Ui32 i = 0; i < cntof(map); i++)
{
if(map[i] > 0x0){return -1;}
}
// calculate COST...
for(Ui32 o = 0; o < px_kod->OpxCnt; o++)
{
cost += 12;
cost += 13;
cost += (2 * px_kod->OpxLst[o].Cnt);
}
px_kod->OpxPay = (Ui32)cost; return cost;
}
static int opx_FindNext(char *map, int pi_idx)
{
int i;
if(pi_idx < 0){pi_idx = 0;}
for(i = pi_idx; i < 65536; i++)
{
if(map[i] > 0x0){return i;}
}
return -1;
}
static int opx_FindZero(char *map, int pi_idx)
{
int i;
if(pi_idx < 0){pi_idx = 0;}
for(i = pi_idx; i < 65536; i++)
{
if(map[i] < 0x1){return i;}
}
return -1;
}
static int opx_Resolver(S_MB_code *po_bst, S_MB_code *px_wrk, char *pi_map, Ui32 *px_idx, int _min, int _max)
{
int pay, kmax, kmin = 1;
if(*px_idx >= px_wrk->OpxCnt)
{
return opx_ListOkey(px_wrk, pi_map);
}
_min = opx_FindNext(pi_map, _min);
// ...
if(_min < 0){return -1;}
kmax = (_max - _min) + 1;
// must be less than 127 !
if(kmax > 127){kmax = 127;}
// is this recursion the last one ?
if(*px_idx >= (px_wrk->OpxCnt - 1))
{
kmin = kmax;
}
else
{
int zero = opx_FindZero(pi_map, _min);
// ...
if(zero > 0)
{
kmin = zero - _min;
// enforce kmax limit !?
if(kmin > kmax){kmin = kmax;}
}
}
for(int _cnt = kmin; _cnt <= kmax; _cnt++)
{
px_wrk->OpxLst[*px_idx].Adr = (Ui32)_min;
px_wrk->OpxLst[*px_idx].Cnt = (Ui32)_cnt;
(*px_idx)++;
pay = opx_Resolver(po_bst, px_wrk, pi_map, px_idx, (_min + _cnt), _max);
(*px_idx)--;
if(pay > 0)
{
if((Ui32)pay < po_bst->OpxPay)
{
memcpy(po_bst, px_wrk, sizeof(*po_bst));
}
}
}
return (int)po_bst->OpxPay;
}
int main()
{
int _max = -1, _cnt = 0;
S_MB_code best = {0};
S_MB_code work = {0};
// SOME TEST DATA...
map[ 4] = 1;
map[ 8] = 1;
/*
map[64] = 1;
map[72] = 1;
map[80] = 1;
map[88] = 1;
map[96] = 1;
*/
// SOME TEST DATA...
for(int i = 0; i < cntof(map); i++)
{
if(map[i] > 0)
{
_max = i; _cnt++;
}
}
// num of Opx can be as much as num of individual bit(s).
if(_cnt > cntof(work.OpxLst)){_cnt = cntof(work.OpxLst);}
best.OpxPay = 1000000000L; // invalid great number...
for(int opx_cnt = 1; opx_cnt <= _cnt; opx_cnt++)
{
int rv;
Ui32 x = 0;
ZERO(work); work.OpxCnt = (Ui32)opx_cnt;
rv = opx_Resolver(&best, &work, map, &x, -42, _max);
}
return 0;
}
答案 0 :(得分:2)
您可以使用动态编程来计算涵盖map[]
中第一个i true值的最低成本。称之为f(i)。正如我将解释的那样,你可以通过查看所有f(j)来计算f(i)j&lt;我,所以这需要时间二次的真值数 - 比指数要好得多。你要找的最终答案是f(n),其中n是map[]
中真值的数量。
第一步是将map[]
预处理到真值位置列表中。 (可以在原始map[]
数组上执行DP,但如果真值稀疏且速度不快,则速度会慢一些。)
int pos[65536]; // Every position *could* be true
int nTrue = 0;
void getPosList() {
for (int i = 0; i < 65536; ++i) {
if (map[i]) pos[nTrue++] = i;
}
}
当我们仅查看第一个i真值时的子问题时,我们所知道的是,第i个真值必须由以i结尾的读数覆盖。该块可以从任何位置开始j <= i;我们不知道,所以我们必须测试他们中的所有我并选择最好的。这里启用DP的关键属性(Optimal Substructure)是在i尺寸子问题的任何最优解中的,如果覆盖第i个真值的读取从第j个真值开始,则前面的j-1为真值必须由(j-1)大小的子问题的最优解决方案覆盖。
所以:f(i)= min(f(j)+得分(pos(j + 1),pos(i)),最小值取自全部1 <= j
int scores[65537]; // We effectively start indexing at 1
scores[0] = 0; // Covering the first 0 true values requires 0 cost
// Calculate the minimum score that could allow the first i > 0 true values
// to be read, and store it in scores[i].
// We can assume that all lower values have already been calculated.
void calcF(int i) {
int bestStart, bestScore = INT_MAX;
for (int j = 0; j < i; ++j) { // Always executes at least once
int attemptScore = scores[j] + score(pos[j + 1], pos[i]);
if (attemptScore < bestScore) {
bestStart = j + 1;
bestScore = attemptScore;
}
}
scores[i] = bestScore;
}
int score(int i, int j) {
return 25 + 2 * (j + 1 - i);
}
int main(int argc, char **argv) {
// Set up map[] however you want
getPosList();
for (int i = 1; i <= nTrue; ++i) {
calcF(i);
}
printf("Optimal solution has cost %d.\n", scores[nTrue]);
return 0;
}
使用此方案,您可以计算最优解的得分:它只是f(n),其中n是map[]
中的真值数。为了实际构建解决方案,您需要回读f()分数表以推断出做出了哪个选择:
void printSolution() {
int i = nTrue;
while (i) {
for (int j = 0; j < i; ++j) {
if (scores[i] == scores[j] + score(pos[j + 1], pos[i])) {
// We know that a read can be made from pos[j + 1] to pos[i] in
// an optimal solution, so let's make it.
printf("Read from %d to %d for cost %d.\n", pos[j + 1], pos[i], score(pos[j + 1], pos[i]));
i = j;
break;
}
}
}
}
可能有几种可能的选择,但所有这些选择都会产生最佳解决方案。
上述解决方案适用于任意评分功能。因为您的评分函数具有简单的结构,所以可能会开发更快的算法。
例如,我们可以证明存在一个间隙宽度,高于该间隙宽度总是有利于将单个读取分成两个读取。假设我们从位置x-a到x读取,并且从位置y到y + b读取另一个,其中y> 0。 X。这两个独立读数的组合成本是25 + 2 *(a + 1)+ 25 + 2 *(b + 1)= 54 + 2 *(a + b)。从x-a到y + b的单个读取将花费25 + 2 *(y + b-x + a + 1)= 27 + 2 *(a + b)+ 2 *(y-x)。因此单次读取成本较低,为27 - 2 *(y - x)。如果y - x> 13,这种差异低于零:换句话说,包含跨越12或更大间隙的单个读数绝不是最佳的。
要在calcF()
内使用此属性,可以按开始位置的递减顺序(即按宽度的递增顺序)尝试最终读取,并且一旦间隙宽度超过,内部循环就会停止12.由于读取和所有后续更广泛的读取都会包含这个太大的差距,因此不是最理想的,因此无需尝试。