2013-03-08 2 views
21

Я всегда хотел это сделать, но каждый раз, когда я начинаю думать о проблеме, он сдувает мой разум из-за его экспоненциального характера.Как разработать алгоритм вычисления обратного отсчета стиля математического номера головоломка

Проблема решатель я хочу, чтобы быть в состоянии понять и код для обратного отсчета по математике проблемы:

Учитывая набор чисел X1 для X5 стоимость, как они могут быть объединены с помощью математических операций, чтобы сделать Y. Вы можете применять умножение, деление, сложение и вычитание.

Итак, как 1,3,7,6,8,3348?

Ответ: (((8 * 7) + 3) -1) *6 = 348.

Как написать алгоритм, который может решить эту проблему? Где вы начинаете, когда пытаетесь решить такую ​​проблему? С какими важными соображениями вы должны думать при разработке такого алгоритма?

+4

Грубая сила? То есть попробуйте все комбинации, пока не получите правильный ответ. –

+0

Да, я думаю, что грубая сила - это путь –

+0

Я думаю, что когда у вас есть bruteforce, вы можете добавить ответы на вопросы, и люди будут рады помочь. – bjedrzejewski

ответ

6

Очень быстрый и грязный раствор в Java:

public class JavaApplication1 
{ 

    public static void main(String[] args) 
    { 
     List<Integer> list = Arrays.asList(1, 3, 7, 6, 8, 3); 
     for (Integer integer : list) { 
      List<Integer> runList = new ArrayList<>(list); 
      runList.remove(integer); 
      Result result = getOperations(runList, integer, 348); 
      if (result.success) { 
       System.out.println(integer + result.output); 
       return; 
      } 
     } 
    } 

    public static class Result 
    { 

     public String output; 
     public boolean success; 
    } 

    public static Result getOperations(List<Integer> numbers, int midNumber, int target) 
    { 
     Result midResult = new Result(); 
     if (midNumber == target) { 
      midResult.success = true; 
      midResult.output = ""; 
      return midResult; 
     } 
     for (Integer number : numbers) { 
      List<Integer> newList = new ArrayList<Integer>(numbers); 
      newList.remove(number); 
      if (newList.isEmpty()) { 
       if (midNumber - number == target) { 
        midResult.success = true; 
        midResult.output = "-" + number; 
        return midResult; 
       } 
       if (midNumber + number == target) { 
        midResult.success = true; 
        midResult.output = "+" + number; 
        return midResult; 
       } 
       if (midNumber * number == target) { 
        midResult.success = true; 
        midResult.output = "*" + number; 
        return midResult; 
       } 
       if (midNumber/number == target) { 
        midResult.success = true; 
        midResult.output = "/" + number; 
        return midResult; 
       } 
       midResult.success = false; 
       midResult.output = "f" + number; 
       return midResult; 
      } else { 
       midResult = getOperations(newList, midNumber - number, target); 
       if (midResult.success) { 
        midResult.output = "-" + number + midResult.output; 
        return midResult; 
       } 
       midResult = getOperations(newList, midNumber + number, target); 
       if (midResult.success) { 
        midResult.output = "+" + number + midResult.output; 
        return midResult; 
       } 
       midResult = getOperations(newList, midNumber * number, target); 
       if (midResult.success) { 
        midResult.output = "*" + number + midResult.output; 
        return midResult; 
       } 
       midResult = getOperations(newList, midNumber/number, target); 
       if (midResult.success) { 
        midResult.output = "/" + number + midResult.output; 
        return midResult 
       } 
      } 

     } 
     return midResult; 
    } 
} 

UPDATE

Это в основном только простой перебор алгоритм с экспоненциальной сложностью. Однако вы можете получить некоторые импровизации, используя некоторую эвристическую функцию, которая поможет вам упорядочить последовательность чисел или (и) операций, которые вы будете обрабатывать на каждом уровне getOperatiosn() функции рекурсии.

Пример такой эвристической функции - это, например, разница между средним результатом и итоговым целевым результатом.

Этот способ, однако, улучшается только в лучших случаях и в среднем случае. Худшая сложность случая остается нетронутой.

Худшая сложность корпуса может быть улучшена путем какого-либо разветвления ветвей. Я не уверен, возможно ли в этом случае.

+0

Этот исходный код не компилируется на моей машине (javac 1.6.0_41). – Arne

