C 有效的回溯算法

C 有效的回溯算法,c,algorithm,recursion,backtracking,C,Algorithm,Recursion,Backtracking,我有以下代码: #include <stdio.h> #include <stdlib.h> #include <limits.h> long result = LONG_MAX; void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int hasConnection(int *array, int arrayIndex, int maxBound, i

我有以下代码:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

long result = LONG_MAX;

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int hasConnection(int *array, int arrayIndex, int maxBound, int *rules, int size) {
    int connection;
    for (int i = 0; i < (size - 1)*2; i++) {
        if (rules[i] == array[arrayIndex]) {
            if (i % 2) {
                connection = rules[i - 1];
            } else {
                connection = rules[i + 1];
            }
            for (int j = maxBound - 1; j >= 0; j--) {
                if (array[j] == connection) {
                    return 1;
                }
            }
        }
    }
    return 0;
}

int isCrossed(int *array, int outConnectionIndex, int inConnectionIndex, int *rules, int size) {
    for (int i = inConnectionIndex + 1; i < outConnectionIndex; i++) {//sweep trough indexes in between
        if (hasConnection(array, i, inConnectionIndex, rules, size)) {//array[i] has connection with index lower than inConnectionIndex
            return 1;
        }
    }
    return 0;
}

int isWiredInsideAndCrossed(int *array, int arrayIndex, int *rules, int size) {
    int connection;
    for (int i = 0; i < 2 * (size - 1); i++) {
        if (rules[i] == array[arrayIndex]) {
            if (i % 2) {
                connection = rules[i - 1];
            } else {
                connection = rules[i + 1];
            }
            for (int j = 1; j < arrayIndex - 1; j++) {
                if (array[j] == connection) {
                    if (isCrossed(array, arrayIndex, j, rules, size)) {
                        return 1;
                    }
                }
            }
        }
    }
    return 0;
}

void trySequence(int * array, int size, int *priceMap, int *rules) {
    int ret = 0;
    for (int i = 0; i < size; i++) {
        ret = ret + priceMap[i * size + array[i]];
        if (ret >= result || isWiredInsideAndCrossed(array, i, rules, size)) {
            return;
        }
    }
    result = ret;
}

void permute(int *array, int i, int size, int *priceMap, int *rules) {
    if (size == i) {
        trySequence(array, size, priceMap, rules);
        return;
    }
    int j = i;
    for (j = i; j < size; j++) {
        swap(array + i, array + j);
        permute(array, i + 1, size, priceMap, rules);
        swap(array + i, array + j);
    }
    return;
}

int main(int argc, char** argv) {    
    int size;
    fscanf(stdin, "%d", &size);
    int *priceMap = malloc(sizeof (int)*size * size);
    int *rules = malloc(sizeof (int)*(size - 1)*2);
    int i = 0;
    int squaredSize = size*size;
    while (i < squaredSize) {
        scanf("%d", priceMap + i);
        i++;
    }
    i = 0;
    int rulesSize = (size - 1)*2;
    while (i < rulesSize) {
        scanf("%d", rules + i);
        i++;
    }
    int arrayToPermute [size];
    for (int j = 0; j < size; j++) {
        arrayToPermute[j] = j;
    }
    permute(arrayToPermute, 0, size, priceMap, rules);
    printf("%ld\n", result);
    return (EXIT_SUCCESS);
}
(解决方案是262) 在不到2s的时间内:

13
 52  9 42 65 54 47 16 62 35 47 63  2 48
 25  4 12 25 58 12 45 62 70 60 40 17 33
 28 64 64 62  1 28  3 26 56 15 59 64 17
  7 23 70 20 57 70 46  5  6  1 21 12 40
 62 53  5 15 22 43 57 15 26 42 51 16 38
 20 13 64  3 51 22 28  1 18 27  4 36  9
 11 20 41 65 29 63 54 28 31 63 27 59 41
 44 21 42 16 59 10 60 11  3 53 52 53 37
 41 51 18  4 38  6 22 49 15 51 54 61  7
 54  6  5 24 47 35 46 11 26 17 53 37 25
 34 42  6 54 40 47 59 25 53 53 37  9 64
 69 63 68  5 37 16 17 61 33 51 19 39 44
  6 47  4  6 21 17 23 24 13 29 34 54 33
0 1
0 2
0 3
1 4
1 5
1 6
2 7
2 8
2 9
3 10
3 11
3 12
(解决方案是165)
所以我想知道是否有人知道如何做到这一点,我完全迷路了?

好吧,你只是要求一个想法: 而不是所有的n!排列,你必须使用回溯来忽略那些肯定会使电线交叉的排列

i、 e.u有排列1 2 3 4。。。。11,并且1 2 3部分已经使导线交叉,所以你可以忽略4…的所有排列。。。。第11部分

下面是一些实现细节的伪代码:

int n;              // devices
int cost[n][n];     // cost for putting device i into slot j
bool used[n]={0};   // we need to keep track of used devices
int slots[n];       // tracks which device is in which slot
int edges[n-1];     //edges of a tree
int ats = INF;

bool cross(); //function that checks if any devices cross
              //it seems you already wrote something similar, so i skipped this

solve(int x, int total)     //function that tries putting remaining blocks into slot x
{
if(x==n) //all slots are filled means we`re done
{
ats = min(ats,total);
return;
}

if(total > ats)     // some pruning optimization for some speedup
return;             // cause no matter what we do we won`t be able to beat this cost


for(int i=0; i<n; i++)
if(!used[i])                    //if device is not used and
                                //we can try putting it into our slot
{
    slot[x] = i;
    used[i] = true;
    if(!cross())                //if putting device i into slot x makes some lines cross, skip it
    solve(x+1,total + cost[i][x]);
    used[i] = false;
}
}

main()
{

for (int i=0; i<n; i++) //try all devices into slot 0
{
used[i] = true;
slot[i]=0;
solve(1,cost[i][0]);
used[i] = false;
}

print(ats);

}
int n;//装置
整数成本[n][n];//将设备i放入插槽j的成本
布尔使用了[n]={0};//我们需要跟踪使用过的设备
int插槽[n];//跟踪哪个设备在哪个插槽中
整数边[n-1]//树缘
int ats=INF;
布尔交叉()//检查是否有设备交叉的函数
//看来你已经写了类似的东西,所以我跳过了
solve(intx,inttotal)//尝试将剩余块放入插槽x的函数
{
如果(x==n)//所有插槽都已填满,则表示我们已完成
{
ats=最小值(ats,总计);
回来
}
if(total>ats)//针对某些加速进行一些修剪优化
return;//因为无论我们做什么,我们都无法战胜这一成本

对于(int i=0;i你只要求一个想法: 而不是所有的n!排列,你必须使用回溯忽略排列,这肯定会使电线交叉

i、 e.u有排列1234…11,并且123Part已经使导线交叉,所以你们可以忽略4…11 part的所有排列

下面是一些实现细节的伪代码:

int n;              // devices
int cost[n][n];     // cost for putting device i into slot j
bool used[n]={0};   // we need to keep track of used devices
int slots[n];       // tracks which device is in which slot
int edges[n-1];     //edges of a tree
int ats = INF;

bool cross(); //function that checks if any devices cross
              //it seems you already wrote something similar, so i skipped this

solve(int x, int total)     //function that tries putting remaining blocks into slot x
{
if(x==n) //all slots are filled means we`re done
{
ats = min(ats,total);
return;
}

if(total > ats)     // some pruning optimization for some speedup
return;             // cause no matter what we do we won`t be able to beat this cost


for(int i=0; i<n; i++)
if(!used[i])                    //if device is not used and
                                //we can try putting it into our slot
{
    slot[x] = i;
    used[i] = true;
    if(!cross())                //if putting device i into slot x makes some lines cross, skip it
    solve(x+1,total + cost[i][x]);
    used[i] = false;
}
}

main()
{

for (int i=0; i<n; i++) //try all devices into slot 0
{
used[i] = true;
slot[i]=0;
solve(1,cost[i][0]);
used[i] = false;
}

print(ats);

}
int n;//设备
int成本[n][n];//将设备i放入插槽j的成本
bool used[n]={0};//我们需要跟踪使用过的设备
int slots[n];//跟踪哪个设备在哪个插槽中
int边[n-1];//树的边
int ats=INF;
bool cross();//检查是否有设备交叉的函数
//看来你已经写了类似的东西,所以我跳过了
solve(intx,inttotal)//尝试将剩余块放入插槽x的函数
{
如果(x==n)//所有插槽都已填满,则表示我们已完成
{
ats=最小值(ats,总计);
回来
}
if(total>ats)//针对某些加速进行一些修剪优化
return;//因为无论我们做什么,我们都无法战胜这一成本

对于(int i=0;i,您的程序生成所有可能的置换,然后测试置换是否有效。测试本身涉及嵌套循环。您可以尝试优化数据结构,以便您的检查更有效,或者您可以尝试尽早在搜索空间中找到死胡同,正如光子建议的那样

一种更有效的方法可能是设计程序,以便只创建有效的排列。这减少了搜索空间,也消除了测试

如果您查看问题描述中的示例,wrie网络是一个非循环图:

                            5          
                            |         
                        1   6
                        |   |
                    0---2---7
                        |   |
                        3   8               
                        |
                        4
如果从工具0开始并将其放入插槽0中,下一步是放置工具2及其“后代”的排列,即工具1、7和3及其各自连接的工具。从工具0的角度来看,可以将其转换为树:

                  [1237]
                / /  |  \
              1  2  [34] [678]
                   / |    |  \ \
                  3  4   [56] 7  8
                          | \
                          5  6
在这里,叶子只对应于一个工具。分支有几个工具。所有分支形成有效的排列

0 (1 2 (3 4) ((5 6) 7 8))
当然,
[56]
的每一个排列必须与其他分支的每一个排列相结合。您可以通过实现一种里程表来实现这一点,里程表不通过每个空间中从0到9的数字,而是通过分支的可能排列

生成的每个排列都是有效的,但此技术尚未创建所有可能的排列。请记住,我们已将工具0固定在插槽0中,但情况并非如此。但有效布局的拓扑结构可以通过旋转它并将工具0放置在插槽1、2等中来生成其他8个布局

这项技术将搜索空间从9!减少到9·4!·2!·3!·2!或减少70倍。无需测试,但以更复杂的数据结构为代价

(在您的12工具示例中,减少是极端的,其中导线网络实际上只是一条没有分叉的直线。)

此代码实现了所描述的技术:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>



enum {
    N = 16                          // hardcoded max. size
};

struct tool {
    int conn[N - 1];                // wire connections
    int nconn;

    int desc[N];                    // "descendants" of the tree node
    int ndesc;

    int cost[N];                    // row of the cost matrix
    int used;                       // flag for recursive descent
};

struct drill {
    int n;
    struct tool tool[N];

    int root;                       // root node    
    int branch[N];                  // indices of branch nodes
    int nbranch;                    // permutating branches

    int opt;                        // current optimum
};

void swap(int a[], int i, int j)
{
    int s = a[i]; a[i] = a[j]; a[j] = s;
}

void reverse(int a[], int i, int n)
{
    while (i < --n) swap(a, i++, n);
}

/*
 *      Turn an array to the next higher permutation. When the
 *      permutation is already the highest, return 0 and reset the
 *      array to the smalles permutation. Otherwise, return 1.
 */
int next_perm(int a[], int n)
{
    int i = n - 1;
    int k = n - 1;

    if (n < 2) return 0;

    while (k && a[k] < a[k - 1]) k--;
    if (k == 0) {
        reverse(a, 0, n);
        return 0;
    }
    k--;

    while (i > k && a[i] < a[k]) i--;
    swap(a, i, k);
    reverse(a, k + 1, n);

    return 1;
}

/*
 *      Insertion sort for sorting the branches at the beginning.
 */
void sort(int a[], int len)
{
    for (int i = 1; i < len; i++) {
        int k = i;

        while (k > 0 && a[k] < a[k - 1]) {
            swap(a, k, k - 1);
            k--;
        }
    }
}

/*
 *      Determine the list of descendants for each node.
 */
void descend(struct drill *dr, int n)
{
    struct tool *t = dr->tool + n;

    t->ndesc = 1;
    t->desc[0] = n;

    t->used = 1;

    for (int i = 0; i < t->nconn; i++) {
        int m = t->conn[i];

        if (dr->tool[m].used == 0) {
            t->desc[t->ndesc++] = m;
            descend(dr, m);
        }
    }

    if (t->ndesc > 1) {
        sort(t->desc, t->ndesc);
        dr->branch[dr->nbranch++] = n;
    }

    t->used = 0;
}

/*
 *      Fill the array a with the current arrangement in the tree.
 */
int evaluate(struct drill *dr, int a[], int n)
{
    struct tool *t = dr->tool + n;
    int m = 0;

    if (n == dr->root) {
        a[0] = dr->root;
        return 1 + evaluate(dr, a + 1, dr->tool[n].conn[0]);
    }

    for (int i = 0; i < t->ndesc; i++) {
        int d = t->desc[i];

        if (d == n) {
            a[m++] = d;
        } else {
            m += evaluate(dr, a + m, d);
        }
    }

    return m;
}

/*
 *      Evaluate all possible permutations and find the optimum.
 */
void optimize(struct drill *dr)
{
    dr->opt = (1u << 31) - 1;

    for (;;) {
        int i = 0;
        struct tool *t = dr->tool + dr->branch[0];

        for (int j = 0; j < dr->n; j++) {
            int a[2 * N];
            int cost = 0;

            evaluate(dr, a, dr->root);

            for (int i = 0; i < dr->n; i++) {
                int k = (i + j) % dr->n;

                cost += dr->tool[i].cost[a[k]];
            }

            if (cost < dr->opt) dr->opt = cost;
        }

        while (next_perm(t->desc, t->ndesc) == 0) {
            i++;

            if (i == dr->nbranch) return;
            t = dr->tool + dr->branch[i];            
        }
    }
}

/*
 *      Read and prepare drill data, then optimize.
 */
int main(void)
{
    struct drill dr = {0};

    fscanf(stdin, "%d", &dr.n);

    for (int j = 0; j < dr.n; j++) {
        for (int i = 0; i < dr.n; i++) {
            scanf("%d", &dr.tool[j].cost[i]);
        }
    }

    for (int i = 1; i < dr.n; i++) {
        int a, b;

        scanf("%d", &a);
        scanf("%d", &b);

        dr.tool[a].conn[dr.tool[a].nconn++] = b;
        dr.tool[b].conn[dr.tool[b].nconn++] = a;
    }

    while (dr.tool[dr.root].nconn > 1) dr.root++;
    dr.tool[dr.root].used = 1;

    descend(&dr, dr.tool[dr.root].conn[0]);
    optimize(&dr);

    printf("%d\n", dr.opt);

    return 0;
}
#包括
#包括
#包括
枚举{
N=16//硬编码最大尺寸
};
结构工具{
int conn[N-1];//导线连接
国际网络;
int desc[N];//树节点的“后代”
国际ndesc;
int cost[N];//成本矩阵的行
int used;//递归下降的标志
};
结构钻{
int n;
结构工具[N];
int root;//根节点
int branch[N];//分支节点的索引
int nbranch;//置换分支
int opt;//当前最佳值
};
无效交换(整数a[],整数i,整数j)
{
int s=a[i];a[i]=a[j];a[j]=s;
}
无效反向(整数a[],整数i,整数n)
{
而(i<--n)交换(a,i++,n);
}
/*
*将数组转到下一个更高的排列
*排列已经是最高的,返回0并重置
*数组,否则返回1。
*/
int next_perm(int a[],int n)
{
int i=n-1;
int k=n-1;
如果(n<2)返回0;
而(k&&a[k]k&&a[i]0&&a[k]