Алгоритм минимального изменения, который максимизирует 'свопинг'

Это - вопрос на комбинаторике от нематематика, поэтому попытайтесь терпеть меня!

Учитывая массив n отличных символов, я хочу генерировать подмножества k символов в минимальной заявке на изменение, т.е. порядка, в котором поколение i+1 содержит точно один символ, который не был в поколении i. Это не слишком твердо сам по себе. Однако я также хочу максимизировать количество случаев, в который символ, который выгружается в поколении i+1, тот же символ, который был загружен в поколении i. Проиллюстрировать, для n=7, k=3:

abc abd abe* abf* abg* afg aeg* adg* acg* acd туз* acf* aef автоматическое радиопеленгование* ade процессор баз данных фирмы Borland bdf bef bcf* bce BCD* BCG* здание просит* bfg* cfg ceg* кодирование* cde cdf* cef градус определения dfg efg

Строки asterisked указывают на случай, который я хочу максимизировать; например, e, который является новым в поколении 3, abe, заменяет d, который был новым в поколении 2, abd. Не кажется возможным иметь, это происходит в каждом поколении, но я хочу, чтобы это происходило максимально часто.

Типичные размеры массива, которые я использую, 20-30 и размеры подмножества приблизительно 5-8.

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

Спасибо

Kim Bastin

Редактирование 15 июня 2010:

Я, действительно кажется, имею в более глубокую воду, чем я думал, и в то время как я благодарен за все ответы, не все они были релевантны. Поскольку пример решения, которое не соответствует, позволь мне отправить свою собственную процедуру Незначка генерации k-ary подмножества набора символов s в минимальной заявке на изменение. Вещи, которые необходимо знать для понимания кода: предварительно изложенный * означает размер структуры, поэтому если s является строкой, *s означает размер s (количество символов, которые это содержит). || операция конкатенации строк. Предварительно изложенный! производит каждый элемент структуры, например, каждый символ строки, в свою очередь на последовательных передачах. И 'приостановить' управляющая структура возвращает результат процедуры, но оставляет процедуру 'в напряженности' со всеми локальными переменными на месте, так, чтобы к новым результатам можно было привести, если процедуру называют в цикле.

procedure revdoor(s, k)  
# Produces all k-subsets of a string or character set s in a 'revolving  
# door' order. Each column except the first traverses the characters  
# available to it in alphabetical and reverse alphabetical order  
# alternately. The order of the input string is preserved. 
# If called in a loop as revdoor("abcdefg", 3), 
# the order of production is: abc, abd, abe, abf, abg, acg, acf, ace, acd,  
# ade, adf, adg, aeg, aef, afg, bfg, bef, beg, bdg, bdf, bde, bcd, bce,  
# bcf, bcg, cdg, cdf, cde, cef, ceg, cfg, dfg, deg, def, efg  

local i  
static Ctl  

if /Ctl then {             # this means 'if Ctl doesn't exist'
  if k = 0 then return ""  
  Ctl := list(k, 1)        # a list of k elements, each initialised to 1.
}  

if Ctl[k] = 1 then {  
  if k = 1 then suspend !s else  
    every i := 1 to *s-k+1 do {  
      suspend s[i] || revdoor(s[i+1:0], k-1)  
    }   
  } else {  
    if k = 1 then suspend !reverse(s) else  
    every i := -k to -*s by -1 do {  
      suspend s[i] || revdoor(s[i+1:0], k-1)  
    }  
  }  

  # the following line multiplies element k of Ctl by -1 if k < size of Ctl
  # (this controls the order of generation of characters), 
  # and destroys Ctl on final exit from the procedure.
  if k < *Ctl then Ctl[k] *:= -1 else Ctl := &null   

end  

Обратите внимание, что вывод вышеупомянутой процедуры не оптимален в моем смысле. Один результат моих расследований до сих пор состоит в том, что максимум, 'подкачивающий счет' к списку k-ary подмножеств n элементов, не является меньше, чем расческа (n-1, k), или в случае n=7, k=3, максимальный счет является, по крайней мере, расческой (6, 3) = 20. Я определяю 'счет свопинга' списка как количество объектов в списке, новый элемент которого заменяет элемент в предыдущем объекте, который был самостоятельно новым. У меня нет математического оборудования для доказательства этого, но легко видеть с k=1 или k=2. Наверняка (n, k) немного более высокий счет возможен, как в случае n=7, k=3:

abc abd abe abf abg
acg adg aeg afg
efg dfg cfg bfg
попросите BCG здания
BCD bce bcf
bdf bef
определение cef aef
автоматическое радиопеленгование acf
туз acd
ade
процессор баз данных фирмы Borland cde
кодирование cdf
ceg
градус (подкачивающий счет = 21)

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

Kim

7
задан Kim Bastin 15 June 2010 в 12:02
поделиться

4 ответа

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

char c[] = {'a', 'b', 'c', 'd', 'e'};
const int n = 5;
const int k = 3;
char s[k];

void print()
{
    for( int i = 0; i < k; ++i )
        putchar(c[s[i]]);
    putchar('\n');
}

bool increment( int m )
{
    // reached the limit?
    if( ++s[m] == n && m == 0 )
        return false;

    next:   
    for( int i = 0; i < m; ++i )
    {
        if( s[m] == n )
        {
            // carry
            s[m] = 0;
            if( !increment( m-1 ))
                return false;
            goto next;
        }
        else if( s[i] == s[m] )
        {
            // the character is already used
            ++s[m];
            goto next;
        }   
    }
    return true;
}

int main(int, char**)
{   
    // initialise
    for( int i = 0; i < k; ++i )
        s[i] = i;

    // enumerate all combinations
    do
        print();
    while(increment(k-1));
}
1
ответ дан 7 December 2019 в 20:34
поделиться

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

public class Homework
{
  /**
   * Prints all k-combinations of a set of n elements. Answer to this 
   * question: http://stackoverflow.com/questions/2698551
   */
  public static void main(String[] args)
  {
    Combinations combinations = new Combinations(7, 3);
    System.out.printf(
        "Printing all %d %d-combinations of a set with %d elements:\n", 
        combinations.size(), combinations.k, combinations.n);
    for (int[] c : combinations)
      System.out.println(Arrays.toString(c));
  }

  /**
   * Provides an iterator for all k-combinations of a set of n elements. 
   */
  static class Combinations implements Iterable<int[]>  
  {
    public final int n, k;

    public Combinations(int n, int k)
    {
      if (n < 1 || n < k)
        throw new IllegalArgumentException();
      this.n = n;
      this.k = k;
    }

    @Override
    public Iterator<int[]> iterator()
    {
      return new Iterator<int[]>()
      {
        private int[] c;

        @Override
        public void remove() { throw new UnsupportedOperationException(); }

        @Override
        public int[] next()
        {
          if (c == null)
          {
            c = new int[k];
            for (int i = 0; i < k; i++)
              c[i] = i;
          }
          else
          {
            int i = c.length - 1;
            while (i >= 0 && c[i] == n - k + i)
              i--;

            if (i < 0)
              throw new NoSuchElementException();

            c[i]++;
            for (int j = i + 1; j < c.length; j++)
              c[j] = c[i] + j - i;
          }
          return c.clone(); // remove defensive copy if performance is more important
        }

        @Override
        public boolean hasNext() { return c == null || c[0] < n - k; }
      };
    }

    /**
     * Returns number of combinations: n! / (k! * (n - k)!).
     */
    public BigInteger size()
    {
      BigInteger s = BigInteger.valueOf(n);
      for (int i = n - 1; i > n - k; i--)
        s = s.multiply(BigInteger.valueOf(i));
      for (int i = k; i > 1; i--)
        s = s.divide(BigInteger.valueOf(i));
      return s;
    }
  }
}

Вот вывод для вашего примера:

Printing all 35 3-combinations of a set with 7 elements:
[0, 1, 2] [0, 1, 3] [0, 1, 4] [0, 1, 5] [0, 1, 6] [0, 2, 3] [0, 2, 4] [0, 2, 5] [0, 2, 6] [0, 3, 4] 
[0, 3, 5] [0, 3, 6] [0, 4, 5] [0, 4, 6] [0, 5, 6] [1, 2, 3] [1, 2, 4] [1, 2, 5] [1, 2, 6] [1, 3, 4] 
[1, 3, 5] [1, 3, 6] [1, 4, 5] [1, 4, 6] [1, 5, 6] [2, 3, 4] [2, 3, 5] [2, 3, 6] [2, 4, 5] [2, 4, 6] 
[2, 5, 6] [3, 4, 5] [3, 4, 6] [3, 5, 6] [4, 5, 6] 
0
ответ дан 7 December 2019 в 20:34
поделиться