+3

Да, есть Алмазная нотация - «Список runList = новый ArrayList <> (список);' вам нужно использовать Java 7 или заменить нотацию Diamond классическим синтаксисом java. –

+0

Было несколько бесплатных минут. Добавлено несколько пояснений и предложений по улучшению. Может обеспечить более чистую реализацию позже. –

6

Уверенный, что это экспоненциально, но это крошечный, поэтому хорошая (достаточная) наивная реализация была бы хорошим началом. Я предлагаю вам сбросить стандартную нотацию с помощью брекетинга и использовать постфикс, проще программировать. Вы всегда можете отключать выходы как отдельный этап.

Начните с перечисления и оценки всех (действительных) последовательностей чисел и операторов. Например (в постфиксе):

1 3 7 6 8 3 + + + + + -> 28 
1 3 7 6 8 3 + + + + - -> 26 

Мои Java Смешно, я не пришел сюда, чтобы быть смеялся, так что я уеду кодирование это до вас.

Для всех умных людей, читающих это: да, я знаю, что даже для небольшой проблемы вроде бы есть более разумные подходы, которые, вероятно, будут быстрее, я просто указываю OP на начальное рабочее решение. Кто-то может написать ответ с помощью более разумного решения.

Таким образом, чтобы ответить на ваши вопросы:

  • Начну с алгоритмом, который я думаю, что приведет меня быстро рабочего раствора. В этом случае очевидным (для меня) выбором является исчерпывающее перечисление и тестирование всех возможных вычислений.
  • Если очевидный алгоритм выглядит непривлекательным по соображениям производительности, я начну думать об этом глубже, вспомнив другие алгоритмы, которые, как я знаю, могут повысить производительность. Сначала я могу начать кодирование одного из них.
  • Если я придерживаюсь исчерпывающего алгоритма и обнаруживаю, что время выполнения на практике слишком длительное, я могу вернуться к предыдущему шагу и коду снова. Но это должно стоить того времени, есть оценка затрат/выгод, если мой код может опередить Рэйчел Райли, я был бы доволен.
  • Важные соображения включают мое время vs компьютерное время, мое стоит еще немного.
+0

Это определенно то, что нужно сделать. Порядок задачи - это 4^5 операторных комбинаций * (9 выберите 4) места для операторов. (Первый элемент в строке должен быть числом, а последним должен быть оператор). Другие последовательности могут создавать незаконные деревья. Это всего лишь 100 миллионов комбинаций? –

+0

Правильный выбор представления уравнения - постфикс или обратный полиш могут представлять все комбинации скобок в уравнении, хотя это может привести к дублированию представления. Комбинаторный анализ все еще показывает, что задача возможна. –

+1

У меня есть _feeling_, что размещение 6! число, связанное с операторами 4^5 в этом порядке, не будет обрабатывать случаи с круглыми скобками - так как этот порядок создает только левые или правые ассоциативные выражения. Я не думаю, что можно оценить (1 + 2)/(3 + 4) с помощью «N N N N op op op». –

1

Я думаю, вам нужно строго определить проблему в первую очередь. Что вам разрешено делать, а что нет. Вы можете начать с упрощения и только для умножения, деления, вычитания и добавления.

Теперь вы знаете свой проблемный набор входов, набор доступных операций и желаемого ввода.Если у вас есть только 4 операции и х входы, количество комбинаций меньше:

Номер заказа, в котором вы можете выполнять операции (x!) Раз, когда возможны варианты операций на каждом шаге: 4^x , Как вы можете видеть на 6 номеров, он дает разумные 2949120 операций. Это означает, что это может быть вашим пределом для алгоритма грубой силы.

Как только у вас есть грубая сила, и вы знаете, что она работает, вы можете начать улучшать свой алгоритм с помощью своего рода A* algorithm, который потребует от вас определения эвристических функций.

На мой взгляд, лучший способ подумать об этом - проблема поиска. Основная трудность будет заключаться в поиске хорошей эвристики или способах сокращения вашего проблемного пространства (если у вас есть номера, которые не совпадают с ответом, вам потребуется как минимум одно умножение и т. Д.). Начните с малого, опираясь на это и задайте вопросы, если у вас есть код.

5

