【问题标题】:Implementing a backtrack search with heuristic?用启发式实现回溯搜索?
【发布时间】:2013-05-06 16:41:12
【问题描述】:

我对搜索算法和回溯编程越来越感兴趣。目前,我已经实现了算法 X(请参阅我的其他帖子:Determine conflict-free sets?)来解决精确覆盖问题。这很好用,但我现在有兴趣使用更基本的回溯变体来解决这个问题。我只是无法弄清楚如何做到这一点。问题描述和之前一样:

假设您有一堆集合,而每个集合都有几个子集。

Set1 = {(香蕉、菠萝、橙子)、(苹果、羽衣甘蓝、黄瓜)、(洋葱、大蒜)}

Set2 = {(香蕉、黄瓜、大蒜)、(鳄梨、番茄)}

...

SetN = { ... }

现在的目标是从每个集合中选择一个子集,而每个子集必须与任何其他选定的子集无冲突(一个元素不包含在任何其他选定的子集中)。

例如,我编写了两个 Java 类。集合由单个字符标识,元素是纯数字。

我具体有两个问题:

  • 如何迭代所有集合/子集,以便可以使用启发式方法(选择具有最少元素、最低成本的子集...)
  • 如何维护选定的集合+子集及其包含的元素。

我能找到的所有其他示例都是数独或 n-Queens,并且都使用固定的 for 循环。除此之外,我正在考虑一种相当通用的方法,其中可以使用函数“isPossiblePartialSolution”来检查所选路径/集是否可能与先前选择的子集/元素冲突。

这是两个 Java 类:

import java.util.ArrayList;

public class Main {

public static void main(String[] args) {

    ArrayList<Set> allSets = buildRandomTest();

    for(Set r : allSets) {
        System.out.print("Set with id: " + r.id + " is subset in collection: " + r.name + " and contains: ");
        for(Integer n : r.listOfElements) {
            System.out.print(" " + n + " ");
        }
        System.out.println();
    }

}

public static int myRandomRange(int low, int high) {
    return (int) (Math.random() * (++high - low) + low);
}

public static ArrayList<Set> buildRandomTest() {

    ArrayList<Set> mySet = new ArrayList<Set>();

    int numberOfSets = myRandomRange(10, 12);

    for(int i=0; i<numberOfSets; i++) {
        String nameOfSet = Character.toString((char) myRandomRange(65, 67));
        Set tmp = new Set(nameOfSet, i);

        ArrayList<Integer> listOfElements = new ArrayList<Integer>();
        int elementsInList = myRandomRange(2, 4);

        for(int j=0; j<elementsInList; j++) {
            listOfElements.add(myRandomRange(1,30));
        }

        tmp.listOfElements = listOfElements;
        mySet.add(tmp);
    }

    return mySet;
}

}

import java.util.ArrayList;

public class Set {

public String name;
public int id;
public ArrayList<Integer> listOfElements;

public Set(String name, int id) {
    this.name = name;
    this.id = id;
    listOfElements = new ArrayList<Integer>();
}

}

【问题讨论】:

    标签: java search backtracking np


    【解决方案1】:

    编辑:实际上听起来您已经实现了 Dancing Links(使用名称“算法 X”),所以我不确定您要的是什么。 “更基本的回溯变体”是指“更慢的变体”吗? Dancing Links 是你能得到的最基础的东西......

    原始答案:如果我这样做,我会尝试将其简化为精确覆盖问题,这可以通过Dancing Links 解决。即,构造一个由 0 和 1 组成的矩阵,找到其行的子集,使得每列中恰好有一个 1,然后将该行集转换回原始问题的答案。

    以下答案是用 C++(11) 编写的,但希望您能看到如何将其翻译成 Java。在 Java 中实现 Dancing Links 作为练习留给读者和/或您选择的搜索引擎。

    enum Element {
        apple, avocado, banana, cucumber, garlic,
        kale, onion, orange, pineapple, NUM_ELEMENTS
    };
    
    std::vector<std::vector<std::set<Element>>> sets = {
        { {banana, pineapple, orange}, {apple, kale, cucumber}, {onion, garlic} },
        { {banana, cucumber, garlic}, {avocado, tomato} },
        ...
    };
    
    int rows, columns;
    
    // One row per subset, obviously...
    rows = 0;
    for (auto &vs : sets) {
        rows += vs.size();
    }
    // ...plus N more rows for "vegetable X is not in any selected subset".
    rows += NUM_ELEMENTS;
    
    // One column per vegetable, obviously...
    columns = NUM_ELEMENTS;
    // ...plus N more columns for "we've chosen a subset from set X".
    columns += sets.size();
    
    Matrix M(rows, columns);
    
    M.initialize_to_all_zeros();
    
    int r = 0;
    for (int i=0; i < sets.size(); ++i) {
        for (int j=0; j < sets[i].size(); ++j) {
            auto &subset = sets[i][j];
            M[r][NUM_ELEMENTS + i] = 1;  // the subset is a member of set i
            for (Element veg : subset) {
                M[r][veg] = 1;  // the subset contains this element
            }
            ++r;
        }
    }
    for (Element veg = apple; veg < NUM_ELEMENTS; ++veg) {
        M[r][veg] = 1;
        ++r;
    }
    
    // Now perform Dancing Links on the matrix to compute an exact cover.
    std::set<int> row_indices = dancing_links(M);
    
    // Now print out the subsets.
    r = 0;
    for (int i=0; i < sets.size(); ++i) {
        for (int j=0; j < sets[i].size(); ++j) {
            if (row_indices.find(r) != row_indices.end()) {
                print_set(sets[i][j]);
            }
            ++r;
        }
    }
    // And print the unused vegetables, just for kicks.
    for (Element veg = apple; veg < NUM_ELEMENTS; ++veg) {
        if (row_indices.find(r) != row_indices.end()) {
            std::cout << "Vegetable " << veg << " was not mentioned above.\n";
        }
        ++r;
    }
    

    【讨论】:

    • 回溯的思想很笼统,可以应用于很多问题。 Dancing Links 只能应用于精确覆盖问题。应该可以使用“正常”回溯方法来实现这一点(我知道,这将比 DLX 慢!)。根据我的理解,我们需要一个函数 then 来告诉我们是否与之前的任何决定存在冲突。除此之外,这还允许使用不同的启发式方法!
    • 使用不同的启发式是我想要实现的。想象一下,“购买”一套或另一套更便宜(因此与 Dancing Links 不同,我们会选择成本最低的一套,而不是元素最少的一套)。使用 Dancing Links 无法做到这一点。
    • @user26372 Dancing Links 通常使用启发式“首先检查具有最少 1 的列”(即,首先尝试满足最困难约束的行),但如果你不喜欢那个,你当然可以使用不同的启发式。请参阅my own Dancing Links implementation in C, here,并想象一下如何更改#if USE_HEURISTIC 下的代码以使用不同的启发式算法。
    • 这行不通,因为列没有说明受影响的行。要使用某些自定义指标,您必须根据某些属性而不是列来选择行。我确实在寻找一种比 Dancing Links 慢的变体,因为我想使用一些定制的启发式方法。应该可以迭代所有集合,就像对数独、n-Queens 等所做的那样,而无需实际使用舞蹈链接。
    • 查看算法描述(在框中)here。请注意,步骤 2 和 3 可以替换为单个步骤“选择一行 r(非确定性)”。在您的 Dancing Links 求解器中实现此功能(即,始终按照 第 1 行、第 2 行、第 3 行... 的顺序删除行,按您喜欢的任何“成本”函数排序)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-29
    • 2014-01-13
    相关资源
    最近更新 更多