Как переставить двоичные строки, чтобы минимизировать расстояние между ними? - PullRequest
0 голосов
/ 26 августа 2018

У меня есть массив весов, например

[1, 0, 3, 5]

Расстояние между двумя строками определяется как сумма весов для разных битов, например:

size_t distance(const std::string& str1, const std::string& str2, const std::vector<size_t>& weights) {
  size_t result = 0;
  for (size_t i = 0; i < str1.size(); ++i) {
    if (str1[i] != str2.at(i))
      result += weights.at(i);
  }
  return result;
}

и начальная строка, например

'1101'

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

'1001'  # changed bits: 2nd. Because it has lowest weight. Distance is 0
'0101'  # changed bits: 1st.                               Distance is 1
'0001'  # changed bits: 1st, 2nd.                          Distance is 1
'1011'  # changed bits: 2nd, 3rd.                          Distance is 3
'1111'  # changed bits: 3rd.                               Distance is 3
'0111'  # changed bits: 1st, 3rd.                          Distance is 4
'0011'  # changed bits: 1st, 2nd, 3rd.                     Distance is 4
'1100'  # changed bits: 4th.                               Distance is 5
'1000'  # changed bits: 2nd, 4th.                          Distance is 5
'0100'  # changed bits: 1st, 4th.                          Distance is 6
'0000'  # changed bits: 1st, 2nd, 4th.                     Distance is 6
'1110'  # changed bits: 3rd, 4th.                          Distance is 8
'1010'  # changed bits: 2nd, 3rd, 4th.                     Distance is 8
'0110'  # changed bits: 1st, 3nd, 4th.                     Distance is 9
'0010'  # changed bits: 1st, 2nd, 3rd, 4th.                Distance is 9

Мне не нужен код, мне нужен только алгоритм, который получает строку длины N, массив весов одинаковой длины и i в качестве входных данных и генерирует i-ю перестановку, не генерируя весь список и не сортируя его.

Ответы [ 4 ]

0 голосов
/ 26 августа 2018

Если вам нужна только i-я перестановка, то вам действительно нужно взглянуть на веса.

Если веса были отсортированы в обратном порядке, скажем, [5,3,1,0], и вы хотели 5-ю перестановку, то вам нужно было бы перевернуть 0, 1, 0, 1 как 5 = 0101 в двоичном виде.

Так что вам нужно очень маленькое сопоставление веса и его исходного индекса. Затем сортируйте от наименьшего к меньшему, возьмите N-ю перестановку, основанную на двоичном представлении N, и переверните сопоставленные биты исходной строки.

0 голосов
/ 26 августа 2018

В современном C ++ способ выполнить то, что вы просите, - это использовать std::bitset для представления всех возможных битов мультимножество с и затем обернуть distance() компаратор структура функтора для вызова std::sort(). Я подчеркиваю возможный бит мультимножества , не перестановок , поскольку последние допускают только изменение порядка. Ваш код будет выглядеть примерно так:

#include <string>
#include <array>
#include <cmath>
#include <bitset>
#include <vector>
#include <algorithm>
#include <iostream>

constexpr size_t BITSET_SIZE = 4;

size_t distance(const std::string& str1, const std::string& str2, const std::array<size_t, BITSET_SIZE>& weights) {
    size_t result = 0;
    for (size_t i = 0; i < str1.size(); ++i) {
        if (str1[i] != str2.at(i))
            result += weights.at(i);
    }
    return result;
}

struct of_lesser_distance
{
    const std::bitset<BITSET_SIZE>& originalBitSet;
    const std::array<size_t, BITSET_SIZE>& distanceVec;

    inline bool operator() (const std::bitset<BITSET_SIZE>& lhs, const std::bitset<BITSET_SIZE>& rhs)
    {
        return distance(originalBitSet.to_string(), lhs.to_string(), distanceVec) < distance(originalBitSet.to_string(), rhs.to_string(), distanceVec);
    }
};

int main()
{
    std::string s{"1101"};    
    std::array<size_t, 4> weights{1, 0, 3, 5};

    int possibleBitSetsCount = std::pow(2, s.length());

    std::vector<std::bitset<BITSET_SIZE>> bitSets;

    // Generates all possible bitsets
    for (auto i = 0; i < possibleBitSetsCount; i++)
        bitSets.emplace_back(i);

    // Sort them according to distance
    std::sort(bitSets.begin(), bitSets.end(), of_lesser_distance{ std::bitset<BITSET_SIZE>(s), weights });

    // Print
    for (const auto& bitset : bitSets)
        std::cout << bitset.to_string().substr(BITSET_SIZE - s.length(), s.length()) << " Distance: " << distance(s, bitset.to_string(), weights) << "\n";
}

Выход:

1001 Distance: 0
1101 Distance: 0
0001 Distance: 1
0101 Distance: 1
1011 Distance: 3
1111 Distance: 3
0011 Distance: 4
0111 Distance: 4
1000 Distance: 5
1100 Distance: 5
0000 Distance: 6
0100 Distance: 6
1010 Distance: 8
1110 Distance: 8
0010 Distance: 9
0110 Distance: 9

Живая версия здесь .

Примечание. Таким образом, вам лучше изменить distance(), чтобы он работал на std::bitset с вместо std::string с, поскольку это сохранит все эти ненужные преобразования.

Мне не нужен код, мне нужен только алгоритм

Мне легче дать код, но дайте мне знать, если вы хотите что-то еще.

0 голосов
/ 26 августа 2018

Звучит как тяжелая проблема.

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

Таким образом, ваш алгоритм больше не зависит от строки, вы находите i-ую битовую маску, XOR it (оператор ^ в C ++) с битовой маской входной строки, и вы получаете свой результат. Сложнее всего найти i-ю битовую маску, но таким образом, то есть без использования строк во внутренних циклах алгоритма, код будет намного быстрее (на несколько порядков).

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

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

P.S. Есть один особый случай, который может сработать для вас. Сортируйте весовые коэффициенты и проверьте, верно ли следующее weights[N] == weights[N-1] || weights[N] >= sum( weights[0 .. N-1] для всех 1

Кстати, веса, которые у вас есть в вопросе, удовлетворяют этому условию, потому что 1> = 0, 3> = 0 + 1 и 5> = 0 + 1 + 3, так что этот простой алгоритм будет работать нормально для ваших конкретных весов.

Обновление: Вот полное решение. Он печатает немного другой результат, чем ваш образец, например, в вашем примере у вас есть «1011», а затем «1111», мой код будет печатать «1011» сразу после «1111», но их расстояние будет таким же, т. е. мой алгоритм все еще работает в порядке.

#include <string>
#include <vector>
#include <algorithm>
#include <stdio.h>

struct WeightWithBit
{
    size_t weight, bit;
};

// Sort the weights while preserving the original order in the separate field
std::vector<WeightWithBit> sortWeights( const std::vector<size_t>& weights )
{
    std::vector<WeightWithBit> sorted;
    sorted.resize( weights.size() );
    for( size_t i = 0; i < weights.size(); i++ )
    {
        sorted[ i ].weight = weights[ i ];
        sorted[ i ].bit = ( (size_t)1 << i );
    }
    std::sort( sorted.begin(), sorted.end(), []( const WeightWithBit& a, const WeightWithBit& b ) { return a.weight < b.weight; } );
    return sorted;
}

// Check if the simple bit-based algorithm will work with these weights
bool willFastAlgorithmWork( const std::vector<WeightWithBit>& sorted )
{
    size_t prev = 0, sum = 0;
    for( const auto& wb : sorted )
    {
        const size_t w = wb.weight;
        if( w == prev || w >= sum )
        {
            prev = w;
            sum += w;
            continue;
        }
        return false;
    }
    return true;
}

size_t bitsFromString( const std::string& s )
{
    if( s.length() > sizeof( size_t ) * 8 )
        throw std::invalid_argument( "The string's too long, permutation index will overflow" );
    size_t result = 0;
    for( size_t i = 0; i < s.length(); i++ )
        if( s[ i ] != '0' )
            result |= ( (size_t)1 << i );
    return result;
}

std::string stringFromBits( size_t bits, size_t length )
{
    std::string result;
    result.reserve( length );
    for( size_t i = 0; i < length; i++, bits = bits >> 1 )
        result += ( bits & 1 ) ? '1' : '0';
    return result;
}

// Calculate the permitation. Index is 0-based, 0 will return the original string without any changes.
std::string permitation( const std::string& str, const std::vector<WeightWithBit>& weights, size_t index )
{
    // Reorder the bits to get the bitmask.
    // BTW, if this function is called many times for the same weights, it's a good idea to extract just the ".bit" fields and put it into a separate vector, memory locality will be slightly better.
    size_t reordered = 0;
    for( size_t i = 0; index; i++, index = index >> 1 )
        if( index & 1 )
            reordered |= weights[ i ].bit;

    // Convert string into bits
    const size_t input = bitsFromString( str );
    // Calculate the result by flipping the bits in the input according to the mask.
    const size_t result = input ^ reordered;
    // Convert result to string
    return stringFromBits( result, str.length() );
}

int main()
{
    const std::vector<size_t> weights = { 1, 0, 3, 5 };

    using namespace std::literals::string_literals;
    const std::string theString = "1101"s;

    if( weights.size() != theString.length() )
    {
        printf( "Size mismatch" );
        return 1;
    }

    if( weights.size() > sizeof( size_t ) * 8 )
    {
        printf( "The string is too long" );
        return 1;
    }

    // Sort weights and check are they suitable for the fast algorithm
    const std::vector<WeightWithBit> sorted = sortWeights( weights );
    if( !willFastAlgorithmWork( sorted ) )
    {
        printf( "The weights aren't suitable for the fast algorithm" );
        return 1;
    }

    // Print all permutations
    const size_t maxIndex = ( 1 << weights.size() ) - 1;
    for( size_t i = 0; true; i++ )
    {
        const std::string p = permitation( theString, sorted, i );
        printf( "%zu: %s\n", i, p.c_str() );
        if( i == maxIndex )
            break;  // Avoid endless loop when the string is exactly 32 or 64 characters.
    }
    return 0;
}
0 голосов
/ 26 августа 2018

Эта проблема не может быть эффективно решена. Она может быть полиномиально сведена к проблеме подмножества сумм, которая сама по себе является проблемой NP-полной.

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

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

Вы можете попробовать что-то вроде [1] :
1. Сгенерируйте все возможные строки той же длины, что и базовая строка. Это довольно просто. Просто выполните цикл от 0 до (2 | base_str | -1) и используйте sprintf(&strs[loop_counter]"%b", loop_counter)
2. Сортируйте strs, используя qsort и используйте distance в качестве компаратора. Что-то вроде qsort(str, 1 << strlen(base_str)-1, sizeof(char*), comp), где comp - это функция, принимающая две строки и возвращающая -1, если первая имеет меньшее расстояние до base_str, чем вторая, 0, если две имеют равные расстояния, и 1, если первая находится дальше от base_str, чем Второй аргумент.

[1] Я программист на C, а не на C ++, поэтому я уверен, что есть и другие (возможно, лучшие) способы сделать то, что я предлагаю в C ++, но мои примеры приведены в C.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...