Ввод, очевидно, представляет собой набор цифр и операторов: D = {1,3,3,6,7,8,3} и Op = {+, -, *, /}. Наиболее прямым алгоритмом был бы brute force решатель, который enumerates все возможные комбинации этих наборов. Если элементы set Op могут использоваться так часто, как требуется, но элементы из набора D используются ровно один раз. Псевдокод:

D={1,3,3,6,7,8,3} 
Op={+,-,*,/} 
Solution=348 
for each permutation D_ of D: 
    for each binary tree T with D_ as its leafs: 
     for each sequence of operators Op_ from Op with length |D_|-1: 
      label each inner tree node with operators from Op_ 
      result = compute T using infix traversal 
      if result==Solution 
       return T 
return nil 

Кроме этого: прочитайте ответы от jedrus07 и HPM.

5

Рабочее решение в C++ 11 ниже.

Основная идея - использовать оценку на основе стека (см. RPN) и конвертировать жизнеспособные решения в infix notation только для отображения.

Если у нас есть N вводные цифры, мы будем использовать операторы (N-1), так как каждый оператор является двоичным.

Сначала мы создаем действительные перестановки операндов и операторов (массив selector_). Допустимая перестановка - это та, которая может быть оценена без стека и заканчивается ровно одним значением (результатом) в стеке. Таким образом, 1 1 + действительно, но 1 + 1 нет.

Мы тестируем каждую такую ​​операнд-операторную перестановку с каждой перестановкой операндов (массив values_) и каждой комбинацией операторов (массив ops_). Соответствующие результаты довольно печатаются.

Аргументы взяты из командной строки как [-s] <target> <digit>[ <digit>...]. Переключатель -s предотвращает исчерпывающий поиск, печатается только первый результат сопоставления.

(используйте ./mathpuzzle 348 1 3 7 6 8 3, чтобы получить ответ на первоначальный вопрос)

Это решение не позволяет конкатенации ввода цифр для формирования чисел. Это может быть добавлено как дополнительный внешний цикл.

Рабочий код может быть загружен с here. (Примечание: я обновил этот код с поддержкой конкатенации входных цифр для формирования решения)

См. Комментарии к коду для дополнительного пояснения.

#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <stack> 
#include <iterator> 
#include <string> 

namespace { 

enum class Op { 
    Add, 
    Sub, 
    Mul, 
    Div, 
}; 

const std::size_t NumOps = static_cast<std::size_t>(Op::Div) + 1; 
const Op FirstOp = Op::Add; 

using Number = int; 

class Evaluator { 
    std::vector<Number> values_; // stores our digits/number we can use 
    std::vector<Op> ops_; // stores the operators 
    std::vector<char> selector_; // used to select digit (0) or operator (1) when evaluating. should be std::vector<bool>, but that's broken 

    template <typename T> 
    using Stack = std::stack<T, std::vector<T>>; 

    // checks if a given number/operator order can be evaluated or not 
    bool isSelectorValid() const { 
     int numValues = 0; 
     for (auto s : selector_) { 
      if (s) { 
       if (--numValues <= 0) { 
        return false; 
       } 
      } 
      else { 
       ++numValues; 
      } 
     } 
     return (numValues == 1); 
    } 

    // evaluates the current values_ and ops_ based on selector_ 
    Number eval(Stack<Number> &stack) const { 
     auto vi = values_.cbegin(); 
     auto oi = ops_.cbegin(); 
     for (auto s : selector_) { 
      if (!s) { 
       stack.push(*(vi++)); 
       continue; 
      } 
      Number top = stack.top(); 
      stack.pop(); 
      switch (*(oi++)) { 
       case Op::Add: 
        stack.top() += top; 
        break; 
       case Op::Sub: 
        stack.top() -= top; 
        break; 
       case Op::Mul: 
        stack.top() *= top; 
        break; 
       case Op::Div: 
        if (top == 0) { 
         return std::numeric_limits<Number>::max(); 
        } 
        Number res = stack.top()/top; 
        if (res * top != stack.top()) { 
         return std::numeric_limits<Number>::max(); 
        } 
        stack.top() = res; 
        break; 
      } 
     } 
     Number res = stack.top(); 
     stack.pop(); 
     return res; 
    } 

    bool nextValuesPermutation() { 
     return std::next_permutation(values_.begin(), values_.end()); 
    } 

    bool nextOps() { 
     for (auto i = ops_.rbegin(), end = ops_.rend(); i != end; ++i) { 
      std::size_t next = static_cast<std::size_t>(*i) + 1; 
      if (next < NumOps) { 
       *i = static_cast<Op>(next); 
       return true; 
      } 
      *i = FirstOp; 
     } 
     return false; 
    } 

    bool nextSelectorPermutation() { 
     // the start permutation is always valid 
     do { 
      if (!std::next_permutation(selector_.begin(), selector_.end())) { 
       return false; 
      } 
     } while (!isSelectorValid()); 
     return true; 
    } 

    static std::string buildExpr(const std::string& left, char op, const std::string &right) { 
     return std::string("(") + left + ' ' + op + ' ' + right + ')'; 
    } 

    std::string toString() const { 
     Stack<std::string> stack; 
     auto vi = values_.cbegin(); 
     auto oi = ops_.cbegin(); 
     for (auto s : selector_) { 
      if (!s) { 
       stack.push(std::to_string(*(vi++))); 
       continue; 
      } 
      std::string top = stack.top(); 
      stack.pop(); 
      switch (*(oi++)) { 
       case Op::Add: 
        stack.top() = buildExpr(stack.top(), '+', top); 
        break; 
       case Op::Sub: 
        stack.top() = buildExpr(stack.top(), '-', top); 
        break; 
       case Op::Mul: 
        stack.top() = buildExpr(stack.top(), '*', top); 
        break; 
       case Op::Div: 
        stack.top() = buildExpr(stack.top(), '/', top); 
        break; 
      } 
     } 
     return stack.top(); 
    } 

public: 
    Evaluator(const std::vector<Number>& values) : 
      values_(values), 
      ops_(values.size() - 1, FirstOp), 
      selector_(2 * values.size() - 1, 0) { 
     std::fill(selector_.begin() + values_.size(), selector_.end(), 1); 
     std::sort(values_.begin(), values_.end()); 
    } 

    // check for solutions 
    // 1) we create valid permutations of our selector_ array (eg: "1 1 + 1 +", 
    // "1 1 1 + +", but skip "1 + 1 1 +" as that cannot be evaluated 
    // 2) for each evaluation order, we permutate our values 
    // 3) for each value permutation we check with each combination of 
    // operators 
    // 
    // In the first version I used a local stack in eval() (see toString()) but 
    // it turned out to be a performance bottleneck, so now I use a cached 
    // stack. Reusing the stack gives an order of magnitude speed-up (from 
    // 4.3sec to 0.7sec) due to avoiding repeated allocations. Using 
    // std::vector as a backing store also gives a slight performance boost 
    // over the default std::deque. 
    std::size_t check(Number target, bool singleResult = false) { 
     Stack<Number> stack; 

     std::size_t res = 0; 
     do { 
      do { 
       do { 
        Number value = eval(stack); 
        if (value == target) { 
         ++res; 
         std::cout << target << " = " << toString() << "\n"; 
         if (singleResult) { 
          return res; 
         } 
        } 
       } while (nextOps()); 
      } while (nextValuesPermutation()); 
     } while (nextSelectorPermutation()); 
     return res; 
    } 
}; 

} // namespace 

int main(int argc, const char **argv) { 
    int i = 1; 
    bool singleResult = false; 
    if (argc > 1 && std::string("-s") == argv[1]) { 
     singleResult = true; 
     ++i; 
    } 
    if (argc < i + 2) { 
     std::cerr << argv[0] << " [-s] <target> <digit>[ <digit>]...\n"; 
     std::exit(1); 
    } 
    Number target = std::stoi(argv[i]); 
    std::vector<Number> values; 
    while (++i < argc) { 
     values.push_back(std::stoi(argv[i])); 
    } 
    Evaluator evaluator{values}; 
    std::size_t res = evaluator.check(target, singleResult); 
    if (!singleResult) { 
     std::cout << "Number of solutions: " << res << "\n"; 
    } 
    return 0; 
} 
+0

Голосовать за дополнительные решения, кроме ответа! – bjedrzejewski

0

Я нашел большой алгоритм из Oxford's Computer Science Docs (с исходным кодом Java) уже давно. И я восхищаюсь им каждый раз, когда читаю это решение. Я считаю, что это будет полезно.

Смежные вопросы