Вместо того, чтобы начинать с алгоритма, я попытался придумать способ найти форму для максимального «показателя подкачки», чтобы вы знали, к чему стремиться. Часто из такого доказательства может появиться алгоритм создания желаемой структуры.

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

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

  • «n выбрать k» вершин
  • каждая вершина имеет степень k (nk)
  • количество ребер = «n выбрать k» * k (nk) / 2 = «n выбрать 2» * «n -2 choose k-1 "

Эти графы очень симметричны. График такой же для любого заданного {n, k}, как и для {n, n-k}. Если k = 1 или k = n-1, это полный граф на n вершинах (каждая комбинация отличается от всех остальных только одним символом). Однако я не вижу здесь очевидного алгоритма.

Edit: Моя следующая мысль заключалась в том, чтобы представить график с несколько иной интерпретацией. Вы можете думать о каждой {n, k} -комбинации как о последовательности из n битов, где есть k единиц. Положение единиц соответствует тому, какой из n символов присутствует в комбинации. Итак, для n = 7 k = 3, abc равно 1110000, adg равно 1001001, efg равно 0000111. С его помощью вы также можете представить себе точки, лежащие в углах n-мерного гиперкуба. Итак, для данной подпоследовательности ребра соответствуют критериям «минимальной замены», если они копланарны: я думаю о них как о «плоскостях, разрезающих» гиперкуб.

Вы ищете гамильтонов путь через этот граф комбинаций, который соответствует вашим особым критериям.

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

0
ответ дан 7 December 2019 в 20:34
поделиться

Чтобы получить хороший ответ, вычисление список комбинаций всех сразу приемлем, или вам нужно вычислять их по одной за раз? Другими словами, вам нужна функция:

Combination nextCombo();

или

vector<Combination> allCombinations();

будет приемлемым?

Если вычисление комбинаций в пакетном режиме допустимо, возможно, что итеративно-углубляющий поиск A * (или просто A * поиск, но я подозреваю, что у него закончилась память) будет работать. При допустимой эвристике A * гарантированно дает оптимум. У меня мало времени, поэтому я решил опубликовать этот частичный ответ и отредактировать сообщение, если у меня будет время написать код.

A * - алгоритм поиска по графу. В этом случае узлы представляют собой списки комбинаций, которые использовались до сих пор (в списке не допускаются дубликаты). Мой план состоял в том, чтобы использовать представление узлов в виде битовой строки. n = 30 поместится в 32-битное целое число. Мы можем произвольно переставить любое решение так, чтобы первая комбинация начиналась с нулей и заканчивалась единицами, то есть 000 ... 1111.Узел с более коротким списком соединяется с более длинным, если два списка одинаковы до последнего элемента, а последний элемент отличается только тем, что один нулевой бит перевернут в 1, а один бит 1 - на 0.. расстояние между ними равно 0, если произошел «своп», и 1, если был своп.

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

Чтобы проиллюстрировать эту эвристику, рассмотрим последовательность abc abd abe * abf * abg afg сверху. (буквы были бы числами в моем обращении, но это незначительное различие). В этой последовательности (которая будет одним узлом в графе поиска) будут отмечены следующие места:

 1   2   3
*a      
 b  *b   
 c   c  *c
 d   d  *d
 e   e  *e
    *f  *f
        *g

Таким образом, эвристика предсказывает, что требуется по крайней мере один обмен (поскольку в позиции 3 нет неотмеченных элементов и текущая позиция 2).

Если у меня будет время, я отредактирую это, чтобы указать код публикации и производительность алгоритма.


Re: результат полноты NP (в комментарии к ответу Зака ​​Томпсона). Граф, на котором мы ищем гамильтонов путь минимальной стоимости, имеет особую структуру. Например, обычно NP-полная проблема гамильтонова пути может быть решена за время O (n) с помощью алгоритма «перечислить все комбинации», где n - количество узлов в графе.Эта структура делает возможным, что, хотя на обычном графе покрытие вершин является жестким, на вашем графике оно может быть полиномиальным (даже линейным или квадратичным). Конечно, поскольку в графе много узлов, например, n = 30, k = 8, возможно, впереди еще много вычислений.

0
ответ дан 7 December 2019 в 20:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: